由于某种原因(这个原因我还说不清楚,你可以帮我完整的描述一次吗🤕)
,我们在react的某些时候调用的方法中无法获取到最新的state,比如事件监听
举个例子,在某个页面添加一个DOM监听点击事件,点击后输出count的值,这个count可能会通过其他方法改变
如果是class的写法,我们就没有这个疑问,因为在执行方法中我们都是直接从this.state里的值,始终是最新的;如果修改后需要立即用最新的值做其他的操作,也可以利用setState的回调函数
state = {
count: 0,
};
componentDidMount() {
// 添加事件监听
document.addEventListener("click", () => handleClick(), true)
}
componentWillUnmount() {
// 移除事件监听
document.removeEventListener("click", () => handleClick(), true)
}
const handleClick = () => {
this.setState({
count: this.state.count + 1 //设置一个新的值
}, () => {
// 这里的state是最新的
console.log(this.state.count)
})
}
当使用react Hooks时,常规写法是
const [count, setCount] = useState<number>(0);
useEffect(() => {
// 添加事件监听
document.addEventListener("click", handleClick, true)
return () => {
// 移除事件监听
document.removeEventListener("click", handleClick, true)
}
}, [])
const handleClick = () => {
// 执行一些读取state的操作
console.log(count);
setCount(count+1);
}
我们平时就很容易写成上面这样,这个时候就发现,count永远输出的是初始值0,然后页面渲染的count值除了第一次点击从0变成1后,就再也不会变化了真是个头痛的问题,也不报错,不报错的bug最难找咯🥺
解决办法:
- 由于state的值改变,我们可以在useEffect中监听这个state的改变,当state改变,则取消上一次的监听,并重新新的一轮监听,这样在监听执行方法中获取的state就一直都是最新的了
useEffect(() => {
// 添加事件监听
document.addEventListener("click", handleClick, true)
return () => {
// 移除事件监听
document.removeEventListener("click", handleClick, true)
}
}, [count])
这是一种解决办法,但是每一次count的修改,都会取消上一次的监听,并重新绑定监听,如果多几个这样的操作,可能会消耗内存
- 通过 ref 来保存可变变量,声明一个useRef,每次state更新,都把新值赋给这个ref,在执行的方法中使用这个ref代替state
const [count, setCount] = useState<number>(0);
const countRef = useRef<number>(count);
useEffect(() => {
// 添加事件监听
document.addEventListener("click", handleClick, true)
return () => {
// 移除事件监听
document.removeEventListener("click", handleClick, true)
}
}, [])
useEffect(() => {
countRef.current = count; //让countRef.current永远和count保持一致,避免其他操作改变了count的值
}, [count])
const handleClick = () => {
// 使用countRef.current代替count
console.log(count);
setCount(countRef.current+1);
}
运行后会发现,在监听执行函数中打印的count始终还是初始值0,但是页面渲染的count已经随时在改变啦~
我们要知道在handleClick函数中拿到的count和函数外拿到的count已经不是同一个东西了,函数内的count在开始监听document.addEventListene的时候已经确定了。
这是我目前知道的两种方法,有更好的方法时更新~
看文档果然才是最重要的,文档明确说了setState除了可以接收state的最新值外,还可以接收一个函数,这个函数的参数是旧的state,返回值为新值,所以我们可以这样写
useEffect(() => {
// 添加事件监听
document.addEventListener("click", handleClick, true);
return () => {
// 移除事件监听
document.removeEventListener("click", handleClick, true) }
}, [])
const handleClick = () => {
// 执行一些读取state的操作
console.log(count);
setCount(preValue => {
// 执行一些读取state的操作
console.log(preValue);
return preValue+1;
});
}