设计state

state是跟UI相关的,组件的任何UI改变都可以从state的变化中反映出来。state代表一个组件UI呈现的最小状态,没有任何多余的状态,也不应该存在通过其他状态计算而来的中间状态。

  1. 1. 多余的中间状态
  2. //totalCost 就是一个多余的状态,因为其可以通过list计算而来。
  3. {
  4. list: [], //购物车列表
  5. totalCost: 0 //总价格
  6. }
  7. 2. 不跟UI相关的变量作为组件实例的普通属性
  8. // 计时器例子
  9. componentDidMount() {
  10. this.timer = setInterval(this.updateDate, 1000)
  11. }
  12. 复制代码

state一般又分为两种类型:
1 用作渲染组件使用到的数据来源
2 用作组件UI展示的判断依据

  1. thisstate = {
  2. user: "Tom", //展示内容数据
  3. isLogin: true // 展示判断依据
  4. }
  5. { this.state.isLogin ? <h1>Hello, {this.state.user}</h1> : null }复制代码

修改state

状态可以通过this.state.xxx来直接获取,但是修改state缺需要多多注意:

1. 不能直接修改state

直接修改state,尽管不会报错,但是不会触发render,正确的方法是使用setState
this.setState({ title: "react"})

2. state的更新是异步的

调用setState并不会立刻改变state,而是把要修改的状态放入一个队列中,React会优化真正的执行时机。可能会将多次setState的状态修改合并到一次状态修改。所以不能依赖当前的state,计算下一个state。
比如,点击一个购买按钮,购买的数量就会加1。如果连续点击两次,则会连续两次调用this.setState({ quantity: this.state.qunatity + 1 })。 在合并为一次修改得情况下,相当于等价执行如下代码:

  1. Object.assign(previousState, {quantity: this.state.qunatity + 1 }, {quantity: this.state.qunatity + 1 })复制代码

最终购买的数量只增加1。
所以解决办法是,调用setState,传入一个函数作为参数。这个函数有两个参数,第一个是当前的最新状态的前一个状态preState,第二个参数是当前最新的属性props

  1. this.setState((preState, props)=> ({
  2. counter: preState.quantity + 1
  3. }) )复制代码

3. state的更新是一个合并的过程

当调用setState修改组件状态时,只需传入发生改变的state,而不是组件完整的state,因为组建的state的更新过程是一个合并的过程。

  1. thisstate = {
  2. user: "Tom", //展示内容数据
  3. isLogin: true // 展示判断依据
  4. }
  5. // 只需传入改变的state
  6. this.setState({
  7. user: "Tony"
  8. })复制代码

不可变对象

React官方建议把state当做不可变对象,一方面,直接修改this.state,组件并不会重新render;另一方面,state中包含的所有状态都应该是不可变对象。当state中的某个状态返生变化,应该重新创建这个状态对象,而不是直接修改原来的状态。如何创建,分三种情况:

1. 状态的类型是不可变类型(数字 字符串 布尔值 null undefined)

因为状态是不可变类型,所以直接给要修改得状态赋值一个新值即可

2. 状态的类型是数组

不能使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而concat、slice、filter会返回一个新的数组。也可以使用ES6的数组扩展语法
通常如果需要使用push、splice等方法,首先会先调用slice方法,返回一个新的组数,在新数组上使用push等方法。

  1. this.setState(preState => ({
  2. books: preState.books.concat(["nodejs"]}
  3. }))
  4. 复制代码
  1. this.setState(preState => ({
  2. books: [...preState.books, "nodej"]
  3. }))复制代码

3. 状态的类型是普通对象

  1. // 使用ES6的Object.assgin 方法
  2. this.setState(preState => ({
  3. people: Object.assign({}, preState.owner, {name: "Tom"})
  4. }))复制代码
  1. // 使用对象扩展方法
  2. this.setState(preState => ({
  3. people: {...preState.owner, name: "Tom"}
  4. }))复制代码

为什么React推荐组件的状态是不可变对象,原因大概有两点:
1. 防止原有对象意外修改。虽然react使用setState来改变state,但是直接this.state.xx = yy直接赋值并不会报错,只是不会触发render,但是state确实发生了变化,有可能导致不可预期的错误。
2. 处于性能考虑,可以在shouldComponentUpdate方法中仅仅比较两次状态对象的引用就可以判断状态是否发生了改变。 尤其我们在使用pureComponent组件时,其内部会在shouldComponentUpdate执行“浅比较”,就是只比较this.state.aa === nextState.aa,通过判断对象引用是否变化,如果对象变化但是引用没有变,及在原有对象修改(push、pop等方法),也不会触发render,这个要特别注意了。