这两个观点引起了剧烈的讨论,当然大多数人还是持反对意见的,甚至质疑我不会用 Hooks,(⊙o⊙)… 我想说我写的 Hooks 比你吃的盐都多(开玩笑 ~)。
公司主营业务:成都网站建设、成都网站制作、移动网站开发等业务。帮助企业客户真正实现互联网宣传,提高企业的竞争能力。创新互联建站是一支青春激扬、勤奋敬业、活力青春激扬、勤奋敬业、活力澎湃、和谐高效的团队。公司秉承以“开放、自由、严谨、自律”为核心的企业文化,感谢他们对我们的高要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。创新互联建站推出曲周免费做网站回馈大家。
然后呢,知乎上来了个提问《如何看待《React Hooks 使用误区,驳官方文档》?[2]》,大家依旧是讨论激烈,甚至 #黄玄 大佬也亲自来回答了。
很多同学极力反对我的观点,刚开始我还想一争高下,后来实在没精力一个一个对线。
这不,React 官方来帮我助阵了?React 官方为啥出 useEvent?就是发现以前要求的依赖写法,实在有太大问题,不加一个新的 API,官方示例都没法写了 。
以前一直觉得 React Hooks 教程,包括 Dan 写的 useEffect 教程[3],都只是写了基础场景,对于稍微复杂点的场景,都避而不谈。因为这些复杂场景,在之前的规则下,确实是没法玩。
关于 useEvent 是什么,官方 RFC[4] 文档有非常详细的解释,并且目前社区上也有非常多的文章介绍(其实很多介绍都是有问题的)。接下来用一个官方文档上的一个例子,来认识一下 useEvent。需求很简单,我们希望 url 变化的时候,上报下当前 url和 username。
function Page({ route, currentUser }) {
useEffect(() => {
logAnalytics('visit_page', route.url, currentUser.name);
}, [route.url]);
// ...
}
如上代码,会有 warning,告诉我们 currentUser.name要放到 deps 中。修正后代码是这样:
function Page({ route, currentUser }) {
useEffect(() => {
logAnalytics('visit_page', route.url, currentUser.name);
}, [route.url, currentUser.name]);
// ...
}
但这样明显满足不了我们的业务需求,因为 currentUser.name变化后,也触发了上报请求。
很多杠精就问,为啥你的需求要这样设计?为啥 currentUser.name变化后不要上报?你的需求不合理吧?这个你去问 dan 吧!
以前的解决方案可能有两个:
function Page({ route, currentUser }) {
const ref = useRef(currentUser.name);
ref.current = currentUser.name;
useEffect(() => {
logAnalytics('visit_page', route.url, ref.current);
}, [route.url]);
// ...
}
两个方案都有缺点:
基于 useEvent 改造起来就很简单了。
function Page({ route, currentUser }) {
// Stable identity
const onVisit = useEvent(visitedUrl => {
logAnalytics('visit_page', visitedUrl, currentUser.name);
});
useEffect(() => {
onVisit(route.url);
}, [route.url]); // Re-runs only on route change
// ...
}
useEvent 会将一个函数「持久化」,同时可以保证函数内部的变量引用永远是最新的。如果你用过 ahooks 的 useMemoizedFn,实现的效果是几乎一致的。再强调下 useEvent 的两个特性:
useEvent 可以用来代替 useCallback,以前这样写,在 text 变化的时候,函数地址会变化。
function Chat() {
const [text, setText] = useState('');
// A different function whenever `text` changes
const onClick = useCallback(() => {
sendMessage(text);
}, [text]);
return;
}
通过 useEvent 代替 useCallback 后,不用写 deps 函数了,并且函数地址永远是固定的,text也永远是最新的。
function Chat() {
const [text, setText] = useState('');
// Always the same function (even if `text` changes)
const onClick = useEvent(() => {
sendMessage(text);
});
return;
}
useEvent 的实现原理比较简单,但现在看到的社区上的介绍文章几乎都有问题。
// (!) Approximate behavior
function useEvent(handler) {
const handlerRef = useRef(null);
// In a real implementation, this would run before layout effects
useLayoutEffect(() => {
handlerRef.current = handler;
});
return useCallback((...args) => {
// In a real implementation, this would throw if called during render
const fn = handlerRef.current;
return fn(...args);
}, []);
}
上面的代码是官方提供的一个示例代码,需要重点注意这句注释 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还没执行呢。
在 React 18 之前,社区上有很多类似 useEvent 的实现,比如 ahooks[5] 的 useMemoizedFn,类似下面这样:
function useMemoizedFn(fn) {
const fnRef = useRef(fn);
fnRef.current = useMemo(() => fn, [fn]);
return useCallback((...args) => {
return fnRef.current.apply(args);
}, []);
}
之前很多同学问,为啥不用 useLayoutEffect,是不是有问题?现在应该明白了吧?我们需要一个比useLayoutEffect执行更早的 Hooks,很遗憾的是之前更没有,所以只能放到 render 中。
为什么之前官方没有提供类似的 Hooks?useMemoizedFn 有问题吗?之前 React Issue #16956[6] 上对类似的封装做了很多讨论,官方的态度一直是 “在 concurrent 下可能会存在问题” ,也就是官方也吃不准未来会不会出问题。随着 React 18 发布,concurrent 模式稳定之后,官方发现,这种写法不会有问题,索性就自己提供了一个。
在 React 18 之前,因为没有 concurrent,所以 useMemoizedFn 不会有任何问题。在 React 18 之后,我目前也没看到有什么问题。不过为了稳妥起见,后面 ahooks 的 useMemoizedFn 会做一次升级,向官方的 useEvent 看齐。
最后用知乎上一个同学的评论结尾“面多了加水,水多了加面”。
[1]React Hooks 使用误区,驳官方文档: https://zhuanlan.zhihu.com/p/450513902
[2]如何看待《React Hooks 使用误区,驳官方文档》?: https://www.zhihu.com/question/508780830
[3]useEffect 教程: https://overreacted.io/a-complete-guide-to-useeffect/
[4]RFC: https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md
[5]ahooks: https://github.com/alibaba/hooks[6]#16956: https://github.com/facebook/react/issues/16956
网站名称:ReactuseEvent:砖家说的没问题
文章分享:http://www.shufengxianlan.com/qtweb/news33/15433.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联