博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
React源码分析 - 组件更新与事务
阅读量:7060 次
发布时间:2019-06-28

本文共 8091 字,大约阅读时间需要 26 分钟。

在React中,组件的更新本质上都是由setState操作改变state引起的。因此组件更新的入口在于setState,同样经过撸源码和打断点分析画了以下的组件更新的流程图:

setState的定义在组件mountComponent的时候定义:

inst = new Component(publicProps, publicContext, ReactUpdateQueue);复制代码
function ReactComponent(props, context, updater) {  this.props = props;  this.context = context;  this.refs = emptyObject;  this.updater = updater || ReactNoopUpdateQueue;}ReactComponent.prototype.setState = function (partialState, callback) {  this.updater.enqueueSetState(this, partialState);  if (callback) {    this.updater.enqueueCallback(this, callback, 'setState');  }};复制代码

所以setState的真正的定义在 ReactUpdateQueue.js

enqueueSetState: function (publicInstance, partialState) {  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');  if (!internalInstance) {    return;  }  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);  // 将state添加到对应的component的_pendingStateQueue数组中。  queue.push(partialState);  enqueueUpdate(internalInstance);}enqueueCallback: function (publicInstance, callback, callerName) {  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);  if (!internalInstance) {    return null;  }  // 将callback添加到对应的component的_pendingCallbacks数组中。  if (internalInstance._pendingCallbacks) {    internalInstance._pendingCallbacks.push(callback);  } else {    internalInstance._pendingCallbacks = [callback];  }  enqueueUpdate(internalInstance);}复制代码

两个方法最后都调用enqueueUpdate:

function enqueueUpdate(internalInstance) {  ReactUpdates.enqueueUpdate(internalInstance);}function enqueueUpdate(component) {  // 确认需要的事务是否注入了。  ensureInjected();  // batchingStrategy.isBatchingUpdates为false的时候,  // 或者说当不处于批量更新的时候,用事务的方式批量的进行component的更新。  if (!batchingStrategy.isBatchingUpdates) {    batchingStrategy.batchedUpdates(enqueueUpdate, component);    return;  }  // 当处于批量更新阶段时,不进行state的更新操作,而是将需要更新的component添加到dirtyComponents数组中  dirtyComponents.push(component);}复制代码

这里需要注意enqueueUpdate中根据batchingStrategy.isBatchingUpdates分别进入不同的流程,当isBatchingUpdates为true的时候表示已经处于批量更新的过程中了,这时候会将所有的有改动的组件push到dirtyComponents中。当isBatchingUpdates为false的时候会执行更新操作,这里先认为当isBatchingUpdates为false的时候进行的操作是更新组件,实际上的过程是更复杂的,稍后马上解释具体的过程。这里我们先理解下React中的事务的概念,事务的概念根据源码中的注释就可以非常清楚的了解了:

*                       wrappers (injected at creation time) *                                      +        + *                                      |        | *                    +-----------------|--------|--------------+ *                    |                 v        |              | *                    |      +---------------+   |              | *                    |   +--|    wrapper1   |---|----+         | *                    |   |  +---------------+   v    |         | *                    |   |          +-------------+  |         | *                    |   |     +----|   wrapper2  |--------+   | *                    |   |     |    +-------------+  |     |   | *                    |   |     |                     |     |   | *                    |   v     v                     v     v   | wrapper *                    | +---+ +---+   +---------+   +---+ +---+ | invariants * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|--------> *                    | |   | |   |   |         |   |   | |   | | *                    | |   | |   |   |         |   |   | |   | | *                    | |   | |   |   |         |   |   | |   | | *                    | +---+ +---+   +---------+   +---+ +---+ | *                    |  initialize                    close    | *                    +-----------------------------------------+复制代码

简单来说当使用transaction.perform执行方法method的时候会按顺序先执行WRAPPER里面的initialize方法然后执行method最后再执行close方法。

React提供了基础的事务对象Transaction,不同的事务的区别就在于initialize和close方法的不同,这个可以通过定义getTransactionWrappers方法来传入WRAPPER数组,具体的用法看下源码就好了,不过实际使用中是不会要自己去定义事务的,当然要的话也阻止不了~。

回到enqueueUpdate,其调用的batchingStrategy.batchedUpdates方法在ReactDefaultBatchingStrategy 中定义了:

var ReactDefaultBatchingStrategy = {  isBatchingUpdates: false,  batchedUpdates: function (callback, a, b, c, d, e) {    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;    ReactDefaultBatchingStrategy.isBatchingUpdates = true;    if (alreadyBatchingUpdates) {      callback(a, b, c, d, e);    } else {      transaction.perform(callback, null, a, b, c, d, e);    }  }};复制代码

可以看到isBatchingUpdates的初始值是false的,在调用batchedUpdates方法的时候会将isBatchingUpdates变量设置为true。然后根据设置之前的isBatchingUpdates的值来执行不同的流程。

对于enqueueUpdate的效果就是,当执行enqueueUpdate的时候如果isBatchingUpdates为true的话(已经处于批量执行操作),则不会进行更新操作,而是将改动的component添加到dirtyComponents数组中;如果isBatchingUpdates为false的话,会执行batchedUpdates将isBatchingUpdates置为true然后调用enqueueUpdate方法,这个时候会用事务的方式来执行enqueueUpdate。

根据流程图可以知道,事务ReactDefaultBatchingStrategyTransaction的initialize是foo没有任务操作,接着会执行method即:将改动的组件push到dirtyComponent中,最后执行close方法执行flushBatchedUpdate方法再把isBatchingUpdates重置为false。在flushBatchedUpdates方法中事务执行runBatchedUpdates方法将dirtyComponent中的component依次(先父组件在子组件的顺序)进行更新操作。这里具体的更新的过程看流程图就可以理解了,需要注意的是updateChildren方法这个方法是virtual DOM的Diff算法的核心代码,作用就是根据更新前后组件的不同进行有效的更新,具体的部分,之后单独的文章再介绍。

在更新的过程中需要注意的一个方法是_processPendingState方法:

_processPendingState: function (props, context) {   var inst = this._instance;   var queue = this._pendingStateQueue;   var replace = this._pendingReplaceState;   this._pendingReplaceState = false;   this._pendingStateQueue = null;   if (!queue) {     return inst.state;   }   if (replace && queue.length === 1) {     return queue[0];   }   var nextState = _assign({}, replace ? queue[0] : inst.state);   for (var i = replace ? 1 : 0; i < queue.length; i++) {     var partial = queue[i];     _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);   }   return nextState; }复制代码

可以看到当setState传入的是函数的时候,函数被调用的时候的传入的参数是merge了已经遍历的queue的state的nextState,如果传入的不是函数则直接merge state至nextState。这也解释了,为什么用回调函数的形式使用setState的时候可以解决state是按照顺序最新的state了。

从流程图可以看到在保证组件更新完毕后会将setState中传入的callback按照顺序依次push到事务的callback queue队列中,在事务结束的时候close方法中notifyAll就是执行这些callbacks,这样保证了回调函数是在组件完全更新完成后执行的,也就是setState的回调函数传入的state是更新后的state的原因。

在了解了以上的组件更新的流程后,可以看一个场景,栗子如下:

import React from 'react';import ReactDOM from 'react-dom';import App from './App';ReactDOM.render(
, document.getElementById('root'));import React, { Component } from 'react';import Hello from './Hello';class App extends Component { constructor(props) { super(props); this.state = { appText: 'hello App', helloText: 'heiheihei' }; } handleAppClick = () => { console.log('App is clicked ~'); this.setState({ appText: 'App is clicked ~' }); } render() { const { appText, helloText } = this.state; console.log('render App'); return (
{appText}
); }}export default App;import React, { Component } from 'react';class Hello extends Component { constructor(props) { super(props); this.state = { text: 'hello Hello' }; } componentWillReceiveProps(nextProps) { this.setState({ text: nextProps.text + '~' }); } handleClick = () => { this.setState({ text: 'Hello is clicked ~' }); this.props.handleAppClick(); } render() { const { text } = this.state; console.log('render Hello'); return (
{text}
); }}export default Hello;复制代码

点击

hello Hello复制代码

后组件的渲染如下,可以看到父组件到子组件按顺序更新了一次:

render Apprender Hello复制代码

而不是:

render Hellorender Apprender Hello复制代码

批量更新的时候组件的顺序由:

dirtyComponents.sort(mountOrderComparator);复制代码

处理的。

到这里你需要知道这个结果产生的原因在于不是只有setState的调用栈会改变isBatchingUpdates的值

回顾《React事件机制》的流程图可以知道事件的统一回调函数dispatchEvent调用了ReactUpdates.batchedUpdates用事务的方式进行事件的处理,也就是说点击事件的处理本身就是在一个大的事务中,在setState执行的时候isBatchingUpdates已经是true了,setState做的就是将更新都统一push到dirtyComponents数组中,在事务结束的时候按照上述的流程进行批量更新,然后将批量执行关闭结束事务。

事务的机制在这里也保证了React更新的效率,此外在更新组件的时候的virtual DOM的Diff算法也起到很大的作用,这个在后续的文章再介绍。

参考资料

转载地址:http://abfll.baihongyu.com/

你可能感兴趣的文章
dedecms 模型新添加的自定义字段设置样式
查看>>
linux 下文件夹的复制、覆盖以及确认问题解决
查看>>
NETAPP E-Series 使用简单配置
查看>>
Hadoop tutorial - 1 - 环境搭建 2015-3-17
查看>>
编写高质量代码改善java程序的151个建议——[52-57]String !about Strin
查看>>
Java下拼接执行动态SQL语句
查看>>
JavaScript的Function类型
查看>>
Linux centos6.8与7.2在虚拟机上安装后配置成实验环境命令及变化对比
查看>>
WindowsServer2012R2安装Office2010和Visual Studio2010
查看>>
搭建论坛的几种方法
查看>>
使用Jxls操作Excel的方式,针对excel中的数据为List/Map,或非List组合的操作
查看>>
pg_bulkload批量载入工具(初探)
查看>>
Unity3d 失去获取焦点,暂停
查看>>
程控交换机程序故障处理
查看>>
PRTG流量监控基本安装和使用方法
查看>>
KVM虚拟机在CentOS6.4上的应用
查看>>
Android px、dp、sp之间相互转换
查看>>
使用XFire调用Web服务 测式 案例!
查看>>
使用zabbix监控HAProxy的状态信息
查看>>
Linux命令之date
查看>>