Skip to main content

自定义Hook

驱动条件

自定义 hooks 驱动本质上就是函数组件的执行

自定义 hooks 驱动条件:

  • props 改变带来的函数组件执行。
  • useState | useReducer 改变 state 引起函数组件的更新。

顺序原则

那么自定义 hooks 也要遵循 hooks 的规则,不能放在条件语句中,而且要保持执行顺序的一致性。

条件限定

一段不好的代码:

function useXXX() {
const value = React.useContext(defaultContext);
/* .....用上下文中 value 一段初始化逻辑 */
const newValue = initValueFunction(
value
); /* 初始化 value 得到新的 newValue */
/* ...... */
return newValue;
}

不好在哪里 👇:

每一次函数组件更新,就会执行此自定义 hooks ,那么就会重复执行初始化逻辑,重复执行 initValueFunction ,每一次都会得到一个最新的 newValue 。 如果 newValue 作为 useMemo 和 useEffect 的 deps ,或者作为子组件的 props ,那么子组件的浅比较 props 将失去作用,那么会带来一串麻烦。

应该怎么定义?

function useXXX() {
const newValue = React.useRef(null); /* 创建一个 value 保存状态。 */
const value = React.useContext(defaultContext);
if (!newValue.current) {
/* 如果 newValue 不存在 */
newValue.current = initValueFunction(value);
}
return newValue.current;
}

考虑可变性

可变性:就是考虑一些状态值发生变化,是否有依赖于当前值变化的执行逻辑或执行副作用。

比如上面的例子中,如果 defaultContext 中的 value 是可变的,那么如果还像上述用 useRef 这么写,就会造成 context 变化,得不到最新的 value 值的情况发生。

对于可变性如何处理:

  • 对于依赖于可变性状态的执行逻辑,可以用 useMemo 来处理。
  • 对于可变性状态的执行副作用,可以用 useEffect 来处理。
  • 对于依赖可变性状态的函数或者属性,可以用 useCallback 来处理。
function useXXX() {
const value = React.useContext(defaultContext);
const newValue = React.useMemo(() => initValueFunction(value), [value]);
return newValue;
}

闭包效应

函数组件更新就是函数本身执行,一次更新所有含有状态的 hooks ( useState 和 useReducer )产生的状态 state 是重新声明的。但是如果像 useEffect , useMemo ,useCallback 等,它们内部如果引用了 state 或 props 的值,而且这些状态最后保存在了函数组件对应的 fiber 上,那么此次函数组件执行完毕后,这些状态就不会被垃圾回收机制回收释放。这样造成的影响是,上述 hooks 如果没有把内部使用的 state 或 props 作为依赖项,那么内部就一直无法使用最新的 props 或者 state 。

思考:如何分清楚依赖关系呢?

  • 第一步:找到 hooks 内部可能发生变化的状态 , 这个状态可以是 state 或者 props。
  • 第二步:分析 useMemo 或者 useCallback 内部是否使用上述状态,或者是否关联使用 useMemo 或者 useCallback 派生出来的状态( 比如上述的 value ,就是 useMemo 派生的状态 ) ,如果有使用,那么加入到 deps 。
  • 第三步:分析 useEffect ,useLayoutEffect ,useImperativeHandle 内部是否使用上述两个步骤产生的值,而且还要这些值做一些副作用,如果有,那么加入到 deps 。