React源码分析=; Reac初次渲染分析
Posted 刘翾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React源码分析=; Reac初次渲染分析相关的知识,希望对你有一定的参考价值。
文章目录
- 1. render阶段 legacyRenderSubtreeIntoContainer
- 续1. legacyRenderSubtreeIntoContainer
- 1.2. root.render()
- 1.3. updateContainer 这节内部函数太多, 拆成二级标题, 续1.2.
- 1.4. scheduleWork 这节内部函数太多了, 分出一个2级标题, 续1.3.3.1.
- 1.5 performSyncWork 理由同上, 续1.4.4
- 1.6. renderRoot 理由同上, 续1.5.2.
- 1.7 updateHostRoot, 续1.6.2
- 1.8 completeUnitOfWork, 续1.6.2. workLoop
- 2. commit阶段 completeRoot
- 3. 初次渲染总结
本不该有4级标题, 没办法东西太多, 不加4级标题更不清晰.
继续上一节回来继续看代码, 本章还涉及到了work调度问题, 由于内容太多, 存在if (__DEV__)
的判断语句, 本章选择性忽略.
由于React源码使用了flow的静态类型检查器,所以会有:类型
这样的语法,对于熟悉Typescript的肯定很熟悉这样的写法,不懂的直接把类型忽略就好(例如第一行的ReactDOM:Object
,后面的:Object
就是ReactDOM的类型,有些类型是自定义的有些是框架自带的,render函数的参数里React$Element
这种类型一看就是自定义的),没什么影响。
源码基于React 16.8.6, 内容极长, 必须配合源码一起来看
单步调试的例子: ReactDOM.render('hello', document.getElementById('root'))
;
下文基于这个例子来一步一步分析, 你要是有什么见解可以直接在下面的评论区留言, 我会及时查看.
1. render阶段 legacyRenderSubtreeIntoContainer
从下面的源码可以看到,调用了ReactDOM.render
时首先执行了legacyRenderSubtreeIntoContainer
函数
// 例
ReactDOM.render(<App />, document.getElementById('root'));
// packages\\react-dom\\src\\client\\ReactDOM.js
/**
* nodeType 属性返回节点类型。
如果节点是一个元素节点,nodeType 属性返回 1。
如果节点是属性节点, nodeType 属性返回 2。
如果节点是一个文本节点,nodeType 属性返回 3。
如果节点是一个注释节点,nodeType 属性返回 8。
如果节点是一个整个文档(DOM 树的根节点),nodeType 属性返回 9。
如果节点是一个DocumentFragment,nodeType 属性返回 11。
ELEMENT_NODE = 1,
DOCUMENT_NODE = 9,
DOCUMENT_FRAGMENT_NODE = 11,
COMMENT_NODE = 8
*/
function isValidContainer(node)
return !!(
node &&
(node.nodeType === ELEMENT_NODE ||
node.nodeType === DOCUMENT_NODE ||
node.nodeType === DOCUMENT_FRAGMENT_NODE ||
(node.nodeType === COMMENT_NODE &&
node.nodeValue === ' react-mount-point-unstable '))
);
const ReactDOM: Object =
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
)
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
,
下面来看下legacyRenderSubtreeIntoContainer
函数内部
// packages\\react-dom\\src\\client\\ReactDOM.js
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>, // 首次渲染, partComponet为null
children: ReactNodeList, // children为使用React编写的组件
container: DOMContainer, // container为真实DOM元素
forceHydrate: boolean, // forceHydrate写死的false, true时为服务端渲染
callback: ?Function,
)
let root: _ReactSyncRoot = (container._reactRootContainer: any);
let fiberRoot;
if (!root)
// Initial mount 初始化容器
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// !!!未完!!!
1.1. legacyCreateRootFromDOMContainer
legacyCreateRootFromDOMContainer
这个函数主要是拿着真是DOM创建一个ReactRoot并返回, 见下方代码
// packages\\react-dom\\src\\client\\ReactDOM.js
function legacyCreateRootFromDOMContainer(
container: DOMContainer, // 真实DOM元素
forceHydrate: boolean, // 目前为false
): _ReactSyncRoot
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate)
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild))
// 删除跟节点内部元素
container.removeChild(rootSibling);
// Legacy roots are not batched.
// 返回一个ReactRoot对象
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
1.1.1. ReactRoot
继续上个函数, 见下方代码:
// packages\\react-dom\\src\\client\\ReactDOM.js
function ReactRoot(
container: DOMContainer, // 真实DOM元素
tag: RootTag, // 0
hydrate: boolean, // false
)
// Tag is either LegacyRoot or Concurrent Root
const root = createContainer(container, tag, hydrate);
this._internalRoot = root;
下面来看下createContainer
函数
// packages\\react-reconciler\\src\\ReactFiberReconciler.js
function createContainer(
containerInfo: Container, // 真实DOM元素
isConcurrent: boolean, // false
hydrate: boolean, // false
): OpaqueRoot
return createFiberRoot(containerInfo, tag, hydrate);
// 直接返回的createFiberRoot函数, 继续
1.1.1.1. createFiberRoot
这个函数返回了一个FiberRoot
function createFiberRoot(
containerInfo: any, // 真实DOM元素
isConcurrent: boolean, // false
hydrate: boolean, // false
): FiberRoot
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
// Fiber Node
const uninitializedFiber = createHostRootFiber(isConcurrent);
let root;
if (enableSchedulerTracing)
// react 19的alpha.0版本这里被抽成工厂函数了
root = ( // 创建一个FiberRootNode
current: uninitializedFiber, // 上面那个Fiber Node
containerInfo: containerInfo, // 真实DOM元素
pendingChildren: null,
earliestPendingTime: NoWork, // 0
latestPendingTime: NoWork, // 0
earliestSuspendedTime: NoWork, // 0
latestSuspendedTime: NoWork, // 0
latestPingedTime: NoWork, // 0
pingCache: null,
didError: false,
pendingCommitExpirationTime: NoWork, // 0
finishedWork: null,
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork, // 0
expirationTime: NoWork, // 0
firstBatch: null,
nextScheduledRoot: null,
interactionThreadID: unstable_getThreadID(),
memoizedInteractions: new Set(),
pendingInteractionMap: new Map(),
: FiberRoot);
else
// ...省略
uninitializedFiber.stateNode = root;
return ((root: any): FiberRoot);
1.1.1.2. createHostRootFiber
这个函数返回一个FiberNode
// packages\\react-reconciler\\src\\ReactFiber.js
function createHostRootFiber(isConcurrent: boolean): Fiber
// isConcurrent = false; ConcurrentMode = 0b001, StrictMode = 0b010, NoContext = 0b000
let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
if (enableProfilerTimer && isDevToolsPresent)
// Always collect profile timings when DevTools are present.
// This enables DevTools to start capturing timing at any point–
// Without some nodes in the tree having empty base times.
mode |= ProfileMode;
// HostRoot是常量, 值为3, mode = 0b000
return createFiber(HostRoot, null, null, mode);
// 下面来看下createFiber
// packages\\react-reconciler\\src\\ReactFiber.js
const createFiber = function(
tag: WorkTag, // 3
pendingProps: mixed, // null
key: null | string, // null
mode: TypeOfMode, // 0
): Fiber
return new FiberNode(tag, pendingProps, key, mode);
;
// 继续
// packages\\react-reconciler\\src\\ReactFiber.js
function FiberNode(
tag: WorkTag, // 3
pendingProps: mixed, // null
key: null | string, // null
mode: TypeOfMode, // 0
)
// Instance
this.tag = tag; // 3
this.key = key; // null
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps; // null
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.contextDependencies = null;
this.mode = mode; // 0
// Effects
this.effectTag = NoEffect; // 0b000000000000
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.expirationTime = NoWork; // 0
this.childExpirationTime = NoWork; // 0
this.alternate = null;
if (enableProfilerTimer)
// Note: The following is done to avoid a v8 performance cliff.
// 避免V8性能悬崖
// Learn more about this here:
// https://github.com/facebook/react/issues/14365
// https://bugs.chromium.org/p/v8/issues/detail?id=8538
this.actualDuration = Number.NaN;
this.actualStartTime = Number.NaN;
this.selfBaseDuration = Number.NaN;
this.treeBaseDuration = Number.NaN;
// It's okay to replace the initial doubles with smis after initialization.
// This won't trigger the performance cliff mentioned above,
// and it simplifies other profiler code (including DevTools).
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
// ...省略
截止到目前为止关于节点总结: (可以打开一个react项目, 选择跟节点的_reactRootContainer
属性验证一下)
续1. legacyRenderSubtreeIntoContainer
接着最开始来,
// 接着1.legacyRenderSubtreeIntoContainer内部的代码
// packages\\react-dom\\src\\client\\ReactDOM.js
// 此处的root是ReactRoot
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function')
const originalCallback = callback;
callback = function()
const instance = getPublicRootInstance(root._internalRoot);
// 将函数this注入
originalCallback.call(instance);
;
// Initial mount should not be batched.
unbatchedUpdates(() =>
if (parentComponent != null)
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
else
// 首次进到这里
root.render(children, callback);
);
else
// 非首次暂时省略
return getPublicRootInstance(root._internalRoot);
1.2. root.render()
该函数在ReactRoot
的原型链上, 下面来看一下代码:
function ReactWork()
this._callbacks = null;
this._didCommit = false;
this._onCommit = this._onCommit.bind(this); // ReactWork原型链上有这个函数, 暂时不看
ReactRoot.prototype.render = function(
children: ReactNodeList, // children为使用React编写的组件
callback: ?() => mixed,
): Work
const root = this._internalRoot; // FiberRoot
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__)
warnOnInvalidCallback(callback, 'render');
if (callback !== null)
work.then(callback);
updateContainer(children, root, null, work._onCommit);
return work;
;
1.3. updateContainer 这节内部函数太多, 拆成二级标题, 续1.2.
unbatchedUpdates
就不看了, 大体就是立即执行updateContainer
函数.
updateContainer
函数里面都是调用其他函数直接看代码吧, 代码如下:
// packages\\react-reconciler\\src\\ReactFiberReconciler.js
function updateContainer(
element: ReactNodeList, // 使用React编写的组件
container: OpaqueRoot, // FiberRoot
parentComponent: ?React$Component<any, any>, // null
callback: ?Function, // ReactWork._onCommit
): ExpirationTime
const current = container.current; // FibeNode
const currentTime = requestCurrentTime(); // 获取当前已经花费的时间
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
具体函数见下面1.3.x.下节
1.3.1. requestCurrentTime
这个函数主要用来计算到期时间, 代码如下:
// packages\\react-reconciler\\src\\ReactFiberScheduler.js
function findHighestPriorityRoot()
let highestPriorityWork = NoWork; // 0
let highestPriorityRoot = null;
// lastScheduledRoot初始化为null
if (lastScheduledRoot !== null)
// ... 省略
nextFlushedRoot = highestPriorityRoot; // null
nextFlushedExpirationTime = highestPriorityWork; // 0
// packages\\react-reconciler\\src\\ReactFiberWorkLoop.js
function requestCurrentTime() // 计算到期时间
// 通过添加到当前时间(开始时间)来计算到期时间。 但是,如果在同一事件中安排了两次更新,我们应将它们的开始时间视为同步,即使第一次和第二次呼叫之间的实际时钟时间已提前。
// 换句话说, 因为过期时间决定了如何批量更新
// 我们希望在同一事件中发生的所有类似优先级的更新都会收到相同的到期时间。 否则我们会撕裂。
// 我们跟踪两个不同的时间:renderer time和scheduler time。 renderer time可以随时使用`performance.now`更新;
// 但是scheduler time只能更新在 没有待处理的工作,或者我们确定我们不在事件的中间时
if (isRendering)
// We're already rendering. Return the most recently read time.
return currentSchedulerTime;
// Check if there's pending work.
findHighestPriorityRoot();
if (
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
)
// If there's no pending work, or if the pending work is offscreen, we can
// read the current time without risk of tearing.
// 没有待处理的工作,或者待处理的工作是在屏幕外,
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime; // 上一个函数计算的值赋值到了currentRendererTime
return currentSchedulerTime;
// There's already pending work. We might be in the middle of a browser
// event. If we were to read the current time, it could cause multiple updates
// 已经有pending work了。 我们可能正处于浏览器事件的中间。 如果我们要读取当前时间,可能会导致多次更新
// within the same event to receive different expiration times, leading to
// tearing. Return the last read time. During the next idle callback, the
// time will be updated.
// 防止在事件返回不同时间, 返回上次的时间, 下次空闲回调时时间会更新.
return currentSchedulerTime;
下面来看一下recomputeCurrentRendererTime
函数
1.3.1.1. recomputeCurrentRendererTime
此处now函数就是上一节说的performance.now, 具体:
https://liuxuan.blog.csdn.net/article/details/90641799
function recomputeCurrentRendererTime()
// originalStartTimeMs = now(); 启动时执行的
const currentTimeMs = now() - originalStartTimeMs;
currentRendererTime = msToExpirationTime(currentTimeMs);
1.3.1.2. msToExpirationTime
// packages\\react-reconciler\\src\\ReactFiberExpirationTime.js
const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;
// 1 unit of expiration time represents 10ms.
// 1个到期时间单位是10ms
export function msToExpirationTime(ms: number): ExpirationTime
// Always add an offset so that we don't clash with the magic number for NoWork.
// NoWork是常量, 值为0, 应该是怕减数正好为0吧
return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0);
/ 10
抹掉10ms时间差, | 0
的意思是取整,
1.3.2 computeExpirationForFiber
这个函数主要根据Fiber类型来计算到期时间, 代码如下:
// packages\\react-reconciler\\src\\ReactFiberWorkLoop.js
function computeExpirationForFiber(
currentTime: ExpirationTime, // requestCurrentTime算得的时间
fiber: Fiber, // FiberNode
)
const priorityLevel = getCurrentPriorityLevel(); // 初始返回NormalPriority => 3
let expirationTime;
// ConcurrentMode是常量为0b001
// NoContext是常量为0b000
// fiber.mode 为 0
if ((fiber.mode & ConcurrentMode) === NoContext)
// Outside of concurrent mode, updates are always synchronous.
// 在并发模式之外,更新始终是同步的
// Sync是常量, 值为32位系统的V8中的最大整数, Math.pow(2, 30) - 1
expirationTime = Sync;
else if (isWorking && !isCommitting)
// During render phase, updates expire during as the current render.
// 在render阶段
expirationTime = nextRenderExpirationTime;
else以上是关于React源码分析=; Reac初次渲染分析的主要内容,如果未能解决你的问题,请参考以下文章