共计 5297 个字符,预计需要花费 14 分钟才能阅读完成。
React 源码解析系列文章欢迎阅读:
React16 源码解析 (一)- 图解 Fiber 架构
React16 源码解析(二)- 创建更新
React16 源码解析(三)-ExpirationTime
React16 源码解析(四)-Scheduler
React16 源码解析(五)- 更新流程渲染阶段 1
React16 源码解析(六)- 更新流程渲染阶段 2
React16 源码解析(七)- 更新流程渲染阶段 3
React16 源码解析(八)- 更新流程提交阶段
正在更新中 …
上篇文章讲解到 renderRoot & completeRoot。
React 把组件的更新过程分为了两个阶段:渲染阶段(renderRoot)和提交阶段(completeRoot)。
本文讨论渲染阶段。
从上篇文章末尾的 renderRoot 讲起:
renderRoot
1、nextUnitOfWork = createWorkInProgress() 拷贝一份 fiber 节点,在 nextUnitOfWork 中修改,防止改变当前 fiberTree。nextUnitOfWork 是下一个要更新的节点。
2、进入 workLoop。
// 开始渲染整颗树,这个函数在异步模式下可能会被多次执行,因为在异步模式下
// 可以打断任务。打断也就意味着每次都得回到 root 再开始从上往下循环
function renderRoot(
root: FiberRoot,
isYieldy: boolean,
isExpired: boolean,
): void {
isWorking = true;
ReactCurrentOwner.currentDispatcher = Dispatcher;
const expirationTime = root.nextExpirationTimeToWorkOn;
// Check if we're starting from a fresh stack, or if we're resuming from
// previously yielded work.
if (
expirationTime !== nextRenderExpirationTime ||
root !== nextRoot ||
nextUnitOfWork === null
) {
// Reset the stack and start working from the root.
resetStack();
nextRoot = root;
nextRenderExpirationTime = expirationTime;
// 获取下一个需要工作的单元
nextUnitOfWork = createWorkInProgress(
// FiberRoot 对应的 Rootfiber
nextRoot.current,
null,
nextRenderExpirationTime,
);
root.pendingCommitExpirationTime = NoWork;
// ......
}
// ......
do {
try {
// 循环更新节点
workLoop(isYieldy);
} catch (thrownValue) {
// 遇到某种错误
break;
} while (true);
// 这里有一大堆的错误处理
// Ready to commit.
onComplete(root, rootWorkInProgress, expirationTime);
}
workLoop
循环单元更新,对整颗 fiberTree 都遍历一遍。
还记得之前传入进来的 isYieldy 的么,如果为 false,不可中断,不断的更新下一个节点任务(performUnitOfWork(nextUnitOfWork)),知道整棵树更新完毕。如果可以中断,通过 shouldYield()判断当前帧是否还有时间更新,有时间就更新,没有时间了就不更了。
注:在 workLoop 跟新后会有各种错误处理。
function workLoop(isYieldy) {
// 对 nextUnitOfWork 循环进行判断,直到没有 nextUnitOfWork
if (!isYieldy) {
// 不可中断
// Flush work without yielding
while (nextUnitOfWork !== null) {
// 一开始进来 nextUnitOfWork 是 root,每次执行 performUnitOfWork 后
// 都会生成下一个工作单元
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// 可中断
// Flush asynchronous work until the deadline runs out of time.
while (nextUnitOfWork !== null && !shouldYield()) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
performUnitOfWork
1、调用 beginWork()更新当前任务节点
2、如果当前 fiber 树已经更新到叶子节点了,则调用 completeUnitOfWork 更新。
// 开始组件更新
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
// 获得 fiber 的替身,调和这一阶段都是在替身上完成的
// 然后直接看 beginWork
const current = workInProgress.alternate;
// ......
let next;
// .....
// 开始工作
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
// ......
// 当前 fiber 树已经更新到叶子节点了
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner.current = null;
return next;
}
beginWork
通过 workInProgress.tag 判断当前是什么类型的节点。
workInProgress.tag 的所有类型定义在 ReactWorkTags.js 里面
export const FunctionComponent = 0; // 函数式组件
export const ClassComponent = 1; //class 类组件
export const IndeterminateComponent = 2; // 并不确定是函数组件还是类组件
export const HostRoot = 3; // 组件树根组件,可以嵌套
export const HostPortal = 4; // 子树. Could be an entry point to a different renderer.
export const HostComponent = 5;// 标准组件,如地 div,span 等
export const HostText = 6;// 文本
export const Fragment = 7;// 片段
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
接下来我会依次讲解下面这些组件的更新:
1、FunctionComponent(函数组件),return updateFunctionComponent(…)。
2、ClassComponent(class 类组件)return updateClassComponent(…)
3、IndeterminateComponent(不确定是函数组件还是类组件),return mountIndeterminateComponent(…)
4、HostRoot(组件树根组件),return updateHostRoot(…);
5、HostComponent(标准 dom 节点),return updateHostComponent(…);
6、HostText(文本),updateHostText(…);
7、HostPortal,updatePortalComponent(…)
8、ForwardRef,updateForwardRef(…)
9、Mode,updateMode(…)
10、MemoComponent,updateMemoComponent(….)
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime;
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 判断 props 和 context 是否改变
// 判断当前 fiber 的优先级是否小于本次渲染的优先级,小于的话可以跳过
if (
oldProps === newProps &&
!hasLegacyContextChanged() &&
(updateExpirationTime === NoWork ||
updateExpirationTime > renderExpirationTime)
) {// ......}
// bailoutOnAlreadyFinishedWork 会判断这个 fiber 的子树是否需要更新,如果有需要更新会 clone 一份到 workInProgress.child 返回到 workLoop 的 nextUnitOfWork, 否则为 null
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
}
// Before entering the begin phase, clear the expiration time.
// 进行更新先把当前 fiber 的 expirationTime 设置为 NoWork
workInProgress.expirationTime = NoWork;
// 根据 fiber 的 tag 类型进行更新
switch (workInProgress.tag) {
case IndeterminateComponent: ......
case LazyComponent: ......
case FunctionComponent: ......
case ClassComponent: ......
case HostRoot:......
case HostComponent:......
case HostText:......
case SuspenseComponent:......
case HostPortal:......
case ForwardRef: ......
case Fragment:......
case Mode:......
case Profiler:......
case ContextProvider:......
case ContextConsumer:......
case MemoComponent: ......
case SimpleMemoComponent: ......
case IncompleteClassComponent:......
default:
invariant(
false,
'Unknown unit of work tag. This error is likely caused by a bug in' +
'React. Please file an issue.',
);
}
}
下篇文章继续讲解这些组件的更新。
任世界纷繁复杂, 仍旧保持可爱。
我是小柚子小仙女。文章如有不妥,欢迎指正~