资讯专栏INFORMATION COLUMN

React中setState真的是异步的吗

hankkin / 1352人阅读

摘要:在学习的过程中几乎所有学习材料都会反复强调一点是异步的来看一下官网对于的说明。将认为是一次请求而不是一次立即执行更新组件的命令。总结在组件生命周期中或者事件绑定中,是通过异步更新的。在延时的回调或者原生事件绑定的回调中调用不一定是异步的。

在学习react的过程中几乎所有学习材料都会反复强调一点setState是异步的,来看一下react官网对于setState的说明。

setState()认为是一次请求而不是一次立即执行更新组件的命令。为了更为可观的性能,React可能会推迟它,稍后会一次性更新这些组件。React不会保证在setState之后,能够立刻拿到改变的结果。

一个很经典的例子如下

// state.count 当前为 0
componentDidMount(){
    this.setState({count: state.count + 1});
    console.log(this.state.count)
}

如果你熟悉react,你一定知道最后的输出结果是0,而不是1。

然而事实真的是这样吗?

我们再来看一个例子

class Hello extends Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }
  render() {
    return 
点我
; } componentDidMount() { //手动绑定mousedown事件 ReactDom.findDOMNode(this).addEventListener( "mousedown", this.onClick.bind(this) ); //延时调用onclick事件 setTimeout(this.onClick.bind(this), 1000); } onClick(event) { if (event) { console.log(event.type); } else { console.log("timeout"); } console.log("prev state:", this.state.counter); this.setState({ counter: this.state.counter + 1 }); console.log("next state:", this.state.counter); } } export default Hello;

在这个组件中采用3中方法更新state

在div节点中绑定onClick事件

在componentDidMount中手动绑定mousedown事件

在componentDidMount中使用setTimeout调用onClick

你可以猜到结果吗?输出结果是:

timeout
"prev state:"
0
"next state:"
1
mousedown
"prev state:"
1
"next state:"
2
click
"prev state:"
2
"next state:"
2

结果似乎有点出人意料,三种方式只有在div上绑定的onClick事件输出了可以证明setState是异步的结果,另外两种方式显示setState似乎是同步的。

React的核心成员Dan Abramov也在一次回复中提到

这到底是这么回事?

话不多说,直接上源码,如果你对react源码有一定了解可以接着往下看,如果没有,可以直接跳到结论(以下分析基于react15,16版本可能有出入)。

setState异步的实现
在componentWillMount中调用setState
//代码位于ReactBaseClasses
 * @param {partialState} 设置的state参数
 * @param {callback} 设置state后的回调
ReactComponent.prototype.setState = function(partialState, callback) {
  invariant(
    typeof partialState === "object" ||
      typeof partialState === "function" ||
      partialState == null,
    "setState(...): takes an object of state variables to update or a " +
      "function which returns an object of state variables.",
  );
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, "setState");
  }
};

setState中调用了enqueueSetState方法将传入的state放到一个队列中,接下来,看下enqueueSetState的具体实现:

//代码位于ReactUpdateQueue.js
   * @param {publicInstance} 需要重新渲染的组件实例
   * @param {partialState} 设置的state
   * @internal
  enqueueSetState: function(publicInstance, partialState) {
      //省略部分代码

      //从组件列表中找到并返回需渲染的组件
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      "setState",
    );

    if (!internalInstance) {
      return;
    }

    //state队列
    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    //将新的state放入队列
    queue.push(partialState);

    enqueueUpdate(internalInstance);
  },

enqueueSetState中先是找到需渲染组件并将新的state并入该组件的需更新的state队列中,接下来调用了enqueueUpdate方法,接着来看:

//代码位于ReactUpdateQueue.js
function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}
//代码位于ReactUpdates.js
function enqueueUpdate(component) {
  ensureInjected();

  // Various parts of our code (such as ReactCompositeComponent"s
  // _renderValidatedComponent) assume that calls to render aren"t nested;
  // verify that that"s the case. (This is called by each top-level update
  // function, like setState, forceUpdate, etc.; creation and
  // destruction of top-level components is guarded in ReactMount.)

  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

这段代码就是实现setState异步更新的关键了,首先要了解的就是batchingStrategy,顾名思义就是批量更新策略,其中通过事务的方式实现state的批量更新,这里的事务和数据库中的事务的概念类似,但不完全相同,这里就不具体展开了,有时间可以具体写下,是react中十分重要也是很有意思的内容。
isBatchingUpdates是该事务的一个标志,如果为true,表示react正在一个更新组件的事务流中,根据以上代码逻辑:

如果没有在事务流中,调用batchedUpdates方法进入更新流程,进入流程后,会将isBatchingUpdates设置为true。

否则,将需更新的组件放入dirtyComponents中,也很好理解,先将需更新的组件存起来,稍后更新。

这就解释了在componentDidMount中调用setState并不会立即更新state,因为正处于一个更新流程中,isBatchingUpdates为true,所以只会放入dirtyComponents中等待稍后更新。

事件中的调用setState

那么在事件中调用setState又为什么也是异步的呢,react是通过合成事件实现了对于事件的绑定,在组件创建和更新的入口方法mountComponent和updateComponent中会将绑定的事件注册到document节点上,相应的回调函数通过EventPluginHub存储。
当事件触发时,document上addEventListener注册的callback会被回调。从前面事件注册部分发现,此时回调函数为ReactEventListener.dispatchEvent,它是事件分发的入口方法。下面我们来看下dispatchEvent:

dispatchEvent: function (topLevelType, nativeEvent) {
    // disable了则直接不回调相关方法
    if (!ReactEventListener._enabled) {
      return;
    }

    var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);
    try {
      // 放入
      ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
    } finally {
      TopLevelCallbackBookKeeping.release(bookKeeping);
    }
}

看到了熟悉的batchedUpdates方法,只是调用方换成了ReactUpdates,再进入ReactUpdates.batchedUpdates。

function batchedUpdates(callback, a, b, c, d, e) {
  ensureInjected();
  return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

豁然开朗,原来在事件的处理中也是通过同样的事务完成的,当进入事件处理流程后,该事务的isBatchingUpdates为true,如果在事件中调用setState方法,也会进入dirtyComponent流程。

原生事件绑定和setTimeout中setState

在回过头来看同步的情况,原生事件绑定不会通过合成事件的方式处理,自然也不会进入更新事务的处理流程。setTimeout也一样,在setTimeout回调执行时已经完成了原更新组件流程,不会放入dirtyComponent进行异步更新,其结果自然是同步的。

顺便提一下,在更新组建时,将更新的state合并到原state是在componentWillUpdate之后,render之前,所以在componentWillUpdate之前设置的setState可以在render中拿到最新值。

总结

1.在组件生命周期中或者react事件绑定中,setState是通过异步更新的。
2.在延时的回调或者原生事件绑定的回调中调用setState不一定是异步的。

这个结果并不说明setState异步执行的说法是错误的,更加准确的说法应该是setState不能保证同步执行。

Dan Abramov也多次提到今后会将setState改造为异步的,从js conf中提到的suspend新特新也印证了这一点。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93891.html

相关文章

  • 解读react的setSate的异步问题

    摘要:总结在组件生命周期中或者事件绑定中,是通过异步更新的。在延时的回调或者原生事件绑定的回调中调用不一定是异步的。这个结果并不说明异步执行的说法是错误的,更加准确的说法应该是不能保证同步执行。 在我们阅读文档的时候,大多都说react的setState是异步的,可是它真的是异步的吗?如果是,那我们还可以猜想:那可以不可以同步?那什么时候需要异步,什么时候需要同步呢? 我们先来看下reac...

    atinosun 评论0 收藏0
  • 理解reactsetState

    摘要:组件状态是一种持有,处理和使用信息的方式。更新唯一你能直接写的地方应该是组件的构造函数中。是异步的事实上会引起的一致性处理重新渲染组件树的过程,是下一个属性的基础即是异步的。常见错误其中最常见的错误之一就是在构造函数中使用设置的值。 组件状态(state)是一种持有,处理和使用信息的方式。state包含的信息仅作用于一个给定组件的内部,并允许你根据它实现组件的一些逻辑。state通常是...

    FingerLiu 评论0 收藏0
  • Luy 1.0 :一个React-like轮子的诞生

    摘要:司徒正美的一款了不起的化方案,支持到。行代码内实现一个胡子大哈实现的作品其实就是的了源码学习个人文章源码学习个人文章源码学习个人文章源码学习个人文章这几片文章的作者都是司徒正美,全面的解析和官方的对比。 前言 在过去的一个多月中,为了能够更深入的学习,使用React,了解React内部算法,数据结构,我自己,从零开始写了一个玩具框架。 截止今日,终于可以发布第一个版本,因为就在昨天,我...

    codecook 评论0 收藏0
  • 深入React知识点整理(二)

    摘要:承接上文,深入知识点整理一使用也满一年了,从刚刚会使用到逐渐探究其底层实现,以便学习几招奇技淫巧从而在自己的代码中使用,写出高效的代码。有限状态机,表示有限个状态以及在这些状态之间的转移和动作等行为的模型。 承接上文,深入React知识点整理(一)使用React也满一年了,从刚刚会使用到逐渐探究其底层实现,以便学习几招奇技淫巧从而在自己的代码中使用,写出高效的代码。下面整理一些知识点,...

    villainhr 评论0 收藏0

发表评论

0条评论

hankkin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<