State 的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。

例如,你的 state 包含几个独立的变量:

constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}

然后你可以分别调用 setState() 来单独地更新它们:

componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});

fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}

这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments


源码

react-reconciler -> ReactUpdateQueue.old.js -> processUpdateQueue

image-20220703233800310

这个processUpdateQueue就是用来计算状态更新的方法。

可以看到这个方法从workInProgress取出了updateQueue,这个updateQueue就是上一次文章中提到的保存了update对象的队列。

这个方法中又调用了一个getStateFromUpdate方法,这个方法就会根据updateQueue计算State。

image-20220703234109078

可以从下面的源码中看到:

​ React源码中,使用了Object.assign()方法,对preState和更新后的State做了一个合并。

image-20220703234406144

同时,update.payload就是我们传递给setState的参数。

React会对参数做一个判断:

  • 如果传入的是一个function就会执行这个函数,获得对应的State。
  • 如果不是函数(一般都是一个对象),React会直接将它赋值给partialState,也就是newState。

因此,如果多次传入相同的对象,如:

this.state = { counter: 0 };

increment() {
this.setState({
counter: this.state.counter + 1
});
this.setState({
counter: this.state.counter + 1
});
this.setState({
counter: this.state.counter + 1
});
}

increment();
调用后 this.state = { counter: 1 };
而不是3

原因就是每次拿到的partialState都是 { counter: 0 }, 然后对它们做合并操作, 最后还是 { counter: 1 },

所以如果想完成上面的操作,需要给setState传入函数, 从源码中可以看到,函数可以接受到两个参数,一个是上一次的prevState,一个是nextProps

image-20220704112745905

this.state = { counter: 0 };

increment() {
this.setState((preState, nextProps) => {
return {
counter: preState.counter + 1;
}
});
this.setState((preState, nextProps) => {
return {
counter: preState.counter + 1;
}
});
this.setState((preState, nextProps) => {
return {
counter: preState.counter + 1;
}
});
}

this.state = { counter: 3 };

参考: