日期:2022年12月2日

state的问题

引出问题

有了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的值而需要依赖于前边的值进行计算时,最安全的方式就是通过回调函数而不是直接修改。

5 2 投票数
文章评分
订阅评论
提醒
guest

4 评论
最旧
最新 最多投票
内联反馈
查看所有评论
Chin
Chin
5 月 前

打卡

断章*
断章*
4 月 前

厉害了

都是*
都是*
4 月 前
回复给  断章*

你好

阿康*
阿康*
2 月 前

超哥威武

4
0
希望看到您的想法,请您发表评论x