React@16.x(5)核心概念-setState

最佳实践

  1. 把所有的 setState 当做异步的。
  2. 永远不要信任 setState 调用之后的状态(可能未更新)。
  3. 如果要使用改变后的状态,需要使用回调函数(setState 的第2个参数)。
  4. 如果新的状态,需要使用之前的状态参与计算,则使用函数的方式改变状态(setState 的第1个参数改为函数)

1,异步更新状态

this.setState() 改变状态后,会触发 render 函数执行,但不是立即执行。

而状态的改变是异步还是同步,取决于执行 setState()的方法是否通过事件调用。如果是事件调用,则是异步的。

状态的改变,才会触发 render 执行。

下面的代码中,状态更新就是异步的。所以第1次点击时输出:0,render

class MyClassComp extends Component {
    state = {
        num: 0,
    };
    handleClick = () => {
        this.setState({
            num: this.state.num + 1,
        });
        console.log(this.state.num);
    };
    render() {
        console.log("render");
        return (
            <>
                <div>{this.state.num}</div>
                <button onClick={this.handleClick}>1</button>
            </>
        );
    }
}

第2个参数

setState() 的第2个参数是函数,可获取更改后的状态。该函数在 render 之后执行(状态改变,才能获取新的状态)。

更改 handleClick 后,第1次点击输出:0,render,第2个参数 1

handleClick = () => {
   this.setState({
        num: this.state.num + 1,
    }, () => {
        console.log('第2个参数',this.state.num);
    });
    console.log(this.state.num); 
};

2,多个 setState

1,问题

更改 handleClick 如下,第1次点击后输出:0,render。页面显示 1

handleClick = () => {
   this.setState({
        num: this.state.num + 1,
    });
    this.setState({
        num: this.state.num + 1,
    });
    this.setState({
        num: this.state.num + 1,
    });
    console.log(this.state.num);
};

原因:因为状态更改是异步的,所以上面的代码相当于:

handleClick = () => {
    this.setState({
        num: 0 + 1,
    });
    this.setState({
        num: 0 + 1,
    });
    this.setState({
        num: 0 + 1,
    });
    console.log(this.state.num);
};

2,配合第2个参数解决

更改 handleClick 如下,第1次点击时输出:0,3次render,页面显示 3

上面说了,第2个函数参数会在状态更新后执行,所以执行顺序:
状态更新–> render --> 状态更新–> render --> 状态更新–> render

handleClick = () => {
    this.setState({
        num: this.state.num + 1,
    }, () => {
        this.setState({
            num: this.state.num + 1,
        }, () => {
            this.setState({
                num: this.state.num + 1,
            })
        })
    });
    console.log(this.state.num);
};

3,第1个参数是函数

使用场景:如果遇到某个事件中,需要同步调用多次setState,则可使用函数的方式得到最新状态。

这种情况下,会将每个 setState 的函数参数放到一个队列中,按顺序执行。队列执行完毕后,再更新真正的 state,再执行 render(只执行了1次)。

注意是对异步 setState 的处理。同步的更新并不会合并!render 函数会执行多次。
为什么会合并?因为 React 对事件处理函数处理的思路时,元素的事件处理,可能会操作比较多的东西,如果不加限制,会影响性能。比如一个点击事件的逻辑中会触发多个自定义方法,每个方法中又会更改不同的状态。所以会将他们合并为一次修改,做成异步的,最终统一更新,执行一次 render 函数。
而不在HTML元素的事件中,不会遇到复杂的处理,比如计时器中。

在队列中的函数,可以获取上一个函数更新后的 state。换句话说,作为参数的 state 是可以被信任的(最新的)。

更改 handleClick如下,第一次点击时输出:0,1,2,3,render,页面显示 3

handleClick = () => {
    this.setState((curState) => {
        console.log(1);
        return {
            num: curState.num + 1,
        };
    });
    this.setState((curState) => {
        console.log(2);
        return {
            num: curState.num + 1,
        };
    });
    this.setState((curState) => {
        console.log(3);
        return {
            num: curState.num + 1,
        };
    });
    console.log(this.state.num);
};

注意,第2个参数依旧会在 render 执行后执行。

修改第1个 setState 如下,第一次点击的输出:0,1,2,3,render,x

this.setState((curState) => {
    console.log(1);
    return {
        num: curState.num + 1,
    };
}, () => {
    console.log('x')
});

4,同步更新状态举例

虽然是在点击事件中,但因为嵌套了计时器,所以可看做同步 setState

举例1:1s 后输出:render,1

handleClick = () => {
    setTimeout(() => {
        this.setState({
            num: this.state.num + 1,
        });
        console.log(this.state.num);
    }, 1000);
};

举例2:1s 后输出:3次render,3

handleClick = () => {
    setTimeout(() => {
        this.setState({
            num: this.state.num + 1,
        });
        this.setState({
            num: this.state.num + 1,
        });
        this.setState({
            num: this.state.num + 1,
        });
        console.log(this.state.num);
    }, 1000);
};

举例3,异步函数不会阻塞 render 执行,但异步函数之后的 setState 算做同步。

const delay = (duration = 1000) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve();
        }, duration);
    });
};

handleClick = async () => {
    this.setState({
        num: this.state.num + 1,
    });
    // 会同步执行 render
    await delay();
    // num 已经是修改后的
    this.setState({
        num: this.state.num + 1,
    });
    console.log(this.state.num);
};

第一次点击是的输出:render,(等待1s)render,2

这里,第2个 render 比 2 先输出,就是因为异步函数之后,算做同步 setState 了。


以上。

  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

下雪天的夏风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值