react hooks基础功能细节总结

261 阅读6分钟

函数组件与hook组件

函数组件是无状态组件,而hook组件是一个包含函数组件的有状态组件

useState

语法

useState(initValue:any):[any,(v:any)=>void];

useState函数接受任何类型数据,返回一个长度为2的一维数组,数组的第一个值是最新的state值,数组第二个值是一个设置state的函数。

可以使用多次useState,定义多个state。你也不必使用多个 state 变量,State 变量可以很好地存储对象和数组。

但是我们要注意:不像 class 中的 this.setState合并它state,useState中更新 state 变量总是替换它。

推荐把 state 切分成多个 state 变量,将一起变化的可以放在一起,每个变量包含的不同值会在同时发生变化

不可在if判断中使用useState,这样无法保证hooks链表的排列顺序,而出现意想不到的bug。

setState传递函数

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。 你的更新函数返回值与当前 state 完全相同,则随后的重渲染会被完全跳过。 可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果:

setState(prevState => {
  // 也可以使用 Object.assign
  return {...prevState, ...updatedValues};
});

useReducer 是另一种可选方案,下面介绍它

useEffect

语法

useEffect(callback:()=>void|Function,dependent:Array):void;

useEffect Hook 看做 componentDidMountcomponentDidUpdate 和 componentWillUnmount 这三个函数的组合。

传递给 useEffect 的函数在每次渲染中都会有所不同,这样可以在 effect 中获取最新的 count 的值。

使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快,大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同

需要清除的 effect`

如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它。

React 会在组件卸载的时候执行清除操作,也就是说React会在执行当前 effect 之前对上一个 effect 进行清除(如果返回了清除函数)

使用多个 Effect 实现关注点分离

就像你可以使用多个 state 的 Hook 一样,你也可以使用多个 effect

Hook 允许我们按照代码的用途分离他们

为什么每次更新的时候都要运行 Effect

因为要执行清理操作,它会在调用一个新的 effect 之前对前一个 effect 进行清理

通过跳过 Effect 进行性能优化

你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可。

如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。

当第二个参数时空数组时: 依耐性项数组为空时,useEffect里的回调函数只在组件初始化和组件卸载的时候调用,传空数组表示不依赖任何的props和state。

注意点

通常你会想要在 effect 内部 去声明它所需要的函数

function Example({ someProp }) {
  useEffect(() => {
    function doSomething() {
      console.log(someProp);    }

    doSomething();
  }, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)}

如果你指定了一个 依赖列表 作为 useEffectuseMemouseCallback 或 useImperativeHandle 的最后一个参数,它必须包含回调中的所有值,并参与 React 数据流。这就包括 props、state,以及任何由它们衍生而来的东西

effect 的依赖频繁变化时

我们可以使用 setState 的函数式更新形式,解决hooks的闭包陷阱。

或者你也可以使用一个你可以 使用一个 ref 来保存一个可变的变量,但是仅当你实在找不到更好办法的时候才这么做

hook规范

  1. 不要在循环,条件或嵌套函数中调用 Hook
  2. 只在 React 函数中调用 Hook

useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。

当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

调用了 useContext 的hooks组件总会在 context 值变化时重新渲染。

useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>

useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context,也就是说必须要<MyContext.Provider>useContext(MyContext)组合使用,别期望只使用useContext(MyContext)

useReducer

基础概念:

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

const [state, dispatch] = useReducer(reducer, initialArg, init);

适用场景:

  1. state 逻辑较复杂且包含多个子值;
  2. 下一个 state 依赖于之前的 state;

优势:

可以向子组件传递 dispatch 而不是回调函数;可以通过 context 用useReducer 往下传一个 dispatch

注意点:

  1. React 会确保 dispatch 函数的标识是稳定的,并且不会在组件重新渲染时改变。
  2. 指定初始 state:将初始 state 作为第二个参数传入 useReducer
  3. 惰性初始化:将 init 函数作为 useReducer 的第三个参数传入;好处:为将来对重置 state 的 action 做处理提供了便利。
  4. 如果Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。执行了高开销的计算,则可以使用 useMemo 来进行优化。

useCallback

目的:

缓存回调函数,来保证每次不重新创建函数,来避免依赖这个函数的子组件不重新渲染。 useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

useMemo

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

useRef()Hook 不仅可以用于 DOM refs。「ref」 对象是一个 current 属性可变且可以容纳任意值的通用容器,类似于一个 class 的实例属性;可以对其赋值更改。

通常你应该在事件处理器和 effects 中修改 refs。

检测dom

当 ref 对象内容发生变化时,useRef 并不会通知你,要实现ref监听对象改变,可以使用回调 ref

function MeasureExample() {
  const [height, setHeight] = useState(0);

  const measuredRef = useCallback(node => {    
      if (node !== null) {      
          setHeight(node.getBoundingClientRect().height);    
      }  
  }, []);
  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>      
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

useImperativeHandle

让内部使用ref的子组件暴露给父组件。useImperativeHandle 应当与 forwardRef 一起使用。

useLayoutEffect

在所有的 DOM 变更之后同步调用 effect。

useDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

useDebugValue 接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。