如果你曾经试图在React组件中连续多次设置一个状态变量,你可能会惊讶地发现,它并不完全有效。
首先,让我澄清一下我说的 "连续多次 "是什么意思。我们将使用一个非常具体的例子--计数器。
function App() {
const [count, setCount] = useState(0);
const incrementTwice = () => {
setCount(count + 1);
setCount(count + 1);
};
return (
<>
{count}
<br />
<button onClick={incrementTwice}>Increment Twice</button>
</>
);
}
我们可以合理地预期,每次你点击 "增加两次 "按钮,count ,就会增加两次。但是它没有!它只是增加1。它只是在你每次点击时增加1。确定性的。
那么,为什么会这样呢?
重温基本原理
还记得闭包吗?是的,这就是这里发生的事情。尽管我们是在React领域,但我们最终仍然是在处理JavaScript。在我们的计数器例子中,incrementTwice 函数对count 和它在当前渲染时的值有封闭性。对于两个 setCount 的调用,这个值是0 。想一想:第二个setCount 的调用怎么可能知道count 的未来值呢?
所以......我们把count 设置为0 + 1 ,然后再把count 设置为0 + 1......哎呀!
状态下的对象怎么办?
对状态中的对象也是如此。让我们看看另一个假想的例子。
function App() {
const [person, setPerson] = useState({ name: 'Gerald', age: 19 });
const incrementTwice = () => {
setPerson({ ...person, age: person.age + 1 });
setPerson({ ...person, age: person.age + 1 });
};
return (
<>
{person.age}
<br />
<button onClick={incrementTwice}>Increment Twice</button>
</>
);
}
同样,我们在当前渲染过程中对person 对象进行了封闭,这个对象的age 是19。尽管我们第一次调用了setPerson ,但它现在在内存中创建了一个全新的对象,而第二个setPerson 却对此一无所知。结果是:可怜的杰拉尔德在这里只能递增到20岁。
修复问题
修复问题背后的问题
你可能只是想让代码修复这个问题,但我也认为必须认识到这种情况可能是一种代码的味道--你很可能重写你的代码,使之只需要一个状态更新。在我们设计的例子中,这很容易:你只需将计数/年龄增加2而不是1。在你目前面临的情况下,你很可能只需要做一次状态更新,但这肯定有点难看。
实际的代码修正
假设你对我的 "代码气味 "讨论不感兴趣(谁能怪你呢),确保你在状态设置器中总是引用最新版本的状态的方法是使用一个回调函数。
function App() {
const [person, setPerson] = useState({ name: 'Gerald', age: 19 });
const incrementTwice = () => {
setPerson((p) => ({ ...p, age: p.age + 1 }));
setPerson((p) => ({ ...p, age: p.age + 1 }));
};
return (
<>
{person.age}
<br />
<button onClick={incrementTwice}>Increment Twice</button>
</>
);
}
而这将使杰拉尔德的年龄增加2!这里发生的事情是setPerson ,不再对person ,而是接收一个函数。React将继续前进,并将当前版本的人的状态--第一次是19岁,第二次是20岁--传递给那个回调函数。
结论
尽管React之地感觉有点神奇,但我们仍然受到JavaScript的基本规则的约束!