React的更新流程

segmentfault上的大佬的文章进行一下总结,想看更详细的建议看这个专栏。

React加入了fiber之后,树的结构发生了改变,用以前的从上至下的单向树,改成了双向链表。也就是Fiber节点中加入了对Parent的引用。

更新流程:
  React的更新分为Render和Commit两个阶段
  React Fiber节点的更新在React的Render阶段,分为beginWork, completeWork两个阶段
  首先,React从上往下遍历fiber树,对于每一个fiber节点触发beginWork阶段,这个阶段会根据setState的调用形成的updateList和本次调度的优先级,来进行state的更新,如果首次发现低于当前优先级的update,会跳过这个update,并且在fiber节点上记录这个被跳过的update之前的所有update计算生成的state,也就是baseState,然后记录firstBaseUpdate为这个跳过的update,记录lastBaseUpdate为updateList的最后一个,用于后续低优先级调度的时候计算完整的state。另外beginWork还会根据旧的fiber树,拦截不需要更新的fiber,并根据旧fiber进行新fiber(这棵新的树叫做Work In Progress树,简称WIP)的构建。构建过程中,会进行一个diff操作。
  上面说到新的state会被计算出来,这时候React会执行一次render,获取到新的ReactElement,然后把新的这些ReactElement和旧的Fiber节点进行对比,这里主要涉及到fiber节点的更新,删除,移动操作。首先,如果两个ReactElement的key + tag(tag就是元素的类型)不一样,那么直接用新节点生成Fiber替换旧节点的Fiber,子树也进行完全重建。然后diff分为两种(以下说的删除和更新Fiber,只是给Fiber打上对应的EffectTag,然后在CompleteWork进行收集):

  1. 如果新的ReactElement只有一个,那么这时候是单元素的diff。这时候找一下旧Fiber节点有没有对应的节点(也就是key + tag相同的节点),如果有对应的节点,那么执行节点更新,并删除旧fiber节点的其他节点,WIP只存在这个对应节点构建的新节点。如果没有对应的节点,删除旧fiber的所有节点,WIP上只存在这个新构建的新节点。
  2. 如果新的ReactElement有多个,那么这时候,我们需要根据key来判断节点有没有移动。对新节点和旧节点进行逐一对比,如果发现相同,判断属性需不需要更新,需要则进行更新,更新完毕构建一个新的Fiber到WIP上。如果发现某一个的key + tag对不上了,那么以上一个相同的节点作为基准节点(用来判断位置),如果后面发现这个对不上的节点只是位置移动了,那么根据基准节点移动位置,同时判断属性是否需要更新,然后构建一个新的Fiber到WIP上。如果新的ReactElement全部遍历完了,但是旧Fiber节点还剩下,表示剩下的旧Fiber已经没用了,那么删除剩下的所有旧Fiber节点。如果旧Fiber节点全部遍历完了,但是还有新的ReactElement,表示有新的元素加入,那么构建新的Fiber到WIP上。

  diff过程中,会根据Fiber的状态打上effectTag,例如被删除的Fiber打上Deletion的EffectTag,之后在completeWork阶段进行收集工作。经过diff之后,新Fiber的形态基本确定,这时候继续深度优先遍历旧Fiber的子树,对每个子节点进行beginWork。
  当深度优先遍历的一个路径上的最后一个叶子节点进入beginWork并结束,开始回溯,让回溯过程中的每一个fiber节点进入completeWork阶段。如果回溯过程中有没进入beginWork的节点,那么接着进入beginWork。
  completeWork阶段,完成的事项有:
  1. 对Dom进行属性更新,或者创建。
  2. 如果Dom有创建,把第一层子Fiber关联到当前Fiber上。
  3. 收集effectList,最后交给Commit阶段处理。
  先来看看1,在completeWork阶段,会根据WIP的新Fiber节点的tag去处理不同的Dom更新,如果是实际的Dom Fiber(在源码中为HostComponent和HostText等),这时候会判断Fiber的Dom是否存在,如果存在,那么执行Dom的更新。如果不存在,那么执行Dom的创建(只是更新的话,新Fiber的Dom来自旧Fiber的Dom,如果是新节点,那么肯定是空)。
  然后是第2点,当对Dom创建完毕之后,会去把这个Fiber的第一层的子节点关联到这个Fiber的Dom的children这里。这里注意的是,如果是自定义组件Fiber,是不会出现关联子节点的,因为自定义组件的Fiber只是用来标记JSX的结构,不会有真实的dom。自定义组件的Fiber就是:<Example /> -> (<div></div>'hhhh'),这时候从'hhhh'的textNode回溯到Example,发现Example是个自定义组件,不需要执行挂载操作,接着找Example的兄弟节点。在从当前Fiber找子节点的过程中,也会跳过自定义组件Fiber,直到找到真实Dom Fiber才会执行关联子节点操作。
  接着是第3点,在对Dom进行更新,创建操作之后,会把当前的Fiber节点标记一个effectTag。源码大概就是(相对于原文章的说法,React更改了名称为flags):

function markUpdate(workInProgress: Fiber) {
  // Tag the fiber with an update effect. This turns a Placement into
  // a PlacementAndUpdate.
  workInProgress.flags |= Update;
}

一旦WIP节点上有了除NoEffects之外的effect的话,表示这个节点需要在commit阶段被处理。每个Fiber节点都有firstEffect和lastEffect这两个指针,表示子节点中需要被处理的节点。如果当前节点没有更新,那么把自己的子节点的effectList并入到自己父节点的effectList中即可。如果当前节点有更新,那么在把自己子节点的effectList并入到自己父节点的effectList之后,再把自己也加入到父节点的effectList中。以此达到effectList收集的目的。从这个过程来看,最终只需要读取根节点的effectList,即可以对整个视图进行更新。贴一下来自大佬文章的代码:

 /*
* effectList是一条单向链表,每完成一个工作单元上的任务,
* 都要将它产生的effect链表并入
* 上级工作单元。
* */
// 将当前节点的effectList并入到父节点的effectList
if (returnFiber.firstEffect === null) {
  returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {
  if (returnFiber.lastEffect !== null) {
    returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
  }
  returnFiber.lastEffect = completedWork.lastEffect;
}

// 将自身添加到effect链,添加时跳过NoWork 和
// PerformedWork的effectTag,因为真正
// 的commit用不到
const effectTag = completedWork.effectTag;

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

推荐阅读更多精彩内容