前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你觉得Hooks这一点烦吗?

你觉得Hooks这一点烦吗?

作者头像
公众号@魔术师卡颂
发布2021-10-12 14:48:07
5250
发布2021-10-12 14:48:07
举报
文章被收录于专栏:魔术师卡颂魔术师卡颂

大家好,我卡颂。

昨天一个小伙伴发了一个Demo给我,让我解释下原因。

我一看,好家伙,小小一个Demo,知识点囊括了:

  • Hooks的闭包问题
  • state是如何组装的

相信看完这个Demo,对函数组件会有更深的认识。

让人懵逼的Demo

Demo包含一个按钮、一个列表。

代码语言:javascript
复制
 <div className="App">
    <button onClick={add}>Add</button>
    {list.map(val => val)}
  </div>

点击按钮,调用add方法,向列表中插入一项:

代码语言:javascript
复制

let i = 0;

export default function App() {
  const [list, setList] = useState([]);

  const add = () => {
    // ...
  };

  return (
    <div className="App">
      <button onClick={add}>Add</button>
      {list.map(val => val)}
    </div>
  );
}

显示效果:

烧脑的地方在于,调用add方法插入的是一个「点击后会调用 add 方法的按钮」

代码语言:javascript
复制
const add = () => {
    setList(
      list.concat(
        <button 
          key={i} 
          onClick={add}>
          {i++}
        </button>
      )
    );
  };

点击Add按钮7下后的显示效果:

那么问题来了,点击带数字按钮(会调用和点击Add按钮一样的add方法)后会有什么效果呢?

state的组装和闭包问题

如果你认为会插入一个新按钮:

那就错了。

正确答案是:点击对应按钮后list长度变为「按钮对应数字 + 1」,且最后一项的数字为「点击前最大数字 + 1」

比如,点击前最大数字为6

如果点击 0,list长度变为0 + 1 = 1,且最后一项为6 + 1 = 7

如果点击 2,list长度变为2 + 1 = 3,且最后一项为6 + 1 = 7

这是两个因素共同作用的结果:

  • Hooks的闭包问题
  • state是如何组装的

原因分析

再来看看add方法:

代码语言:javascript
复制
const add = () => {
    setList(
      list.concat(
        <button 
          key={i} 
          onClick={add}>
          {i++}
        </button>
      )
    );
  };

button点击后调用add,所以会基于add所属上下文(App函数)形成闭包,闭包中包括:

  • add
  • list
  • setList

i属于module级作用域,不在该闭包内

其中listsetList来自于useState调用后的返回值:

代码语言:javascript
复制
const [list, setList] = useState([]);

一种常见的认知误区是:多次调用useState返回的list是同一个引用。

事实上,每次调用useState返回的list都是基于如下公式计算得出的:

基准state + update1 + update2 + ... = 当前state

所以是一个全新的对象。

如果你想了解更多updatestate计算的细节,参考React技术揭秘[1]

首屏渲染时:

  1. App组件首次render
  2. 创建list = []
  3. <button onClick={add}>Add</button>依赖add,形成闭包,闭包中的list = []

接下来,点击Add按钮

  1. 调用add方法,该方法来自于首屏渲染创建的闭包
  2. add方法中依赖的list来自于同一个闭包,所以list = []
  3. <button key={i} onClick={add}>{i++}</button>依赖add,形成闭包,闭包中的list = []

所以,对于按钮0

任何时候点击他实际上执行的都是:

代码语言:javascript
复制
setList(
  [].concat(
    <button key={i} onClick={add}>{i++}</button>
  )
);

那么如何修复这个问题呢,也很简单,将setList的参数改为函数形式:

代码语言:javascript
复制
// 之前
setList(list.concat(<button key={i} onClick={add}>{i++}</button>));
// 之后
setList(list => list.concat(<button key={i} onClick={add}>{i++}</button>));

函数参数中的list来自于Hooks中保存的list,而不是闭包中的list

总结

由于Hooks总是在组件render时才会计算新状态,这为Hooks带来比较重的心智负担。

相比而言,采用「细粒度更新」实现的Hooks(比如VUEComposition API)可以实时更新状态,操作起来更符合直觉。

在使用Hooks过程中,你有没有遇到类似的头疼问题呢?

参考资料

[1]

React技术揭秘: https://react.iamkasong.com/state/mental.html#%E5%90%8C%E6%AD%A5%E6%9B%B4%E6%96%B0%E7%9A%84react

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-09-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 魔术师卡颂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 让人懵逼的Demo
  • state的组装和闭包问题
  • 原因分析
  • 总结
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档