深入浅出React和Redux
上QQ阅读APP看书,第一时间看更新

2.2.2 React的state

驱动组件渲染过程的除了prop,还有state, state代表组件的内部状态。由于React组件不能修改传入的prop,所以需要记录自身数据变化,就要使用state。

在Counter组件中,最初显示初始计数,可以通过initValue这个prop来定制,在Counter已经被显示之后,用户会点击“+”和“-”按钮改变这个计数,这个变化的数据就要Counter组件自己通过state来存储了。

1.初始化state

通常在组件类的构造函数结尾处初始化state,在Counter构造函数中,通过对this. state的赋值完成了对组件state的初始化,代码如下:

constructor(props) {
  ...
  this.state = {
    count: props.initValue || 0
  }
}

因为initValue是一个可选的props,要考虑到父组件没有指定这个props值的情况,我们优先使用传入属性的initValue,如果没有,就使用默认值0。

组件的state必须是一个JavaScript对象,不能是string或者number这样的简单数据类型,即使我们需要存储的只是一个数字类型的数据,也只能把它存作state某个字段对应的值,Counter组件里,我们的唯一数据就存在count字段里。

注意

在React创建之初,使用的是React.createClass方法创建组件类,这种方式下,通过定义组件类的一个getInitialState方法来获取初始state值,但是这种做法已经被废弃了,我们现在都用ES6的语法定义组件类,所以不再考虑定义getInitialState方法。

由于在PropType声明中没有用isRequired要求必须有值的prop,例如上面的initValue,我们需要在代码中判断所给prop值是否存在,如果不存在,就给一个默认的初始值。不过,让这样的判断逻辑充斥在我们组件的构造函数之中并不是一件美观的事情,而且容易有遗漏。我们可以用React的defaultProps功能,让代码更加容易读懂。

给Counter组件添加defaultProps的代码如下:

Counter.defaultProps = {
  initValue: 0
};

有了这样的设定,Counter构造函数中的this.state初始化中可以省去判断条件,可以认为代码执行到这里,必有initValue属性值,代码可以简化为这样:

this.state = {
  count: props.initValue
}

以后,即使Counter的使用者没有指定initValue,在组件中就会收到一个默认的属性值0。

2.读取和更新state

通过给button的onClick属性挂载点击事件处理函数,我们可以改变组件的state,以点击“+”按钮的响应函数为例,代码如下:

onClickIncrementButton() {
  this.setState({count: this.state.count + 1});
}

在代码中,通过this.state可以读取到组件的当前state。值得注意的是,我们改变组件state必须要使用this.setState函数,而不能直接去修改this.state。如果不相信,你可以尝试把onClickIncrementButton函数修改成下面的样子:

onClickIncrementButton() {
  this.state.count = this.state.count + 1;
}

既然this.state就是一个JavaScript对象,上面的代码逻辑看起来也没有什么问题,我们在界面上点击几个按钮看一下实际效果就会发现问题。

首先,在浏览器的Console中会有如下的提示:

warning Do not mutate state directly. Use setState() react/no-direct-mutation-state

这是在警告:“不要直接修改state,应该使用setState()。”

当你点击“+”按钮,也不会看到后面的计数值有任何变化,但是当你点击“-”按钮,就会立即看到计数值发生变化,而且计数值会发生“跳跃”,比如在初始计数值为0的情况下,连续点击“+”按钮三次,计数值没有任何变化依然为0,点击了一下“-”按钮一次,就会看到计数值一下子变成了2,这是怎么回事?

直接修改this.state的值,虽然事实上改变了组件的内部状态,但只是野蛮地修改了state,却没有驱动组件进行重新渲染,既然组件没有重新渲染,当然不会反应this.state值的变化;而this.setState()函数所做的事情,首先是改变this.state的值,然后驱动组件经历更新过程,这样才有机会让this.state里新的值出现在界面上。

在上面描述的操作中,连续点击三次“+”按钮,this.state中的count字段值已经被增加到了3,但是没有重新渲染,这时候点击一次“-”按钮,触发的onClick-DecrementButton函数依然是用this.setState改变组件状态,这个函数调用首先把this.state中的count值从3减少为2,然后触发重新渲染,于是我们看到界面上的数字一下子从0跳跃为2。