博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
源码看React setState漫谈(二)
阅读量:6266 次
发布时间:2019-06-22

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

前面写过一篇谈论了用户操作到页面渲染的过程,相信大家对React的setState机制有了一定了解。这里我们看看setState在生命周期的各个流程里调用都会发生什么。

更新流程

结论:

  1. componentWillReceiveProps中安心调用,对state的改变会被合并,并且只刷新一次。
  2. componentShouldUpdate,componentWillUpdate,render,componentDidUpdate中,可以调用,但是容易导致死循环,所以要做好条件判断。当然,如果非调用不可,官方只建议在componentDidUpdate中调用。

componentWillReceiveProps中调用setState

还是先放上流程图:

图片描述

首先我们知道React是在ReactUpdatesFlushTransaction事务中进行更新操作的。

该事务perform之前会先报错dirtyComponents的length。
perform之后会将this.dirtyComponents长度与之前保存的进行对比。如果长度不一样,表示在这一轮更新中有setState被触发,新的dirtyComponent被加入序列。然后删除这一轮更新的dirtyComponent ,重新flushBatchedUpdates更新新加入的。
源码如下:

var NESTED_UPDATES = {  initialize: function() {    this.dirtyComponentsLength = dirtyComponents.length;  },  close: function() {    if (this.dirtyComponentsLength !== dirtyComponents.length) {      dirtyComponents.splice(0, this.dirtyComponentsLength);      flushBatchedUpdates();    } else {      dirtyComponents.length = 0;    }  },};Object.assign(ReactUpdatesFlushTransaction.prototype, Transaction, {  getTransactionWrappers: function() {    return TRANSACTION_WRAPPERS;  },  destructor: function() {   .....  },  perform: function(method, scope, a) {    ......  },});

然后我们看第一次更新都发生了什么?

首先会判断是否需要更新。

performUpdateIfNecessary: function (transaction) {    if (this._pendingElement != null) {      ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);    } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {      this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);    } else {      this._updateBatchNumber = null;    }  },

第一次进来this._pendingStateQueue有值,所以进入更新,调用updateComponent

updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {    var inst = this._instance;    var willReceive = false;    var nextContext;    var prevProps = prevParentElement.props;    var nextProps = nextParentElement.props;    ....    inst.componentWillReceiveProps(nextProps, nextContext);    //将新的state合并到更新队列中,此时nextState为最新的state    var nextState = this._processPendingState(nextProps, nextContext);    var shouldUpdate = true;    if (!this._pendingForceUpdate) {      shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);      }     this._updateBatchNumber = null;    if (shouldUpdate) {      this._pendingForceUpdate = false;      this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);    }},

我们看到进入updateComponent,然后执行componentWillReceiveProps,

componentWillReceiveProps会调用setState方法。
图片描述
setState,更新了pendIngStateQueue,更新了dirtyComponents。然后接着走updateComponent
我们看到执行了

var nextState = this._processPendingState(nextProps, nextContext);_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);    return nextState;  },

可以看到这里根据pendingStateQueue,更新了state并赋给了nextState,同时删除了pendingStateQueue

接下来就是componentShouldUpdate,componentWillUpdate,render,componentDidUpdate这些生命周期函数。perform执行完毕。

接着是transaction的close方法。上面我们已经介绍过,因为updateComponent内部调用setState,导致dirtyComponent变了,因此又执行一轮flushBatchedUpdates
接着又到了判断的逻辑

performUpdateIfNecessary: function (transaction) {    if (this._pendingStateQueue !== null || this._pendingForceUpdate) {      this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);    } else {      this._updateBatchNumber = null;    }  },

因为上面_pendingStateQueue已经被删除,所以这次是不会触发新一轮的update.

coponentShouldUpdate,componentWillUpdate,render和componentDidUpdate

这几个和componentWIllReceiveProps有什么区别?最大的区别就在于。他们都是在_processPendingState方法之后调用的。

其他逻辑差不多,调用setState更新了pendIngStateQueue,更新了dirtyComponents....
但是这里会到updateComponent方法以后没有删除‘pendingStateQueue’!
所以在close方法中,执行新一轮flushBatchedUpdates时,再次判断performUpdateIfNecessary是需要更新的,因此又会触发循环。这就造成了死循环!

细节补充

也许有人会问为什么setState对_pendingStateQueue的更新会同步到ReactCompositeComponent里面。那我们就来看看

首先,_pendingStateQueue来自enqueueReplaceState

var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);internalInstance._pendingStateQueue = [completeState];internalInstance._pendingReplaceState = true;

其实这里的internalInstance就是ReactElement对象。一路跟着代码跟到ReactReconciler,一直到

internalInstance.performUpdateIfNecessary(transaction);

可我们发现internalInstance并没有performUpdateIfNecessary方法啊,其实这是定义在原型上的方法。我们在instantiateReactComponent中发现了端倪:

Object.assign(  ReactCompositeComponentWrapper.prototype,  ReactCompositeComponent,  {    _instantiateReactComponent: instantiateReactComponent,  },);

所以,composite的调用者就是internalInstance,也就是我们在调用栈里传来传去的component,而我们从头到尾维护的也是internalInstance的属性

加载流程

React将组件分为三大类:

  • ReactEmptyComponent 空组件
  • ReactHostComponent 对原生HTML标签的封装
  • ReactCompositeComponent 用户自定义组件

首先明确一点,一般操作componentWillMount和componentDidMount操作的都是自定义组件。所以这边就主要看看自定义组件的加载流程。具体代码见源码的ReactCompositeComponent的mountComponent方法。

mountComponent: function(    transaction,    hostParent,    hostContainerInfo,    context,  ) {    ...    var doConstruct = shouldConstruct(Component);    var inst = this._constructComponent(      doConstruct,      publicProps,      publicContext,      updateQueue,    );    var renderedElement;        ...        ReactInstanceMap.set(inst, this);    ...    this._pendingStateQueue = null;    this._pendingReplaceState = false;    this._pendingForceUpdate = false;    ...    inst.componentWillMount();           if (this._pendingStateQueue) {        inst.state = this._processPendingState(inst.props, inst.context);      }    }    var markup;    markup = this.performInitialMount(        renderedElement,        hostParent,        hostContainerInfo,        transaction,        context,      );     transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);      const callbacks = this._pendingCallbacks;    if (callbacks) {      this._pendingCallbacks = null;      for (let i = 0; i < callbacks.length; i++) {        transaction.getReactMountReady().enqueue(callbacks[i], inst);      }    }    return markup;  },

大概的流程图为:

图片描述

constructor,componentWillMount

constructor

可以看到执行constructor之后才将实例存入ReactInstanceMap并且初始化_pandingStateQueue

如果这时候调用setState,当进去ReactStateQueue时,

var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);    if (!internalInstance) {      return;    }

尝试从ReactInstanceMap取实例时将取不到任何值,这将导致直接返回。所以结果只会是,不但不会触发重新渲染操作(当然这时候也没东西可以重新渲染),而且,state赋值也失败。

这就是为什么contructor里面给state赋值时直接写

this.state = {...}

就可以了。

componentWillMount

从源码中可以发现componentWillMount的下一步就进行了

this._processPendingState(inst.props, inst.context);

这个方法前面说活,就是合并了state的值。并将_pendingStateQueue序列设为null.然后结果嘛当然是和componentWillUpdate一样,不会触发多次渲染。

这里有一点不一样,请注意,这边是直接把合并过后的state赋给了inst.state。也就是说compnentWillMount过后的操作state里面的值已经是最新的了。

componentDidMount

和componentDidUpdate一样的原因,也会触发重新渲染。

当然,mount操作都是只执行一次,就算重新渲染也是走的更新流程,所以可以放心使用,不会造成更新流程中的死循环问题。

是什么造成的重新渲染。

放一个更大的流程图

图片描述
看到了吧,再判断组件类型之后,就会开启一个事务进行加载。而这也是老熟人了ReactDefalutBatchingStragy。这个transaction在close方法中会执行flushBatchedUpdates
想不起来的可以回过头看看上面的更新流程。

至此,生命周期中对state的操作讲解完毕

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

你可能感兴趣的文章
SQL中 patindex函数的用法
查看>>
Vmware 虚拟机无法启动
查看>>
LeetCode: Partition List 解题报告
查看>>
如何查看Python对象的属性
查看>>
你所需要知道的一些git 的使用命令:历史
查看>>
mysql explain输出中type的取值说明
查看>>
iPhone开发之 - 苹果推送通知服务(APNs)编程
查看>>
linux下so动态库一些不为人知的秘密(上)
查看>>
文本框设置只读,后台可获取
查看>>
JAVA:URL之String组件
查看>>
架构,改善程序复用性的设计~目录(附核心原代码)
查看>>
逆向反汇编代码推算C++的局部变量
查看>>
100个推荐的图片/内容滑动条
查看>>
秋式广告杀手:广告拦截原理与杀手组织
查看>>
内存溢出
查看>>
如何重启IIS进程
查看>>
分享一个javascript alert精简框架
查看>>
【解决方法】System.IO.FileNotFoundException
查看>>
Android 命令行编译、打包生成apk文件
查看>>
java中解决组件重叠的问题(例如鼠标移动组件时)
查看>>