React入门教程 - state和setState概述

1,096 阅读5分钟

概述

本文主要讨论下React中state的使用,以及setState的使用。

定义

state可以被视为React组件中的一个数据集合,这些数据用于记录组件中的可变状态,也就是说这些数据在组件中可以被修改。

那么,如何判断一个变量是否适合作为一个state中的数据?

组件对State的要求

在组件中,对于state数据,有着以下的要求:

  • state能代表组件呈现的完整状态集:组件所有的状态改变,都能在state中体现出来
  • state能代表组件呈现的最小状态集:state中的所有数据都体现组件的变化,没有多余的状态,也不需要通过其他状态计算来的中间状态

State与Props的区别

  • state用于体现组件的变化,在当前组件中是可变的
  • props从父级组件传入,在当前组件中是只读的

变量能否作为State的判据

所以,由上面的描述,我们可以从下面这几个方面判断一个变量是否可以作为state:

  • 该变量是否由父级组件传入?若是,则不可用于state
  • 该变量是否在组件整个生命周期内不变?若是,则不可用于state
  • 该变量是否可以由其他state数据或props计算得到?若是,则不可用于state
  • 该变量是否在render中用于渲染数据?若不是,则不可用于state

若变量不适用于作为state,则一般定义为组件的一个普通属性,即this.name等变量。

若某个状态在多个组件中使用,可考虑将该状态进行升级,提升到父级组件中。

State的更新

在React中。我们使用setState()来进行state的更新,而不是直接更新state的状态。

// 错误的更新方法
this.state.name = '张三';

// 正确的更新方法
this.setState({name: '张三'});

状态更新可能是异步的

出于性能方面的考虑,React 可以将多个setState()调用合并成一个调用来提高性能。

因此,this.state可能是异步更新的,你不应该依靠它们的值来计算下一个状态。

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

我们期待state中的count增加了3次,但结果可能是增加了1次

由于this.state可能是异步更新,this.setState()只是在反复设置同一个值而已,上述代码可能等同于:

const currentCount = this.state.count;
this.setState({count: currentCount + 1});
this.setState({count: currentCount + 1});
this.setState({count: currentCount + 1});

那么,如果我们需要最后的结果一定为3,我们可以怎么做?

通常做法是将this.setState()的第一个参数修改为一个函数,该函数将接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数:

this.setState((prevState, props) => ({
  counter: prevState.currentCount + 1;
}));
... // 重复调用3次即可

this.setState()使用时可传入第二个参数,该参数为一个可选的回调函数,其将会在setState执行完成同时组件被重渲之后再执行。

状态更新合并

state的更新是一个浅合并的过程,所以在更新state时,只需传入发生改变的state数据,而不用传入全都的state数据。

比如,你的组件的state包含多个变量:

constructor(props) {
    super(props);
    this.state = {
      title: 'hello world',
      name: '张三'
    };
}

当你更新state中的name属性时,只需要:

this.setState({name: '李四'});

React会合并新的name到原来的state中,同时保留原有的状态title,合并后的state结果为:

{
  title: 'hello world',
  name: '李四'
}

状态更新与Immutable

Immutable Data就是一旦创建,就不能再被更改的数据。

React官方建议把state中的数据当作是不可变对象,也就是说,当你想执行this.setState()的时候,你应该做的是:重新创建一个新值来赋给state中的数据。

一方面是因为不可变对象方便管理和调试,另一方面是因为当state数据都是不可变对象时,仅需要比较状态的引用就可以判断状态是否真的改变。

下面针对不同的数据类型分别描述。

数字,字符串,布尔值,null, undefined

针对基础类似数据的更新,直接赋值更新即可。

this.setState({
    age: 20,
    name: '李四',
    success: true,
    addr: null,
    tags: undefined
});

对象

对象类似细分可以分为狭义上的对象、数组、函数

当state的数据为数组时,需直接赋值一个新的数组。若需要在袁数组上操作,可使用以下方法:

var arr = this.state.arr;

// 方法一
this.setState({ arr: [].concat(arr, ['arr1']) });

// 方法二
this.setState({ arr: [...arr, 'arr1'] });

// 方法三
this.setState({ arr: arr.slice(1, 3); });

// 方法四
this.setState({ arr: arr.splice(1, 3, 'arr1'); });

还可以使用map、filter等方法来遍历处理数组

当state的数据为狭义上的对象时,可使用以下方法来修改原有对象:

var obj = this.state.obj;

// 方法一
this.setState({ obj: Object.assign({}, obj, {title: 'HELLO'}); });

// 方法二
this.setState({ obj: {...obj, title: 'HELLO'} });

状态更新与页面渲染

当新状态不同于之前状态时会触发重新渲染。所以,this.setState()总是会触发页面的重新渲染。this.setState()调用后,会触发生命周期的钩子函数shouldComponentUpdate,若该函数返回值为false,则不会触发重新渲染。

此外,我们可以使用this.forceUpdate()函数来跳过shouldComponentUpdate,强制性触发重新渲染。

参考链接