You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
useEvent 可以用来代替 useCallback,以前这样写,在 text 变化的时候,函数地址会变化。
functionChat(){const[text,setText]=useState('');// 🟡 A different function whenever `text` changesconstonClick=useCallback(()=>{sendMessage(text);},[text]);return<SendButtononClick={onClick}/>;}
functionChat(){const[text,setText]=useState('');// ✅ Always the same function (even if `text` changes)constonClick=useEvent(()=>{sendMessage(text);});return<SendButtononClick={onClick}/>;}
useEvent 是怎么实现的
useEvent 的实现原理比较简单,但现在看到的社区上的介绍文章几乎都有问题。
// (!) Approximate behaviorfunctionuseEvent(handler){consthandlerRef=useRef(null);// In a real implementation, this would run before layout effectsuseLayoutEffect(()=>{handlerRef.current=handler;});returnuseCallback((...args)=>{// In a real implementation, this would throw if called during renderconstfn=handlerRef.current;returnfn(...args);},[]);}
上面的代码是官方提供的一个示例代码,需要重点注意这句注释 In a real implementation, this would run before layout effects,翻译过来就是 “在真实的实现中,这里用的 Hooks 执行时机在 useLayoutEffect之前”。
之前写了一篇文章《React Hooks 使用误区,驳官方文档》,文中抛出了两个观点:
这两个观点引起了剧烈的讨论,当然大多数人还是持反对意见的,甚至质疑我不会用 Hooks,(⊙o⊙)… 我想说我写的 Hooks 比你吃的盐都多(开玩笑 😋 ~)
然后呢,知乎上来了个提问《如何看待《React Hooks 使用误区,驳官方文档》?》,大家依旧是讨论激烈,甚至 #黄玄 大佬也亲自来回答了。
很多同学极力反对我的观点,刚开始我还想一争高下,后来实在没精力一个一个对线。
这不,React 官方来帮我助阵了?React 官方为啥出 useEvent?就是发现以前要求的依赖写法,实在有太大问题,不加一个新的 API,官方示例都没法写了 🙂。
以前一直觉得 React Hooks 教程,包括 Dan 写的 useEffect 教程,都只是写了基础场景,对于稍微复杂点的场景,都避而不谈。因为这些复杂场景,在之前的规则下,确实是没法玩。
什么是 useEvent
关于 useEvent 是什么,官方 RFC 文档有非常详细的解释,并且目前社区上也有非常多的文章介绍(其实很多介绍都是有问题的)。接下来用一个官方文档上的一个例子,来认识一下 useEvent。
需求很简单,我们希望 url 变化的时候,上报下当前
url
和username
。如上代码,会有 warning,告诉我们
currentUser.name
要放到 deps 中。修正后代码是这样但这样明显满足不了我们的业务需求,因为
currentUser.name
变化后,也触发了上报请求。以前的解决方案可能有两个:
eslint-plugin-react-hooks
卸载掉currentUser.name
两个方案都有缺点:
基于 useEvent 改造起来就很简单了
useEvent 会将一个函数「持久化」,同时可以保证函数内部的变量引用永远是最新的。如果你用过 ahooks 的
useMemoizedFn
,实现的效果是几乎一致的。再强调下 useEvent 的两个特性:
useEvent 可以用来代替 useCallback,以前这样写,在
text
变化的时候,函数地址会变化。通过 useEvent 代替 useCallback 后,不用写 deps 函数了,并且函数地址永远是固定的,
text
也永远是最新的。useEvent 是怎么实现的
useEvent 的实现原理比较简单,但现在看到的社区上的介绍文章几乎都有问题。
上面的代码是官方提供的一个示例代码,需要重点注意这句注释
In a real implementation, this would run before layout effects
,翻译过来就是 “在真实的实现中,这里用的 Hooks 执行时机在useLayoutEffect
之前”。这里一定是不能用
useLayoutEffect
来更新ref
的,因为子组件的useLayoutEffect
比父组件的执行更早,如果这样用的话,子组件的useLayoutEffect
中访问到的ref
一定是旧的。所以官方为了实现
useEvent
,一定是要加一个在useLayoutEffect
之前执行的 Hooks 的,并且这个 Hooks 应该不会开放给普通用户使用的。另外 React 要求不要在 render 中直接调用
useEvent
返回的函数,原理也是一样的,在 render 中访问的函数一定是旧的,因为useLayoutEffect
还没执行呢。useMemoizedFn 和 useEvent 的差异
在 React 18 之前,社区上有很多类似
useEvent
的实现,比如 ahooks 的 useMemoizedFn,类似下面这样之前很多同学问,为啥不用
useLayoutEffect
,是不是有问题?现在应该明白了吧?我们需要一个比useLayoutEffect
执行更早的 Hooks,很遗憾的是之前更没有,所以只能放到 render 中。为什么之前官方没有提供类似的 Hooks?useMemoizedFn 有问题吗?
之前 React Issue #16956 上对类似的封装做了很多讨论,官方的态度一直是 “在 concurrent 下可能会存在问题” ,也就是官方也吃不准未来会不会出问题。随着 React 18 发布,concurrent 模式稳定之后,官方发现,这种写法不会有问题,索性就自己提供了一个。
在 React 18 之前,因为没有 concurrent,所以 useMemoizedFn 不会有任何问题。在 React 18 之后,我目前也没看到有什么问题。不过为了稳妥起见,后面 ahooks 的 useMemoizedFn 会做一次升级,向官方的 useEvent 看齐。
最后用知乎上一个同学的评论结尾“面多了加水,水多了加面”。
The text was updated successfully, but these errors were encountered: