React的setState方法,可以接收两个参数,一个是对象/函数【必须】,一个是回调方法callback【非必须】
这是setState部分的源码
Component.prototype.setState = function (partialState, callback) {
(function () {
if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
{
throw ReactError(Error('setState(...): takes an object of state variables to update or a function which returns an object of state variables.'));
}
}
})();
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
按照官方文档的说法,setState有三点需要注意:
一、不要直接修改 State
直接修改state并不会重新渲染组件,我们来可以做个试验
import React from 'react';
export default class C1 extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
}
handle() {
this.state.counter = 2;
}
componentDidUpdate() {
console.log('C1组件更新完毕 componentDidUpdate');
}
render() {
console.log('C1子组件render');
return (
<div>
<p>C1组件</p>
{this.state.counter}
<button
type="button"
onClick={() => {
this.handle();
}}
>
更新界面
</button>
</div>
);
}
}
结果肯定是没有效果的,非但没有效果,在保存的时候还会提示需要用setState
二、State 的更新会被合并
这个也很简单,setState方法只会更新方法的参数对象里面相应的键值,功能类似ES6的Object.assign静态方法
const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };
const returnedTarget = Object.assign(target, source);
console.log(target);// { a: 1, b: 3, c: 4 }
console.log(returnedTarget);// { a: 1, b: 3, c: 4 }
三、State 的更新可能是异步的
这句话什么意思呢?我琢磨了很久~
出于性能考虑,React 可能会把多个 setState()调用合并成一个调用。
例子
import React from 'react';
export default class C1 extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
}
handle() {
this.setState({
counter: this.state.counter + 1
});
this.setState({
counter: this.state.counter + 2
});
}
componentDidUpdate() {
console.log('C1组件更新完毕 componentDidUpdate');
}
render() {
console.log('C1子组件render');
return (
<div>
<p>C1组件</p>
{this.state.counter}
<button
type="button"
onClick={() => {
this.handle();
}}
>
更新界面
</button>
</div>
);
}
}
点击更新界面按钮之后,counter在界面的显示还是3,这是为什么?原因就是上面那句话,出于性能考虑,React 可能会把多个 setState()调用合并成一个调用。
我们都知道,渲染DOM会消耗性能,所以React为了提高整体的性能,在同一个渲染周期内,对setState进行合并,因此在上面这个例子中,react只认准了当前这次渲染周期的最后一个setState,所以1+2等于3。
那什么时候react不会合并同一个渲染周期里面的setState呢?这就涉及到setState的另一种用法:
import React from 'react';
export default class C1 extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
}
handle() {
this.setState(state => ({
counter: state.counter + 1
}));
this.setState(state => ({
counter: state.counter + 1
}));
}
componentDidUpdate() {
console.log('C1组件更新完毕 componentDidUpdate');
}
render() {
return (
<div>
<p>C1组件</p>
{this.state.counter}
<button
type="button"
onClick={() => {
this.handle();
}}
>
更新界面
</button>
</div>
);
}
}
点击更新界面按钮之后counter渲染为3,setState也可以传入一个函数作为第一个参数
this.setState((state,props,context)=>({
xx:state.xx+?
}))
函数里面传入了两个参数,即上一次更新的state和当前的props,这样在第二次调用setState方法时便可以通过state.counter拿到最新的值从而更新本次的state.counter。
所以通过state参数对属性进行修改,就不会被合并处理,在函数里面如果还用this.state方式进行操作的话,还是会被合并处理,因为此时的this.state里面的值还不是最新的。为什么这么说?我们可以通过一个例子来验证一下。
import React from 'react';
export default class C1 extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
}
handle() {
// this.setState(state => ({
// counter: state.counter + 1
// }));
this.setState({
counter: this.state.counter + 1
});
console.log('handle执行时的counter',this.state.counter);
}
componentDidUpdate() {
console.log('C1组件更新完毕 componentDidUpdate');
console.log('更新完毕的counter',this.state.counter);
}
render() {
console.log('render的counter',this.state.counter);
return (
<div>
<p>C1组件</p>
{this.state.counter}
<button
type="button"
onClick={() => {
this.handle();
}}
>
更新界面
</button>
</div>
);
}
}
点击更新界面之后
我们可以看到初始的counter为1,在handle的时候this.state.counter在setState之后,state并没有合并到this对象上面去,所以值并没有发生改变,直到render执行之后,渲染到页面之后,才发生改变。当然,如果更改为上面那段注释的代码进行赋值操作的话,也会出现这种情况。
我们在源码中可以找到这段注释
* When a function is provided to setState, it will be called at some point in
* the future (not synchronously). It will be called with the up to date
* component arguments (state, props, context). These values can be different
* from this.* because your function may be called after receiveProps but before
* shouldComponentUpdate, and this new state, props, and context will not yet be
* assigned to this.
这段注释,也从另一角度解释了一开始出现的,通过this.state.counter的方式修改counter无论怎样都只认最后一个setState的问题。
四、一个新的问题
下面这段代码为什么能够实现连续的更新界面
import React from 'react';
export default class C1 extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
}
handle() {
this.setState({
counter: this.state.counter + 1
});
console.log('handle执行时的counter', this.state.counter);
setTimeout(() => {
this.setState({
counter: this.state.counter + 1
});
this.setState({
counter: this.state.counter + 1
});
console.log('setTimeout的counter', this.state.counter);
}, 0);
}
componentDidUpdate() {
console.log('C1组件更新完毕 componentDidUpdate');
console.log('更新完毕的counter', this.state.counter);
}
render() {
console.log('render的counter', this.state.counter);
return (
<div>
<p>C1组件</p>
{this.state.counter}
<button
type="button"
onClick={() => {
this.handle();
}}
>
更新界面
</button>
</div>
);
}
}
点击按钮之后结果为4,这是为什么?
handle遇到定时器的时候,会挂起,等到渲染的执行栈执行完毕之后,再执行定时器里面的方法。定时器里面的代码就不涉及到合并setState的逻辑了。
参考链接:
1.https://blog.csdn.net/handsomexiaominge/article/details/86348235
2.https://react-1251415695.cos-website.ap-chengdu.myqcloud.com/docs/state-and-lifecycle.html