React学习之setState的实现机制

        在react中,通过管理状态来实现对组件的管理,通过this.state()来访问state,通过this.setState方法来更新state,当this.setState方法被调用时,react会重新调用render来重新渲染UI。

一、setState的全部实现过程:

        1、enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component

        2、如果组件当前正处于update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。

         3、batchedUpdates发起一次transaction.perform()事务

         4、开始执行事务初始化,运行,结束三个阶段

            初始化:事务初始化阶段没有注册方法,故无方法要执行

            运行:执行setSate时传入的callback方法,一般不会传callback参数

            结束:更新isBatchingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法

          5、FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks, 也就是setState中设置的callback。

二、setState异步更新

源码:

//将新的state合并到状态更新队列中

var nextState = this._processPendingState(nextProps,nextContext);

//根据更新队列和 shouldComponentUpdate的状态来判断是否需要更新组件

var shouldUpdate = this._pendingForceUpdate || ! inst. shouldComponentUpdate || inst. shouldComponentUpdate(nextProps, nextState, nextContext)

        注意:如果不通过setState而直接修改this.state的值,而是诸如这样: this.state.value = 1,那么该state将不会被放入状态队列中,下次调用this.setState并对状态队列进行合并时,将会忽略之前直接别修改的state,因此我们应该用setState更新state的值。

三、setState的循环调用

        React在setState之后,会经对state进行diff,判断是否有改变,然后去diff dom决定是否要更新UI。如果这一系列过程立刻发生在每一个setState之后,就可能会有性能问题。

        当调用setState时,实际上会执行enqueueSetState方法,并对partialState以及_pendingStateQueue更新队列进行合并,最终通过enqueueUpdate执行state更新。

        而performUpdateIfNecessary方法获取_pendingElement、_pendingStateQueue、_pendingForceUpdate,并调用 reciveComponent 和updateComponent方法进行组件更新。

        在短时间内频繁setState。React会将state的改变压入栈中,在合适的时机,批量更新state和视图,达到提高性能的效果。

        注意:setState不能在shouldComponentUpdate或componentWillUpdate中调用setState,则会造成循环调用,将浏览器的内存占满后崩溃。

四、事务 transaction


transaction的运行过程

        · transaction的使用场景:

        1、在一次 DOM reconciliation(调和,即 state 改变导致 Virtual DOM 改变,计算真实 DOM 该如何改变的过程)的前后,保证 input 中选中的文字范围(range)不发生变化。

        2、当 DOM 节点发生重新排列时禁用事件,以确保不会触发多余的 blur/focus 事件。同时可以确保 DOM 重拍完成后事件系统恢复启用状态。

        3、当 worker thread 的 DOM reconciliation 计算完成后,由 main thread 来更新整个 UI

在渲染完新的内容后调用所有 componentDidUpdate 的回调

        · 事务通过wrapper进行封装。

        1、一个wrapper包含一对initialize和close方法。比如RESET_BATCHED_UPDATES

var RESET_BATCHED_UPDATES = {

  // 初始化调用

  initialize: emptyFunction,

  // 事务执行完成,close时调用

  close: function () {

    ReactDefaultBatchingStrategy.isBatchingUpdates = false;

  }

};

        2、transation 被包装在wrapper中,比如

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

        transaction是通过transaction.perform(callback, args…)方法进入的,它会先调用注册好的wrapper中的initialize方法,然后执行perform方法中的callback,最后再执行close方法。

五、总结

        在react中,根绝setState调用栈的不同,我们可以将它划分为两类,一类是在componentDidMount中,一类是在setTimeOut中。


这是一个例子:

class Example extends React.Component{

  constructor() {

    super();

    this.state = {

      val: 0    };

  }

    componentDidMount() {

        this.setState({val: this.state.val + 1});

        console.log(this.state.val);        // 第 1 次 打印  0

        this.setState({val: this.state.val + 1});

        console.log(this.state.val);        // 第 2 次 打印  0   

         setTimeout(() => {

              this.setState({val: this.state.val + 1});

              console.log(this.state.val);      // 第 3 次 打印  2      

              this.setState({val: this.state.val + 1});

              console.log(this.state.val);      // 第 4 次 打印  3   

     }, 0);

  }

  render() {

    return null;

  }

};


代码运行过程:

this.setState(newState) ——newState存入_pendingStateQueue —— 是否处于batch update中

处于batch update中: component保存在dirtyComponents中  

不处于batch update中:遍历dirtyComponents、调用updateComponent、更新state


        在componentDidMount中调用setState时,batchingStrategy的isBatchingUpdates已经被设置为true了,所以setState的结果并没有立即生效,而是被放进了dirtyComponents中。所以前两次打印this.state.val都是0,因为新的state还没被应用到组件中。

         setTimeOut中的两次setState,因为没有前置的batchedUpdate调用,所以batchingStrategy的isBatchingUpdates标志位是false,也就导致了新的state马上生效,没有走到dirtyComponents分支。也就是说,setTimeOut中的第一次执行,setState时,this.state.val为1,而setState完成后打印时this.state.val变成了2。第二次的setState同理,打印结果为3。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容