资讯专栏INFORMATION COLUMN

React官方团队实例原生Hook闭包陷阱

3403771864 / 254人阅读

  陷进到处都是啊!本篇文章就说说Hooks使用时存在所谓的闭包陷阱,看看下面代码:

  function Chat() {
  const [text, setText] = useState('');
  const onClick = useCallback(() => {
  sendMessage(text);
  }, []);
  return <SendButton onClick={onClick} />;
  }

  想要实现的是点击后sendMessage能传递text的最新值。

  可却无法实现,就是由于回调函数被useCallback缓存,形成闭包,所以点击的效果始终是sendMessage('')。

  这就是闭包陷阱

  以上代码的一种解决方式是为useCallback增加依赖项

  const onClick = useCallback(() =&gt; {
  sendMessage(text);
  }, [

text]);

  上面操作之后,每当依赖项(text)变化,useCallback会返回一个全新的onClick引用,这就失去了useCallback缓存函数引用的作用。

  闭包陷阱的出现,加大了Hooks的上手门槛,这样的bug在代码中很容易发现。

  看看React官方团队解决这个问题。

  useEvent

  解决方式是引入一个新的原生Hook——useEvent。

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

  在组件多次render时保持引用一致

  函数内始终能获取到最新的props与state

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

  function Chat() {
  const [text, setText] = useState('');
  const onClick = useEvent(() => {
  sendMessage(text);
  });
  return <SendButton onClick={onClick} />;
  }

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

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

  之所以叫useEvent,是因为React团队认为这个Hook的主要应用场景是:封装事件处理函数

  useEvent的实现

  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);
  }, []);
  }

  整体包括两部分:

  返回一个没有依赖项的useCallback,使得每次render时函数的引用一致

  useCallback((...args) => {
  const fn = handlerRef.current;
  return fn(...args);
  }, []);

  在合适的时机更新handlerRef.current,使得实际执行的函数始终是最新的引用

  与开源Hooks的差异

  要知道现在很多开源Hooks库已经实现类似功能(比如ahooks中的useMemoizedFn)

  useEvent与这些开源实现的差异主要体现在:

  useEvent定位于处理事件回调函数这一单一场景,而useMemoizedFn定位于缓存各种函数

  那么问题来了,既然功能类似,那useEvent为什么要限制自己的使用场景呢?

  答案是:为了更稳定。

  useEvent能否获取到最新的state与props取决于handlerRef.current更新的时机。

  在上面模拟实现中,useEvent更新handlerRef.current的逻辑放在useLayoutEffect回调中进行。

  这就保证了handlerRef.current始终在视图完成渲染后再更新:

  useLayoutEffect(() =&gt; {
  handlerRef.current = handler;
  });

  而事件回调触发的时机显然在视图完成渲染之后,所以能够稳定获取到最新的state与props。

  注:源码内的实际更新时机会更早些,但不影响这里的结论

  再来看看ahooks中的useMemoizedFn,fnRef.current的更新时机是useMemoizedFn执行时(即组件render时):

 

 function useMemoizedFn<T extends noop>(fn: T) {
  const fnRef = useRef<T>(fn);
  // 更新fnRef.current
  fnRef.current = useMemo(() => fn, [fn]);
  // ...省略代码
  }

  当React18启用并发更新后,组件render的次数、时机并不确定。

  所以useMemoizedFn中fnRef.current的更新时机也是不确定的。

  这就增加了在并发更新下使用时潜在的风险。

  可以说,useEvent通过限制handlerRef.current更新时机,进而限制应用场景,最终达到稳定的目的。

  总结

  useEvent当前还处于RFC(Request For Comments)阶段。

  很多热心的开发者对这个Hook的命名提出了建议,比如:useStableCallback:

1.jpg

  又比如:useLatestClosure:

2.jpg

  上面其实就是要扩大了useEvent的应用场景。

  但扩大后应用场景意味着增加开发者使用时出错的风险,这是不可行的。希望大家多多学习。


文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/128258.html

相关文章

  • 浅谈reacthooks闭包陷阱

      本文不会过多讲解基础知识,更多说的是在使用useRef如何能摆脱 这个 闭包陷阱 ?  react hooks 的闭包陷阱 基本每个开发员都有遇见,这是很令人抓狂的。  (以下react示范demo,均为react 16.8.3 版本)  列一个具体的场景:  functionApp(){   const[count,setCount]=useState(1);   useEffect(()=...

    3403771864 评论0 收藏0
  • 详解ahooks解决React闭包问题方法

      想必大家都能看得懂的源码 ahooks 整体架构篇,且可以使用插件化机制优雅的封装你的请求hook,现在我们就探讨下ahooks 是怎么解决 React 的闭包问题的?。  React 的闭包问题  先来看一个例子:  importReact,{useState,useEffect}from"react";   exportdefault()=>{   const[c...

    3403771864 评论0 收藏0
  • 精读《Function Component 入门》

    摘要:比如就是一种,它可以用来管理状态返回的结果是数组,数组的第一项是值,第二项是赋值函数,函数的第一个参数就是默认值,也支持回调函数。而之所以输出还是正确的,原因是的回调函数中,值永远指向最新的值,因此没有逻辑漏洞。 1. 引言 如果你在使用 React 16,可以尝试 Function Component 风格,享受更大的灵活性。但在尝试之前,最好先阅读本文,对 Function Com...

    Scholer 评论0 收藏0
  • React Hooks入门: 基础

    摘要:当组件安装和更新时,回调函数都会被调用。好在为我们提供了第二个参数,如果第二个参数传入一个数组,仅当重新渲染时数组中的值发生改变时,中的回调函数才会执行。 前言   首先欢迎大家关注我的Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励,希望大家多多关注呀!React 16.8中新增了Hooks特性,并且在React官方文档中新增...

    mrli2016 评论0 收藏0
  • 前端面试题收集,持续更新中

    摘要:对于所访问的每个元素,函数应该将该元素传递给所提供的回调函数。 HTML 在线阅读Github地址 题目列表 HTML HTML和XHTML的区别 Html的语义化 Doctype的文档类型 cookie、sessionSttorage、localStory区别 HTML全局属性(global attribute)有哪些? 常见的浏览器内核有哪些? 介绍一下你对浏览器内核的理解?...

    kgbook 评论0 收藏0

发表评论

0条评论

3403771864

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<