04-react-api

1. useReducer

作用:让 React 管理多个相对关联 的状态数据

import {useReducer} from 'react'

// 1. 定义 reducer(),根据不同的 action 返回不同的新状态
function reducer(state, action) {
  switch (action.type) {
    case 'INC':
      return state + 1
    case 'DEC':
      return state - 1
    default:
      return state
  }
}

function App() {
  // 2. 使用 useReducer 分派 action
  const [state, dispatch] = useReducer(reducer, 0)
  return (
      <>
        {/* 3. 调用 dispatch() 传入 action 对象,触发 reducer(),分派 action 操作,使用新状态更新视图 */}
        <button onClick={() => dispatch({type: 'DEC'})}>-</button>
        {state}
        <button onClick={() => dispatch({type: 'INC'})}>+</button>
      </>
  )
}

export default App



 












 



 







1. 更新流程

S3FXek9pZlh1dGdDZTd6S3VQVTlROUtOcmlZUDZiTy83TGJmVUt0K2tRPT0=

2. 分派action传参

做法:分派action时如果想要传递参数,需要在action对象中添加一个payload参数,放置状态参数

import {useReducer} from 'react'

// 1. 根据不同的 action 返回不同的新状态
function reducer(state, action) {
  console.log('reducer执行了')
  switch (action.type) {
    case 'INC':
      return state + 1
    case 'DEC':
      return state - 1
    case 'UPDATE':
      return state + action.payload
    default:
      return state
  }
}

function App() {
  // 2. 使用 useReducer 分派 action
  const [state, dispatch] = useReducer(reducer, 0)
  return (
      <>
        {/* 3. 调用 dispatch() 传入 action 对象,触发 reducer(),分派 action 操作,使用新状态更新视图 */}
        <button onClick={() => dispatch({type: 'DEC'})}>-</button>
        {state}
        <button onClick={() => dispatch({type: 'INC'})}>+</button>
        <button onClick={() => dispatch({type: 'UPDATE', payload: 100})}>
          update to 100
        </button>
      </>
  )
}

export default App



 















 






 







2. 渲染性能优化

1. useMemo

作用:在每次重新渲染时,能够缓存计算的结果

1. 斐波那契数列

基于 count 的变化计算斐波那契数列之和,但是当修改 num 状态时,斐波那契求和函数也会被执行,显然是一种浪费

function factorialOf(n) {
  console.log('斐波那契函数执行了')
  return n <= 0 ? 1 : n * factorialOf(n - 1)
}

function App() {
  const [count, setCount] = useState(0)
  const [num, setNum] = useState(0)

  // 计算斐波那契之和
  const sumByCount = factorialOf(count)

  return (
      <>
        {sumByCount}
        <button onClick={() => setCount(count + 1)}>+count:{count}</button>
        <button onClick={() => setNum(num + 1)}>+num:{num}</button>
      </>
  )
}

export default App










 





 





2. 缓存计算结果

思路:只有 count 发生变化时才重新进行计算

import {useMemo, useState} from 'react'

function fib(n) {
  console.log('计算函数执行了')
  if (n < 3) return 1
  return fib(n - 2) + fib(n - 1)
}

function App() {
  const [count, setCount] = useState(0)
  // 计算斐波那契之和
  // const sum = fib(count)
  // 通过 useMemo 缓存计算结果,只有 count 发生变化时才重新计算
  const sum = useMemo(() => {
    return fib(count)
  }, [count])

  const [num, setNum] = useState(0)

  return (
      <>
        {sum}
        <button onClick={() => setCount(count + 1)}>+count:{count}</button>
        <button onClick={() => setNum(num + 1)}>+num:{num}</button>
      </>
  )
}

export default App













 









 





2. React.memo

作用:允许组件在 props 没有改变的情况下跳过重新渲染

1. 组件默认的渲染机制

默认机制:顶层组件发生重新渲染,这个组件树的子级组件都会被重新渲染

function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is son</div>
}

function App() {
  const [, forceUpdate] = useState()
  console.log('父组件重新渲染了')
  return (
      <>
        <Son/>
        <button onClick={() => forceUpdate(Math.random())}>update</button>
      </>
  )
}

export default App










 






2. memo优化

机制:只有 props 发生变化时才重新渲染

子组件通过 memo 进行包裹,返回一个新的组件 MemoSon,只有传给 MemoSon 的 props 参数发生变化时才会重新渲染

import {useState, memo} from 'react'

const MemoSon = memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})

function App() {
  const [, forceUpdate] = useState()
  console.log('父组件重新渲染了')
  return (
      <>
        <MemoSon/>
        <button onClick={() => forceUpdate(Math.random())}>update</button>
      </>
  )
}

export default App


 









 






3. props变化重新渲染

import {useState, memo} from 'react'

const MemoSon = memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})

function App() {
  console.log('父组件重新渲染了')

  const [count, setCount] = useState(0)
  return (
      <>
        <MemoSon count={count}/>
        <button onClick={() => setCount(count + 1)}>+{count}</button>
      </>
  )
}

export default App


 










 






4. props比较机制

对于 props 的比较,进行的是“浅比较”,底层使用 Object.is 进行比较,针对于对象数据类型,只会对比两次的引用是否相等,如果不相等就会重新渲染,React 并不关心对象中的具体属性

说明:虽然两次的 list 状态都是 [1,2,3],但是因为组件 App 两次渲染生成了不同的对象引用,所以传给 MemoSon 组件的 props 视为不同,子组件还会发生重新渲染

import {useState, memo} from 'react'

const MemoSon = React.memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
})

function App() {
  // const [count, setCount] = useState(0)
  const [list, setList] = useState([1, 2, 3])
  return (
    <>
      <MemoSon list={list} />
      <button onClick={() => setList([1, 2, 3])}>
        {JSON.stringify(list)}
      </button>
    </>
  )
}

export default App

5. 自定义比较函数

不通过引用来比较,而是完全比较数组的成员是否完全一致,则可以通过自定义比较函数来实现

import {useState, memo} from 'react'

// 自定义比较函数
function arePropsEqual(oldProps, newProps) {
  console.log(oldProps, newProps)
  return (
      oldProps.list.length === newProps.list.length &&
      oldProps.list.every((oldItem, index) => {
        const newItem = newProps.list[index]
        console.log(newItem, oldItem)
        return oldItem === newItem
      })
  )
}

const MemoSon = memo(function Son() {
  console.log('子组件被重新渲染了')
  return <div>this is span</div>
}, arePropsEqual)

function App() {
  console.log('父组件重新渲染了')
  const [list, setList] = useState([1, 2, 3])
  return (
      <>
        <MemoSon list={list}/>
        <button onClick={() => setList([1, 2, 3])}>
          内容一样{JSON.stringify(list)}
        </button>
        <button onClick={() => setList([4, 5, 6])}>
          内容不一样{JSON.stringify(list)}
        </button>
      </>
  )
}

export default App

3. React.useCallback

1. 子组件被渲染

当给子组件传递一个引用类型 prop 时,即使使用了memo 依旧无法阻止子组件的渲染,其实传递 prop 时,往往传递一个回调函数更为常见

import {memo, useState} from 'react'

const MemoSon = memo(function Son() {
  console.log('Son组件渲染了')
  return <div>this is son</div>
})

function App() {
  const [, forceUpate] = useState()
  console.log('父组件重新渲染了')

  const onGetSonMessage = (message) => {
    console.log(message)
  }

  return (
      <div>
        <MemoSon onGetSonMessage={onGetSonMessage}/>
        <button onClick={() => forceUpate(Math.random())}>update</button>
      </div>
  )
}

export default App


 








 





 






2. useCallback缓存

useCallback 缓存之后的函数,可以在组件渲染时保持引用稳定,也就是返回同一个引用

import {memo, useCallback, useState} from 'react'

const MemoSon = memo(function Son() {
  console.log('Son组件渲染了')
  return <div>this is son</div>
})

function App() {
  const [, forceUpate] = useState()
  console.log('父组件重新渲染了')

  const onGetSonMessage = useCallback((message) => {
    console.log(message)
  }, [])

  return (
      <div>
        <MemoSon onGetSonMessage={onGetSonMessage}/>
        <button onClick={() => forceUpate(Math.random())}>update</button>
      </div>
  )
}

export default App


 








 





 






3. forwardRef

作用: 允许组件使用 ref 将一个 DOM 节点暴露给父组件

import {forwardRef, useRef} from 'react'

const MyInput = forwardRef(function Input(props, ref) {
  return <input {...props} type="text" ref={ref}/>
}, [])

function App() {
  const ref = useRef(null)

  const focusHandle = () => {
    console.log(ref.current.focus())
  }

  return (
      <div>
        <MyInput ref={ref}/>
        <button onClick={focusHandle}>focus</button>
      </div>
  )
}

export default App


 




 


 




 






4. useImperativeHandle

作用: 如果并不想暴露子组件中的 DOM,而是想暴露子组件内部的方法

import {forwardRef, useImperativeHandle, useRef} from 'react'

const MyInput = forwardRef(function Input(props, ref) {
  // 实现内部的聚焦逻辑
  const inputRef = useRef(null)
  const focus = () => inputRef.current.focus()

  // 暴露子组件内部的聚焦方法
  useImperativeHandle(ref, () => {
    return {
      focus,
    }
  })

  return <input {...props} ref={inputRef} type="text"/>
})

function App() {
  const ref = useRef(null)

  const focusHandle = () => ref.current.focus()

  return (
      <div>
        <MyInput ref={ref}/>
        <button onClick={focusHandle}>focus</button>
      </div>
  )
}

export default App