你觉得Hooks这一点烦吗?

开发 架构
由于Hooks总是在组件render时才会计算新状态,这为Hooks带来比较重的心智负担。相比而言,采用「细粒度更新」实现的Hooks(比如VUE的Composition API)可以实时更新状态,操作起来更符合直觉。

[[424473]]

大家好,我卡颂。

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

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

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

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

让人懵逼的Demo

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

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

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

  1. let i = 0; 
  2.  
  3. export default function App() { 
  4.   const [list, setList] = useState([]); 
  5.  
  6.   const add = () => { 
  7.     // ... 
  8.   }; 
  9.  
  10.   return ( 
  11.     <div className="App"
  12.       <button onClick={add}>Add</button> 
  13.       {list.map(val => val)} 
  14.     </div> 
  15.   ); 

显示效果:

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

  1. const add = () => { 
  2.     setList( 
  3.       list.concat( 
  4.         <button  
  5.           key={i}  
  6.           onClick={add}> 
  7.           {i++} 
  8.         </button> 
  9.       ) 
  10.     ); 
  11.   }; 

点击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方法:

  1. const add = () => { 
  2.     setList( 
  3.       list.concat( 
  4.         <button  
  5.           key={i}  
  6.           onClick={add}> 
  7.           {i++} 
  8.         </button> 
  9.       ) 
  10.     ); 
  11.   }; 

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

  • add
  • list
  • setList

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

其中list与setList来自于useState调用后的返回值:

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

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

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

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

所以是一个全新的对象。

  • 如果你想了解更多update、state计算的细节,参考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,

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

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

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

  1. // 之前 
  2. setList(list.concat(<button key={i} onClick={add}>{i++}</button>)); 
  3. // 之后 
  4. setList(list => list.concat(<button key={i} onClick={add}>{i++}</button>)); 

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

总结

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

相比而言,采用「细粒度更新」实现的Hooks(比如VUE的Composition 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

 

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2023-05-08 00:01:29

数据分析指标标签

2019-11-15 14:11:41

工业革命工业4.0信息化

2019-07-10 06:08:33

IT运维网络故障故障排除

2021-04-29 22:11:28

Python排序算法

2018-10-15 21:12:08

2021-03-25 09:42:37

CIO首席信息官IT领导

2021-10-18 21:55:08

Windows 10Windows微软

2019-04-07 16:40:46

WiFi无线路由器网络

2018-03-08 16:22:39

FlutterAndroid代码

2020-12-24 10:34:59

防火墙网络安全

2017-08-04 13:12:44

2016-01-06 09:49:59

青云/SDN

2015-12-08 16:14:04

2021-01-29 08:09:32

Service接口表现层

2019-04-16 15:05:59

ServerlessIT基础局域网

2017-04-13 11:20:56

机器学习代码

2010-05-20 15:29:43

优化IIS

2021-08-31 10:52:30

容量背包物品

2021-10-28 14:30:19

K8S Kubernetes数据持久化

2016-04-05 10:12:58

HiveSQLHadoop
点赞
收藏

51CTO技术栈公众号