引出问题
有了state,使得React组件可以随着某个值的改变而改变,我们无需再在某个值发生变化后重新手动对界面进行构建,React会替我们完成这些工作,大大降低了我们开发的难度。
但是state中还隐藏着一些不太容易发现的问题,现在假设我们需要开发一个计数器组件,这个组件非常简单,有一个按钮和一个数字,每点击一次按钮数字就会增加1,大概长成这个样子:
点击按钮以后,数字就会增加1,这个组件的实现很简单:
Counter.js
import React, {useState} from 'react';
const Counter = () => {
const [count, setCount] = useState(1);
const clickHandler = ()=> {
setCount(count+1);
}
return (
<div>
<h2>{count}</h2>
<button onClick={clickHandler}>+1</button>
</div>
);
};
export default Counter;
在clickHandler()
中,我们调用了setCount(count+1)
来对count进行更新,每次更新都是在前一次值的基础上增加1。这个代码这么写在大部分的场景下都不会带来任何的问题,但是在某些情况下就不一定了。
产生问题的原因
演示问题之前,先来说说产生这个问题的原因。在React中我们通过setState()
修改状态都是异步完成的,换句话说并不是调用完setState()
后状态立刻就发生变化,而是需要等上一段时间,当然这段时间不会很长。像上边的案例中state的修改虽然是异步完成的,但是由于功能比较简单,等待时间几乎可以忽略不计。但随着功能复杂度的提升,这个间隔会逐渐增多。
问题演示
假设调用setState()
后1秒state的值才会真的改变,这时如果我们连续点击按钮2次,第1次点击按钮时count值是1,第2次点击速度比较快,从而两次间隔没有超过1秒,此时的count值依然是1,这就导致我点击了两次按钮,但是值只增加了1次,因为两次count+1中的count都是1。
为了演示问题,可以将上述案例的setCount()
放入到一个延时调用中:
import React, {useState} from 'react';
const Counter = () => {
const [count, setCount] = useState(1);
const clickHandler = ()=> {
setTimeout(()=>{
setCount(count+1);
}, 1000);
}
return (
<div>
<h2>{count}</h2>
<button onClick={clickHandler}>+1</button>
</div>
);
};
这样一来,点击按钮后1秒setCount()
才会调用,如果我们在1秒内点击按钮多次,你会发现按钮数值只会增加一次,很显然我们不希望这种情况出现。
解决问题
要解决这个问题,其实也不难,在setState()
时除了直接传递一个指定值以外,React还允许我们通过一个回调函数来修改state,回调函数的返回值就是新的state的值,使用回调函数的好处是,这个回调函数会确保上一次的setState()
调用完成后才被调用,同时会使用最新的state值作为回调函数的第一个参数。这样一来就有效的避免了无法正确获取上一个state值的问题。
上边案例中的 setCount(count+1);
可以改成这个样子:
setCount(prevState => prevState+1);
这样一来,函数中的prevState总是上次修改后的最新state,避免再次出现点击多次按钮只修改一次的问题。总的来说,当我们修改一个state的值而需要依赖于前边的值进行计算时,最安全的方式就是通过回调函数而不是直接修改。
打卡
厉害了
你好
超哥威武