精读 React functional setState

精读 React functional setState

本期精读文章: Functional setState is the future of React

1 引言

众所周知,React 组件中的 this.state 和 this.props 存在异步模式更新的情况,我们是没办法直接用他们的值计算下一个 state。出于性能方面的考虑 React 并不会在 setState({…newState}) 后直接更新 DOM,而是以一种批量更新的模式进行。但这对于需要需要当前 state 来计算新的 state 时就很麻烦,functional setState 很好的解决了这个问题。

2. 内容概要

传统的 setState

作为一个基于状态的 UI 库,React 就像是一个状态机将组件的所有状态映射为 UI 元素。React 自身有一个管理状态的函数 setState,开发者可以将一个对象,也就是需要更新的状态作为参数传入。

class User {
  constructor () {
    this.state = {
      score : 0
    };
  }

  // multiple setState() calls
  increaseScoreBy3 () {
    this.setState({score : this.state.score + 1});
    this.setState({score : this.state.score + 1});
    this.setState({score : this.state.score + 1});
  }

  render () {
    return (
      <div>
        <button onClick={this.increaseScoreBy3.bind(this)}>+++</button>
        <h5>This user scored {this.state.score}</h5>
      </div>
    );
  }
}

在上述的代码示例中,在我们调用了 increaseScoreBy3 方法后却不会将 state 更新为 3。正如文中一个形象的类比,『我不会爬到山峰三次,每一次都去更新一个状态』。

我们知道如果 shouldComponentUpdate 返回 true 时(默认返回为 true),setState 后会依次调用 4 个生命周期方法:

  1. shouldComponentUpdate
  2. componentWillUpdate
  3. render()
  4. componentDidUpdate

出于到性能方面的考量,React 避免了 3 次更新,在其内部使用了类似下面的方式来更新 state:

const singleObject = Object.assign(
  {}, 
  objectFromSetState1, 
  objectFromSetState2, 
  objectFromSetState3
);

由于 state 异步的原因导致了调用 increaseScoreBy3 方法后,组件的 score 状态为 1。

Functional setState 的意义

Functional setState 很好的解决了上面我们遇到的问题。

class User{
  state = {score : 0};
  //let's fake setState
  setState(state, callback) {
    this.state = Object.assign({}, this.state, state);
    if (callback) callback();
  }
  // multiple functional setState call
  increaseScoreBy3 () {
    this.setState( (state) => ({score : state.score + 1}) ),
    this.setState( (state) => ({score : state.score + 1}) ),
    this.setState( (state) => ({score : state.score + 1}) )
  }
}
const Justice = new User();

这里同样引用 Dan 的一句好来说明 functional setState 的意义『多次调用 setState 时使用函数作为参数更加保险,React 会将所有的更新组成一个队列,然后按照他们调用的顺序来执行』。这样就避免了将 state 合并成一个对象的问题。

此外,文中还提到了一个使用 functional setState 的最佳实践,将这些用于处理状态的纯函数放到组件类的外部。



这样更有助于组件的关注度分离,组件不需要在去关心如何更新状态的问题,它需要做的就是表达需要更新的意愿。当然还有一个好处就是这些纯函数很方便开发者做测试。



3. 精读

更新状态方式

setState 接收 2 种不同的参数类型,分别是对象和匿名函数。

对象作为参数时,React 内部会以一种对象合并的方式来批量更新组件的状态,就和我们所熟知的 Object.assign() 执行的结果类似,这种方式在状态更新是很好的做到了节流。但这种异步的 state 也就带来了另一个问题,没法使用当前的 state 来计算新的 state 值。

函数作为参数时,则是将所有的更新组成一个队列,然后按照他们调用的顺序来执行:

...
this.updater.enqueueSetState(this, partialState, callback, 'setState');
...
queue.push(partialState);
enqueueUpdate(internalInstance);

这样也就保障了执行队列中每一个函数时都能拿到最新的状态,用于计算新的状态。

分离 action

将组件的 action 分离,这样组件内部就不需要去关心状态的更新方式。这样的实践更偏向于命令式。在更加复杂的组件设计中,可以更大程度的提升组件的复用性,同时保障了组件代码的纯粹。

import {increaseScore} from "../stateChanges";
class User{
  ...
 // inside your component class
  handleIncreaseScore () {
    this.setState( increaseScore)
}
 ...
}

4. 总结

并不是所有的场景都适合使用 functional setState 方式。如果需要处理异步的 state 确实会可以获取到之前的 state 并在其基础上进行操作,这样更加的安全。如果需要的只是简单的对象合并,那继续选择对象参数的 setState 方式也是无可厚非。至于纯函数的测试以及分离组件的 action 也是只 functional setState 的加分项。

编辑于 2017-08-14

文章被以下专栏收录