偶然间发现一个hooks的bug,给window或者body绑定事件,事件回调里拿不到最新的状态(只能拿到绑定时的状态值),但是给dom元素添加的绑定事件是可以的。简化后的代码如下:
import React, { useState, useEffect } from 'react';
const addLabel: React.FC = () => {
const [dataSource, setDataSource] = useState<any>([]);
useEffect(() => {
window.onclick = () => {
setDataSource([...dataSource, {
labelIdentify: Math.random(),
labelName: 0,
}])
}
}, []);
return (<div>
{
dataSource?.map((item: any) => (<p>{item?.labelIdentify}</p>))
}
</div>);
};
export default addLabel;
calss写法不存在这个问题:
import React from 'react';
class addLabel extends React.Component {
constructor(props: any) {
super(props)
this.state = {
dataSource: []
}
}
componentDidMount(): void {
window.onclick = () => {
let { dataSource } = this.state as any
this.setState({
dataSource: [...dataSource, {
labelIdentify: Math.random()
}]
})
}
}
render(): React.ReactNode {
let { dataSource } = this.state as any
return <div>
{
dataSource?.map((item: any) => (<p>{item?.labelIdentify}</p>))
}
</div>
}
}
export default addLabel;
再看给dom元素绑定事件,效果是正常的。观察给dom元素和window绑定事件的区别,给window绑定事件是在组件加载完成时候完成的,所以只触发一次,而给dom元素绑定事件是在render里边绑定的,每次组件渲染(状态更新)都会执行,也会重新绑定事件,所以给dom元素绑定的事件回调里能拿到最新的状态值(状态更新->绑定事件->事件触发拿到最新的状态值)。找到问题,解决就好解决了。
第一种思路:只要让给window绑定的操作在状态值更新后再绑一次就好了。
方法一:把绑定事件写在render函数里(这里只说实现,不考虑性能)
import React, { useState, useEffect } from 'react';
const addLabel: React.FC = () => {
const [dataSource, setDataSource] = useState<any>([]);
window.onclick = () => { setDataSource([...dataSource, {
labelIdentify: Math.random(),
labelName: 0,
}])
}
return (<div>
{
dataSource?.map((item: any) => (<p>{item?.labelIdentify}</p>))
}
</div>);
};
export default addLabel;
方法二:监听要获取状态值,值改变重新绑定事件
import React, { useState, useEffect } from 'react';
const addLabel: React.FC = () => {
const [dataSource, setDataSource] = useState<any>([]);
useEffect(() => {
window.onclick = () => {
setDataSource([...dataSource, {
labelIdentify: Math.random(),
labelName: 0,
}])
}
}, [dataSource]); return (<div>
{
dataSource?.map((item: any) => (<p>{item?.labelIdentify}</p>))
}
</div>);
};
export default addLabel;
第二个思路,为什么class写法可以,hooks写法不行?class和hooks最大的区别是什么?我们都知道class和hooks最大的区别就是this,class中有this概念,有实例概念,实例是什么,简单来说就是个对象。而hooks是个函数,对某些特定数据封装过的函数。所以从数据类型角度考虑,利用引用数据类型引用的是地址,初次绑定事件回调里边取值不直接取状态值,而是把状态放到一个地址上,状态更新时候更新地址里的值(render或者useEffect 或者其他都可以),取值通过地址来取,这样通过地址取到的状态也就是最新的状态。
import React, { useState, useEffect } from 'react';
let object = {}
const addLabel: React.FC = () => {
const [dataSource, setDataSource] = useState<any>([]);
object.state = dataSource
useEffect(() => {
window.onclick = () => {
setDataSource([...dataSource, {
labelIdentify: Math.random(),
labelName: 0,
}])
}
}, []);
return (<div>
{
dataSource?.map((item: any) => (<p>{item?.labelIdentify}</p>))
}
</div>);
};
export default addLabel;
当然这个object 可以是window的一个属性或者任意一个全局属性,ref也可以,只要是个全局对象就行。
其中一个应用场景就是扫码枪监听keydown事件,如果绑定在组件加载完成,而在事件回调中又需要获取最新状态的时候,就只能获取绑定时候的状态值(初始值)
后边查看文章发现定时器也有类似的问题,详细看这里 zhuanlan.zhihu.com/p/579103609
附一篇useref文章blog.csdn.net/u011705725/…