博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
知根知底setState
阅读量:6228 次
发布时间:2019-06-21

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

setState作为react中使用最频繁的一个API,在这里简单分享它的实现机制。

没错本文是一篇讲源码的文章,但尽量避免做代码的搬运工,根据setState的使用场景进行解析,源码基于react v16.4.3-alpha.0

一,预备知识

1,fiber

网上有很多讲解fiber的文章大多在描述fiber的算法。实际上fiber包含数据结构和算法,按照v16之前的版本理解,fiber在源码中表示虚拟DOM的一个节点

2,react的事件系统

对react有一定了解的同学肯定知道react封装了一套自己的事件系统,<div onClick={handleClick}></div>并不是像vue一样调用addEventListener绑定事件到对应的节点上,而是通过事件委托的方式绑定到document上了

接下来我们简单来看实现过程

任何通过react渲染得到的DOM树中随意选取一个节点都会有__reactEventHandlers这个属性

在这个对象中我们可以找到在JSX中为这个标签添加的事件属性,也就是可以拿到onClick

有了上面的知识我们看一下react事件系统的简易过程

  • 点击一个按钮触发document上的click事件
  • 获得事件对象event
  • 通过event.target可以知道是点击的那个按钮
  • 拿到按钮上面的__reactEventHandlers
  • 然后就有了onClick
// 伪代码documnet.addEventListener('click', function(event){    const target = event.target    const onClick = traget.__reactEventHandlers*****.onClick        // isBatchingUpdates全局变量后面会具体讲解到    var previousIsBatchingUpdates = isBatchingUpdates;    isBatchingUpdates = true;        try {        // 执行事件回调        return onClick(event);    } finally {        isBatchingUpdates = previousIsBatchingUpdates;        performSyncWork()    }})复制代码

这里只是简要描述,实际实现要复杂很多

二,走一遍源码

1,setState实现

Component.prototype.setState = function(partialState, callback) {  this.updater.enqueueSetState(this, partialState, callback, 'setState');};复制代码

2,this.updater

this.updater 是在哪个地方进行赋值的我们暂时不用关心,只需要知道他被赋值为classComponentUpdater

3,classComponentUpdater

我们只需关心生成了update,插入到update队列,然后调用scheduleWork

// 伪代码const classComponentUpdater = {  ...  enqueueSetState(inst, payload, callback) {    const update = createUpdate(expirationTime);    // setState(payload, callback);    update.payload = payload;    update.callback = callback;        // 插入到update队列    enqueueUpdate(fiber, update);        scheduleWork(fiber, expirationTime);  },  ...复制代码

4,scheduleWork

这一步我们只需关心下面的这一段逻辑

// isWorking、isCommitting是全部变量,在后面我们会具体分析到if (    !isWorking ||    isCommitting ||    nextRoot !== root  ) {    const rootExpirationTime = root.expirationTime;    requestWork(root, rootExpirationTime);  }复制代码

5,requestWork

function requestWork(root, expirationTime) {  // 将根节点添加到调度任务中  addRootToSchedule(root, expirationTime)    // isRendering是全局变量,在后面我们会具体分析到  if (isRendering) {    return;  }    // isBatchingUpdates、isUnbatchingUpdates是全局变量  // 在第一节了解react事件时有对他们进行重新赋值  if (isBatchingUpdates) {    if (isUnbatchingUpdates) {      ....      performWorkOnRoot(root, Sync, false);    }    return;  }    if (expirationTime === Sync) {    performSyncWork();  } else {    scheduleCallbackWithExpirationTime(root, expirationTime);  }}复制代码

好了,要了解setState的过程,追踪到这五步就可以了,下面会结合具体场景来对这整个过程具体分析

三,使用场景

1,交互事件

handleClick(){    this.setState({        name: '吴彦祖'    })    console.log(this.state.name) // >> 狗蛋    this.setState({        age: '18'    })    console.log(this.state.age) // >> 40}复制代码

第一节中了解到在执行事件回调handleClick前isBatchingUpdates = true,滚动到看第二节的源码过程,最终在第五步requestWork中会执行

function requestWork(){    ...    if (isBatchingUpdates) {        return    }    ...}复制代码

第一个setState也就到此为止被return,接着执行第二个setState同样到这一步为止。

现在我们能知道什么呢?

在交互事件中的setState每次执行只是创建了一个新的update,然后添加到enqueueUpdate,setState并没有直接触发react的update

再回头看第一节中react的事件过程,当handleClick执行后会立马调用performWork开始react的update过程

理一下整个过程,交互事件中的因为isBatchingUpdates = true会先收集所有的update到enqueueUpdate中,交互事件回调执行完后再调用performWork一次更新所有的state

现在来思考一个问题,setState是异步的?

从源码可以看到这整个过程对浏览器来说都是同步的,一步一步顺序执行;对于开发者来说,执行setState后因为要进行批处理操作,而延后了react的更新

2,setTimeout、setInterval、Promise中

在1中我们知道因为isBatchingUpdates = true的原因执行setState后无法直接拿到新的state,如果我们可以避免isBatchingUpdates的问题结果又会怎样

handleClick(){   setTimeout(() => {       this.setState({           name: '吴彦祖'       })       console.log(this.state.name) // >> 吴彦祖       this.setState({           age: '18'       })       console.log(this.state.age) // >> 18   })}复制代码

通过setTimeout执行setState,也就没有react的事件系统什么事了, isBatchingUpdates的默认为false,看第二节第五步,每次setState都会执行performSyncWork触发react的update,所以每次调用setState紧接着我们就能拿到最新的state

通过在setTimeout中执行setState我们达到了setState是同步的效果,当然通过setInterval、Promise也能达到同样的效果。

3,componentWillUpdate (render前生命周期)

先补充一个知识点

在第二节源码中可以注意到三个全局变量:isRenderingisWorkingisCommitting

v16中react更新有两个阶段reconciler和commit阶段

  • isRendering:开始react更新就为true
  • isWorking:进入reconciler阶段就为true、进入commit阶段就为true
  • isCommitting:进入commit阶段就为true

render前生命周期属于reconciler阶段:isRendering = trueisWorking = true

触发第二节第五步:

function requestWork(){    ...    if (isRendering) {        return    }    ...}复制代码

render前生命周期不会触发新的更新,只是将新的update添加到enqueueUpdate尾部,在当前更新任务中处理

4,componentDidUpdate (render后生命周期)

render后生命周期属于commit阶段:isRendering = trueisWorking = trueisCommitting = true

同样会触发第二节第五步:

function requestWork(){    addRootToSchedule(root, expirationTime)    if (isRendering) {        return    }    ...}复制代码

render后生命周期不会立即触发新的更新,当然也不会在本次更新任务中处理,这里我们注意有一个addRootToSchedule(root, expirationTime),将新的更新作为下一个更新任务

例:

修改name触发componentDidUpdate()componentDidUpdate修改age

过程: 修改name开始react的update过程完成reconciler和commit阶段,因为任务中还有一个修改age的任务,再次开始react的update过程完成reconciler和commit阶段

注意:在componentDidUpdate使用setState可能会造成死循环

结尾

react本人用的不是很多,结合官方文档暂时只能想到上述四种场景。为仅讲解setState文中刻意省略了fiber相关的过程,后面有机会会有fiber相关的分享。有什么建议欢迎在下面留言交流。

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

你可能感兴趣的文章
JQuery中 数组与字符串(过滤,排序,拆分,合并)
查看>>
pycharm 设置
查看>>
js添加事件
查看>>
模式识别开发之项目---基于人头检测的人流量监测
查看>>
嵌入式开发之优化---代码优化
查看>>
题解 P1665 【正方形计数】
查看>>
python 字典 get方法
查看>>
支付宝9张图稳扫出敬业福
查看>>
GBDT记录
查看>>
同一个菜品商家中心和erp价格显示不一致解决方案FAQ
查看>>
新博客
查看>>
中文前端UI框架Kit(十一)摇头动画?让你的页面元素嗑药嗑起来??
查看>>
2018-2019-2 20162329 《网络对抗技术》Exp7: 网络欺诈防范
查看>>
今日随笔:scrollTop与overflow
查看>>
分分钟用上C#中的委托和事件
查看>>
[示例]NSDictionary-数组中增加字典,并遍历数组
查看>>
开机一会,出现长时间闪屏,并且跳出SendRpt error
查看>>
Journal List
查看>>
selenium-控制浏览器操作
查看>>
leetcode — recover-binary-search-tree
查看>>