借助于 Redux,我们可以轻松的对 React 中的状态进行管理和维护,同时,React 也为我们提供了组件内的状态管理的方案,也就是 setState()。本文不会涉及到 Redux,我们将从 Component 的角度来说明你不知道的以及更合理的 setState()

先说说大家都知道的

在 React 文档的 State and Lifecycle 一章中,其实有明确的说明 setState() 的用法,向 setState() 中传入一个对象来对已有的 state 进行更新。

比如现在有下面的这样一段代码:

 
      
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: this.state.count + 1
};
}
}

我们如果想要对这个 state 进行更新的话,就可以这样使用 setState()

 
      
this.setState({
count: 1
});

你可能不知道的

最基本的用法世人皆知,但是,在 React 的文档下面,还写着,处理关于异步更新 state 的问题的时候,就不能简单地传入对象来进行更新了。这个时候,需要采用另外一种方式来对 state 进行更新。

setState() 不仅能够接受一个对象作为参数,还能够接受一个函数作为参数。函数的参数即为 state 的前一个状态以及 props。

所以,我们可以向下面这样来更新 state:

 
      
this.setState( (prevState, props) => ({ count: prevState.count + 1 }));

这样写的话,能够达到同样的效果。那么,他们之间有什么区别呢?


区别

我们来详细探讨一下为什么会有两种设置 state 的方案,他们之间有什么区别,我们应该在何时使用何种方案来更新我们的 state 才是最好的。

此处,为了能够明确的看出 state 的更新,我们采用一个比较简单的例子来进行说明。

我们设置一个累加器,在 state 上设置一个 count 属性,同时,为其增加一个 increment 方法,通过这个 increment 方法来更新 count

此处,我们采用给 setState() 传入对象的方式来更新 state,同时,我们在此处设置每调用一次 increment 方法的时候,就调用两次 setState()。具体的原因我们在后文中会讲解。

具体的代码如下:

 
      
class IncrementByObject extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind( this);
}
// 此处设置调用两次 setState()
increment() {
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div>
<button onClick={this.increment}>IncrementByObject </button>
<span>{this.state.count} </span>
</div>
);
}
}
ReactDOM.render(
<IncrementByObject />,
document.getElementById('root')
);

这时候,我们点击 button 的时候,count 就会更新了。但是,可能与我们所预期的有所差别。我们设置了点击一次就调用两次 setState(),但是,count 每一次却还是只增加了 1,所以这是为什么呢?

其实,在 React 内部,对于这种情况,采用的是对象合并的操作,就和我们所熟知的 Object.assign() 执行的结果一样。

比如,我们有以下的代码:

 
      
Object.assign({}, { a: 2, b: 3 }, { a: 1, c: 4 });

那么,我们最终得到的结果将会是 { a: 1, b: 3, c: 4 }。对象合并的操作,属性值将会以最后设置的属性的值为准,如果发现之前存在相同的属性,那么,这个属性将会被后设置的属性所替换。所以,也就不难理解为什么我们调用了两次 setState() 之后,count 依然只增加了 1 了。

用简短的代码说明就是这样:

 
      
this.setState({
count: this.state.count + 1
});
// 同理于
Object.assign({}, this.state, { count: this.state.count + 1 });

以上是我们采用对象的方式传入 setState() 来更新 state 的说明。接下来我们再看看使用函数的方式来更新 state 会有怎么样的效果呢?


我们将上面的累加器采用另外的方式来实现一次,在 setState() 的时候,我们采用传入一个函数的方式来更新我们的 state。

 
      
class IncrementByFunction extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind( this);
}
increment() {
// 采用传入函数的方式来更新 state
this.setState( (prevState, props) => ({
count: prevState.count + 1
}));
this.setState( (prevState, props) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<button onClick={this.increment}>IncrementByFunction </button>
<span>{this.state.count} </span>
</div>
);
}
}
ReactDOM.render(
<IncrementByFunction />,
document.getElementById('root')
);

当我们再次点击按钮的时候,就会发现,我们的累加器就会每次增加 2 了。

我们可以通过查看 React 的源代码来找出这两种更新 state 的区别 (此处只展示通过传入函数进行更新的方式的部分源码)。

在 React 的源代码中,我们可以看到这样一句代码:

 
      
this.updater.enqueueSetState( this, partialState, callback, 'setState');

然后,enqueueSetState 函数中又会有这样的实现:

 
      
queue.push(partialState);
enqueueUpdate(internalInstance);

所以,与传入对象更新 state 的方式不同,我们传入函数来更新 state 的时候,React 会把我们更新 state 的函数加入到一个队列里面,然后,按照函数的顺序依次调用。同时,为每个函数传入 state 的前一个状态,这样,就能更合理的来更新我们的 state 了。


React中另一个改变状态的方法 replaceState

    

replaceState()方法与setState()类似,但是方法只会保留nextState中状态,原state不在nextState中的状态都会被删除。使用语法:

replaceState(object nextState[, function callback])
  • 1
  • nextState,将要设置的新状态,该状态会替换当前的state。
  • callback,可选参数,回调函数。该函数会在replaceState设置成功,且组件重新渲染后调用。

为了说明 setState() 和 replaceState() 的区别,通过以下例子说明:

var HelloMessage = React.createClass({
    getInitialState: function() {
        return { 
            isRed : true,
            count:0
         };
    },
    handleClick1: function(){
        this.setState({isRed : !this.state.isRed},function(){
            console.log("setState:");
            console.log(this.state);
        });
    },
    handleClick2: function(){
        this.replaceState({count : this.state.count+1},function(){
            console.log("replaceState:");
            console.log(this.state);
        });
    },
  render: function() {
    var redStyle = {
        color:'red'
    };
    var blueStyle = {
        color:'blue'
    };
    return (
        <div>
            <h1 style={this.state.isRed ? redStyle : blueStyle}>Hello {this.props.name}!</h1>
            <button onClick={this.handleClick1}>点击切换颜色</button>
            <button onClick={this.handleClick2}>点击次数:{this.state.count}</button>
        </div>
        );
  }
});

ReactDOM.render(
    <HelloMessage name='sean' />,
    document.getElementById("example"));

    </script>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

以上程序设置了两个button,一个调用setState() 另一个调用replaceState(),我们看一下运行结果:

这里写图片描述

通过运行结果我们可以知道,点击第一个button时,调用 setState() ,会将新状态合并到当前状态,因此,状态 isRed 更新到最新状态,而状态 count 没有改变,保持原有状态。

当点击第二个button时,调用 replaceState(),替换当前的状态,由于replaceState()中只设置状态 count , 因此state中只有count一个状态,由于isRed状态不存在,this.state.isRed ? redStyle : blueStyle中 this.state.isRed为false,这样显示的样式为 blueStyle

文章来源:

1:https://blog.csdn.net/u014607184/article/details/53234103;

2:https://www.erichain.me/2017/04/17/2017-04-17-more-reasonable-setstate/