likes
comments
collection
share

react hooks的短板,官方终于出手弥补

作者站长头像
站长
· 阅读数 13

如果你喜欢我的文章,请给个点赞支持一下。

我们在使用hooks的时候都会存在闭包陷阱,这是react保证依赖正确的做法。比如以下的代码。

function App() {
  const [text, setText] = useState('');

  const onClick = useCallback(() => {
    sendMessage(text);
  }, []);

  return <SendButton onClick={onClick} />;
}

我们预期是点击onClick能传递text新的值。但是由于useCallback缓存,形成了闭包所以最后你得到结果是sendMessage('')

hooks上手容易,但是如果不去思考,也很容易让开发者写出有bug的代码。有时候有些人写的hooks忘了添加依赖导致排除很久。

React 新 hooks useEvent

在官网RC的版本中引入了一个新的hooks,useEvent当前还处于RFC(Request For Comments)阶段。

他用于定义一个函数,这个函数有2个特性:

  1. 在组件多次render时保持引用一致
  2. 函数内始终能获取到最新的propsstate

上面的例子使用useEvent改造后:

function App() {
  const [text, setText] = useState('');

  const onClick = useEvent(() => {
    sendMessage(text);
  });

  return <SendButton onClick={onClick} />;
}

Chat组件多次render时,onClick始终指向同一个引用。

并且onClick触发时始终能获取到text的最新值。

之所以叫useEvent,是因为React团队认为这个Hook的主要应用场景是:封装事件处理函数。这将会让我们更好的做DDD领域设计驱动

实现

useEvent的实现其实也不是很难的。

function useEvent(handler) {
  const handlerRef = useRef(null);

  // 视图渲染完成后更新`handlerRef.current`指向
  useLayoutEffect(() => {
    handlerRef.current = handler;
  });

  // 用useCallback包裹,使得render时返回的函数引用一致
  return useCallback((...args) => {
    const fn = handlerRef.current;
    return fn(...args);
  }, []);
}

官网还处于RC阶段,目前的项目怎么处理呢?

从上面的例子来看实现一个useEvent并不难,也有很多开源库实现了类似的功能,比如start最多的hooksahooks就有这么一个hooksuseMemoizedFn

这不过 useMemoizedFn定位于缓存各种函数

function useMemoizedFn<T extends noop>(fn: T) {
  const fnRef = useRef<T>(fn);

  // 更新fnRef.current
  fnRef.current = useMemo(() => fn, [fn]);

  // ...省略代码
}

useEvent定位于处理事件回调函数这一单一场景。

 useLayoutEffect(() => {
    handlerRef.current = handler;
  });

官方这么做的是为了更加稳定,能否获取到最新的stateprops取决于handlerRef.current更新的时机。在上面模拟实现中,useEvent更新handlerRef.current的逻辑放在useLayoutEffect回调中进行。而事件回调触发的时机显然在视图完成渲染之后,所以能够稳定获取到最新的stateprops

useMemoizedFnfnRef.current的更新时机是useMemoizedFn执行时(即组件render时

在React18启用并发更新后,组件render的次数与时间都不确定。有后端并发编程的同学都知道并发是无顺序的。所以导致useMemoizedFn的更新时机也是不确定。从而增加了并发更新下的潜在风险。 可以说useEvent通过限制handlerRef.current更新时机,进而限制应用场景,最终达到稳定的目的。