使用Hooks容易犯错的点
昨天一个小伙伴发了一个Demo
给我,让我解释下原因。
我一看,好家伙,小小一个Demo
,知识点囊括了:
Hooks
的闭包问题state
是如何组装的
Demo
,对函数组件
会有更深的认识。文章图片
让人懵逼的Demo
Demo
包含一个按钮、一个列表。
{list.map(val => val)}
点击按钮,调用
add
方法,向列表中插入一项:let i = 0;
export default function App() {
const [list, setList] = useState([]);
const add = () => {
// ...
};
return (
{list.map(val => val)});
}
显示效果:
文章图片
烧脑的地方在于,调用
add
方法插入的是一个点击后会调用 add 方法的按钮:const add = () => {
setList(
list.concat(
)
);
};
点击
Add
按钮7下后的显示效果:文章图片
那么问题来了,点击带数字按钮(会调用和点击
Add按钮
一样的add
方法)后会有什么效果呢?文章图片
state的组装和闭包问题 【使用Hooks容易犯错的点】如果你认为会插入一个新按钮:
文章图片
那就错了。
文章图片
正确答案是:点击对应按钮后
list
长度变为按钮对应数字 + 1,且最后一项的数字为点击前最大数字 + 1。比如,点击前最大数字为6
文章图片
如果点击 0,
list
长度变为0 + 1 = 1
,且最后一项为6 + 1 = 7
:文章图片
如果点击 2,
list
长度变为2 + 1 = 3
,且最后一项为6 + 1 = 7
:文章图片
这是两个因素共同作用的结果:
Hoo
`ks`的闭包问题state
是如何组装的
add
方法:const add = () => {
setList(
list.concat(
)
);
};
button
点击后调用add
,所以会基于add
所属上下文(App
函数)形成闭包,闭包中包括:- add
- list
- setList
文章图片
i
属于module
级作用域,不在该闭包内其中
list
与setList
来自于useState
调用后的返回值:const [list, setList] = useState([]);
一种常见的认知误区是:多次调用
useState
返回的list
是同一个引用。事实上,每次调用
useState
返回的list
都是基于如下公式计算得出的:基准state + update1 + update2 + ... = 当前state
所以是一个全新的对象。
当
首屏渲染
时:App
组件首次render
- 创建
list = []
依赖
add
,形成闭包,闭包中的list = []
Add按钮
:- 调用
add
方法,该方法来自于首屏渲染创建的闭包 add
方法中依赖的list
来自于同一个闭包,所以list = []
依赖
add
,形成闭包,闭包中的list = []
按钮0
,文章图片
任何时候点击他实际上执行的都是:
setList(
[].concat(
)
);
那么如何修复这个问题呢,也很简单,将
setList
的参数改为函数形式:// 之前
setList(list.concat());
// 之后
setList(list => list.concat());
函数参数中的
list
来自于Hooks
中保存的list
,而不是闭包中的list
。总结 由于
Hooks
总是在组件render
时才会计算新状态,这为Hooks
带来比较重的心智负担。相比而言,采用细粒度更新实现的
Hooks
(比如VUE
的Composition API
)可以实时更新状态,操作起来更符合直觉。推荐阅读
- 宽容谁
- Docker应用:容器间通信与Mariadb数据库主从复制
- 由浅入深理解AOP
- 【译】20个更有效地使用谷歌搜索的技巧
- 吃了早餐,反而容易饿(为什么?)
- mybatisplus如何在xml的连表查询中使用queryWrapper
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询
- iOS中的Block
- Linux下面如何查看tomcat已经使用多少线程