乐趣区

关于前端:你觉得Hooks这一点烦吗

大家好,我卡颂。

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

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

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

置信看懂这个 Demo,对 函数组件 会有更深的意识。

让人懵逼的 Demo

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

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

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


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 办法的按钮

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 办法:

const add = () => {
    setList(
      list.concat(
        <button 
          key={i} 
          onClick={add}>
          {i++}
        </button>
      )
    );
  };

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

  • add
  • list
  • setList

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

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

const [list, setList] = useState([]);

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

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

基准 state + update1 + update2 + … = 以后 state

所以是一个全新的对象。

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

首屏渲染 时:

  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

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

setList([].concat(<button key={i} onClick={add}>{i++}</button>
  )
);

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

// 之前
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 过程中,你有没有遇到相似的头疼问题呢?

欢送退出人类高质量前端框架钻研群,带飞

退出移动版