您现在的位置是:网站首页> 编程资料编程资料
React commit源码分析详解_React_
2023-12-10
220人已围观
简介 React commit源码分析详解_React_
总览
commit 阶段相比于 render 阶段要简单很多,因为大部分更新的前期操作都在 render 阶段做好了,commit 阶段主要做的是根据之前生成的 effectList,对相应的真实 dom 进行更新和渲染,这个阶段是不可中断的。
commit 阶段大致可以分为以下几个过程:
- 获取 effectList 链表,如果 root 上有 effect,则将其也添加进 effectList 中
- 对 effectList 进行第一次遍历,执行
commitBeforeMutationEffects
函数来更新class组件实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数 - 对 effectList 进行第二次遍历,执行
commitMutationEffects
函数来完成副作用的执行,主要包括重置文本节点以及真实 dom 节点的插入、删除和更新等操作。 - 对 effectList 进行第三次遍历,执行
commitLayoutEffects
函数,去触发 componentDidMount、componentDidUpdate 以及各种回调函数等 - 最后进行一点变量还原之类的收尾,就完成了 commit 阶段
我们从 commit 阶段的入口函数 commitRoot
开始看:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js function commitRoot(root) { const renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority( ImmediateSchedulerPriority, commitRootImpl.bind(null, root, renderPriorityLevel), ); return null; }
它调用了 commitRootImpl
函数,所要做的工作都在这个函数中:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js function commitRootImpl(root, renderPriorityLevel) { // ... const finishedWork = root.finishedWork; const lanes = root.finishedLanes; // ... // 获取 effectList 链表 let firstEffect; if (finishedWork.flags > PerformedWork) { // 如果 root 上有 effect,则将其添加进 effectList 链表中 if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // 如果 root 上没有 effect,直接使用 finishedWork.firstEffect 作用链表头节点 firstEffect = finishedWork.firstEffect; } if (firstEffect !== null) { // ... // 第一次遍历,执行 commitBeforeMutationEffects nextEffect = firstEffect; do { if (__DEV__) { invokeGuardedCallback(null, commitBeforeMutationEffects, null); // ... } else { try { commitBeforeMutationEffects(); } catch (error) { // ... } } } while (nextEffect !== null); // ... // 第二次遍历,执行 commitMutationEffects nextEffect = firstEffect; do { if (__DEV__) { invokeGuardedCallback( null, commitMutationEffects, null, root, renderPriorityLevel, ); // ... } else { try { commitMutationEffects(root, renderPriorityLevel); } catch (error) { // ... } } } while (nextEffect !== null); // 第三次遍历,执行 commitLayoutEffects nextEffect = firstEffect; do { if (__DEV__) { invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes); // ... } else { try { commitLayoutEffects(root, lanes); } catch (error) { // ... } } } while (nextEffect !== null); nextEffect = null; // ... } else { // 没有任何副作用 root.current = finishedWork; if (enableProfilerTimer) { recordCommitTime(); } } // ... }
commitBeforeMutationEffects
commitBeforeMutationEffects
中,会从 firstEffect 开始,通过 nextEffect 不断对 effectList 链表进行遍历,若是当前的 fiber 节点有 flags 副作用,则执行 commitBeforeMutationEffectOnFiber
节点去对针对 class 组件单独处理。
相关参考视频讲解:传送门
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js function commitBeforeMutationEffects() { while (nextEffect !== null) { // ... const flags = nextEffect.flags; if ((flags & Snapshot) !== NoFlags) { // 如果当前 fiber 节点有 flags 副作用 commitBeforeMutationEffectOnFiber(current, nextEffect); // ... } // ... nextEffect = nextEffect.nextEffect; } }
然后看一下 commitBeforeMutationEffectOnFiber
,它里面根据 fiber 的 tag 属性,主要是对 ClassComponent 组件进行处理,更新 ClassComponent 实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数:
// packages/react-reconciler/src/ReactFiberCommitWork.old.js function commitBeforeMutationLifeCycles( current: Fiber | null, finishedWork: Fiber, ): void { switch (finishedWork.tag) { case FunctionComponent: case ForwardRef: case SimpleMemoComponent: case Block: { return; } case ClassComponent: { if (finishedWork.flags & Snapshot) { if (current !== null) { // 非首次加载的情况下 // 获取上一次的 props 和 state const prevProps = current.memoizedProps; const prevState = current.memoizedState; // 获取当前 class 组件实例 const instance = finishedWork.stateNode; // ... // 调用 getSnapshotBeforeUpdate 生命周期方法 const snapshot = instance.getSnapshotBeforeUpdate( finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState, ); // ... // 将生成的 snapshot 保存到 instance.__reactInternalSnapshotBeforeUpdate 上,供 DidUpdate 生命周期使用 instance.__reactInternalSnapshotBeforeUpdate = snapshot; } } return; } // ... } }
commitMutationEffects
commitMutationEffects
中会根据对 effectList 进行第二次遍历,根据 flags 的类型进行二进制与操作,然后根据结果去执行不同的操作,对真实 dom 进行修改:相关参考视频讲解:进入学习
- ContentReset: 如果 flags 中包含 ContentReset 类型,代表文本节点内容改变,则执行
commitResetTextContent
重置文本节点的内容 - Ref: 如果 flags 中包含 Ref 类型,则执行
commitDetachRef
更改 ref 对应的 current 的值 - Placement: 上一章 diff 中讲过 Placement 代表插入,会执行
commitPlacement
去插入 dom 节点 - Update: flags 包含 Update 则会执行
commitWork
执行更新操作 - Deletion: flags 包含 Deletion 则会执行
commitDeletion
执行更新操作
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js function commitMutationEffects( root: FiberRoot, renderPriorityLevel: ReactPriorityLevel, ) { // 对 effectList 进行遍历 while (nextEffect !== null) { setCurrentDebugFiberInDEV(nextEffect); const flags = nextEffect.flags; // ContentReset:重置文本节点 if (flags & ContentReset) { commitResetTextContent(nextEffect); } // Ref:commitDetachRef 更新 ref 的 current 值 if (flags & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } if (enableScopeAPI) { if (nextEffect.tag === ScopeComponent) { commitAttachRef(nextEffect); } } } // 执行更新、插入、删除操作 const primaryFlags = flags & (Placement | Update | Deletion | Hydrating); switch (primaryFlags) { case Placement: { // 插入 commitPlacement(nextEffect); nextEffect.flags &= ~Placement; break; } case PlacementAndUpdate: { // 插入并更新 // 插入 commitPlacement(nextEffect); nextEffect.flags &= ~Placement; // 更新 const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // ... case Update: { // 更新 const current = nextEffect.alternate; commitWork(current, nextEffect); break; } case Deletion: { // 删除 commitDeletion(root, nextEffect, renderPriorityLevel); break; } } resetCurrentDebugFiberInDEV(); nextEffect = nextEffect.nextEffect; } }
下面我们重点来看一下 react 是如何对真实 dom 节点进行操作的。
插入 dom 节点
获取父节点及插入位置
插入 dom 节点的操作以 commitPlacement
为入口函数, commitPlacement
中会首先获取当前 fiber 的父 fiber 对应的真实 dom 节点以及在父节点下要插入的位置,根据父节点对应的 dom 是否为 container,去执行 insertOrAppendPlacementNodeIntoContainer
或者 insertOrAppendPlacementNode
进行节点的插入。
// packages/react-reconciler/src/ReactFiberCommitWork.old.js function commitPlacement(finishedWork: Fiber): void { if (!supportsMutation) { return; } // 获取当前 fiber 的父 fiber const parentFiber = getHostParentFiber(finishedWork); let parent; let isContainer; // 获取父 fiber 对应真实 dom 节点 const parentStateNode = parentFiber.stateNode; // 获取父 fiber 对应的 dom 是否可以作为 container case HostComponent: parent = parentStateNode; isContainer = false; break; case HostRoot: parent = parentStateNode.containerInfo; isContainer = true; break; case HostPortal: parent = parentStateNode.containerInfo; isContainer = true; break; case FundamentalComponent: if (enableFundamentalAPI) { parent = parentStateNode.instance; isContainer = false; } default: invariant( false, 'Invalid host parent fiber. This error is likely caused by a bug ' + 'in React. Please file an issue.', ); } // 如果父 fiber 有 ContentReset 的 flags 副作用,则重置其文本内容 if (parentFiber.flags & ContentReset) { resetTextContent(parent); parentFiber.flags &= ~ContentReset; } // 获取要在哪个兄弟 fiber 之前插入 const before = getHostSibling(finishedWork); if (isContainer) { insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); } else { insertOrAppendPlacementNode(finishedWork, before, parent); } }
判断当前节点是否为单节点
我们以 insertOrAppendPlacementNodeIntoContainer
为例看一下其源码,里面通过 ta
相关内容
- 刀塔传奇熊猫酒仙装备搭配攻略推荐_刀塔传奇熊喵进阶装备介绍说明_手机游戏_游戏攻略_
- 刀塔传奇敌法装备攻略推荐_刀塔传奇敌法进阶装备介绍说明_手机游戏_游戏攻略_
- 刀塔传奇蓝胖五星满级满附魔属性分析_手机游戏_游戏攻略_
- 刀塔传奇小娜迦五星满级满附魔属性分析_手机游戏_游戏攻略_
- 天天酷跑爱丽丝兔和布鲁哪个比较好_天天酷跑爱丽丝兔和布鲁属性对比_手机游戏_游戏攻略_
- 刀塔传奇影魔五星满级满附魔属性分析_手机游戏_游戏攻略_
- 雷霆战机追击Boss抢钻石活动介绍_手机游戏_游戏攻略_
- 天天酷跑糖白虎暴力全面解析_天天酷跑糖白虎综合点评_手机游戏_游戏攻略_
- 雷霆战机庆世界杯无尽挑战得装备活动介绍_手机游戏_游戏攻略_
- 雷霆战机合成系统规则详解_手机游戏_游戏攻略_