ReactDOM.render
通常是如下图使用,在提供的 container 里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回 null)。本文主要是将ReactDOM.render的执行流程在后续文章中会对创建更新的细节进行分析,文中的源代码部分为了方便阅读将__DEV__
部分的代码移除掉了。
ReactDOM.render(<App />,document.getElementById('root')
);
render
位于:react-dom/src/client/ReactDOMLegacy.js
export function render( element: React$Element<any>,container: Container,callback: ?Function, ) {// 验证container是否为有效的DOM节点invariant(isValidContainer(container),'Target container is not a DOM element.',);return legacyRenderSubtreeIntoContainer(null,element,container,false,callback,);
}
返回了一个legacyRenderSubtreeIntoContainer函数,这里注意有5个参数
parentComponent
: 父组件因为是初次创建所以为null。
children
: 传入的ReactElement
container
: 渲染React的DOM容器
forceHydrate
: 判断是否需要协调,在服务端渲染的情况下已渲染的DOM结构是类似的因此可以在对比后进行复用。在服务端渲染的情况下使用ReactDOM.hydrate()与 render() 相同只是forceHydrate会标记为true。
callback
: 渲染完成后的回调函数
legacyRenderSubtreeIntoContainer
位于:react-dom/src/client/ReactDOMLegacy.js
作用:
- 判断是否为初次渲染,如果是就创建root并将root._internalRoot赋值给fiberRoot同时封装callback回调,然后调用unbatchedUpdates立即更新子节点。
- 如果不是第一次渲染则进入正常的updateContainer流程。
- 最后getPublicRootInstance(fiberRoot)返回公开的 Root 实例对象。
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any, any>,children: ReactNodeList,container: Container,forceHydrate: boolean,callback: ?Function, ) {// TODO: Without `any` type, Flow says "Property cannot be accessed on any// member of intersection type." Whyyyyyy.let root: RootType = (container._reactRootContainer: any);let fiberRoot;if (!root) {// Initial mount 初次渲染创建FiberRootroot = container._reactRootContainer = legacyCreateRootFromDOMContainer(container,forceHydrate,);fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// Initial mount should not be batched.unbatchedUpdates(() => {updateContainer(children, fiberRoot, parentComponent, callback);});} else {fiberRoot = root._internalRoot;if (typeof callback === 'function') {const originalCallback = callback;callback = function() {const instance = getPublicRootInstance(fiberRoot);originalCallback.call(instance);};}// UpdateupdateContainer(children, fiberRoot, parentComponent, callback);}return getPublicRootInstance(fiberRoot);
}
legacyCreateRootFromDOMContainer
位于:react-dom/src/client/ReactDOMLegacy.js
初次渲染进入创建root的环节:root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate)
作用:主要是判断是否为服务端渲染,如果是的话就会复用存在的dom节点进行协调(reconciliation)提高性能,如果不是则会清空container中的子元素,最后传入container和shouldHydrate返回createLegacyRoot函数。
function legacyCreateRootFromDOMContainer( container: Container,forceHydrate: boolean, ): RootType {const shouldHydrate =forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 判断是否是服务端渲染// First clear any existing content.if (!shouldHydrate) {let warned = false;let rootSibling;while ((rootSibling = container.lastChild)) {container.removeChild(rootSibling);}}return createLegacyRoot(container,shouldHydrate? {hydrate: true,}: undefined,);
}
createLegacyRoot
位于:react-dom/src/client/ReactDOMRoot.js
作用:返回了一个ReactDOMBlockingRoot实例,这里传入了LegacyRoot是一个常量=0代表着现在使用的同步渲染模式,是为了后续的Concurrent可中断渲染模式做准备。
export function createLegacyRoot(container: Container,options?: RootOptions, // hydrate
): RootType {return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
ReactDOMBlockingRoot
位于:react-dom/src/client/ReactDOMRoot.js
作用:将createRootImpl
函数的返回(FiberRoot)挂载到实例的_internalRoot上
function ReactDOMBlockingRoot( container: Container,tag: RootTag,options: void | RootOptions, ) {this._internalRoot = createRootImpl(container, tag, options);
}
相关参考视频讲解:进入学习
createRootImpl
位于:react-dom/src/client/ReactDOMRoot.js
作用:执行createContainer拿到FiberRootNode并赋值给root,再通过markContainerAsRoot将RootFiber挂载到container上。
function createRootImpl( container: Container,tag: RootTag,options: void | RootOptions, ) {// Tag is either LegacyRoot or Concurrent Rootconst hydrate = options != null && options.hydrate === true;const hydrationCallbacks =(options != null && options.hydrationOptions) || null;// 拿到FiberRootNodeconst root = createContainer(container, tag, hydrate, hydrationCallbacks);// 将FiberRootNode挂载到containermarkContainerAsRoot(root.current, container);if (hydrate && tag !== LegacyRoot) {const doc =container.nodeType === DOCUMENT_NODE? container: container.ownerDocument;eagerlyTrapReplayableEvents(container, doc);}return root;
}
createContainer
位于:react-reconciler/src/ReactFiberReconciler.old.js
作用:返回createFiberRoot
export function createContainer(containerInfo: Container,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks,): OpaqueRoot {return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
createFiberRoot
位于:react-reconciler/src/react-reconciler/src/ReactFiberReconciler.old.js
作用: 新建FiberRoot对象并赋值给root,初始化Fiber(通常叫做RootFiber)通过root.current = uninitializedFiber和uninitializedFiber.stateNode = root将两者联系起来。执行initializeUpdateQueue(uninitializedFiber)
创建一个更新队列,挂载fiber.updateQueue下面最后将root返回
export function createFiberRoot(containerInfo: any,tag: RootTag,hydrate: boolean,hydrationCallbacks: null | SuspenseHydrationCallbacks,): FiberRoot {// 新建fiberRoot对象const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);if (enableSuspenseCallback) {root.hydrationCallbacks = hydrationCallbacks;}// Cyclic construction. This cheats the type system right now because// stateNode is any.//初始化RootFiberconst uninitializedFiber = createHostRootFiber(tag);root.current = uninitializedFiber;// RootFiber的stateNode指向FiberRootuninitializedFiber.stateNode = root;initializeUpdateQueue(uninitializedFiber);return root;
}
FiberRoot RootFiber 和 updateQueue
ReactDOM.render主要创建了三个对象FiberRooat、RootFiber和Updatequeue下面我们这对这三个对象进行分析
FiberRoot
FiberRoot是FiberRootNode(containerInfo, tag, hydrate)的实例位于:react-reconciler/src/ReactFiberRoot/FiberRootNode
作用:
- 整个应用的起点
- 包含应用挂载的目标节点
- 记录整个应用更新过程的各种信息
function FiberRootNode(containerInfo, tag, hydrate) {// 标记不同的组件类型this.tag = tag;// 当前应用对应的Fiber对象,是Root Fiber// current:Fiber对象 对应的是 root 节点,即整个应用根对象this.current = null;// root节点,render方法接收的第二个参数this.containerInfo = containerInfo; // 只有在持久更新中会用到,也就是不支持增量更新的平台,react-dom不会用到this.pendingChildren = null;this.pingCache = null;//任务有三种,优先级有高低://(1)没有提交的任务//(2)没有提交的被挂起的任务//(3)没有提交的可能被挂起的任务 //当前更新对应的过期时间this.finishedExpirationTime = NoWork;//已经完成任务的FiberRoot对象,如果你只有一个Root,那么该对象就是这个Root对应的Fiber或null//在commit(提交)阶段只会处理该值对应的任务this.finishedWork = null;// 在任务被挂起的时候通过setTimeout设置的返回内容,用来下一次如果有新的任务挂起时清理还没触发的timeout(例如suspense返回的promise)this.timeoutHandle = noTimeout;// 顶层context对象,只有主动调用renderSubTreeIntoContainer时才会被调用this.context = null;this.pendingContext = null;// 第一次渲染是否需要调和this.hydrate = hydrate;// Node returned by Scheduler.scheduleCallbackthis.callbackNode = null;this.callbackPriority = NoPriority;//存在root中,最旧的挂起时间//不确定是否挂起的状态(所有任务一开始均是该状态)this.firstPendingTime = NoWork;this.firstSuspendedTime = NoWork;this.lastSuspendedTime = NoWork;this.nextKnownPendingLevel = NoWork;//存在root中,最新的挂起时间//不确定是否挂起的状态(所有任务一开始均是该状态)this.lastPingedTime = NoWork;this.lastExpiredTime = NoWork;this.mutableSourcePendingUpdateTime = NoWork;if (enableSchedulerTracing) {this.interactionThreadID = unstable_getThreadID();this.memoizedInteractions = new Set();this.pendingInteractionMap = new Map();}if (enableSuspenseCallback) {this.hydrationCallbacks = null;}
}
RootFiber
RootFiber初始化于const uninitializedFiber = createHostRootFiber(tag) 通过 createFiber 返回 FiberNode的实例
作用:
- 每个ReactElement对应一个Fiber对象
- 记录节点的各种状态(方便了hooks,因为记录state和props都是在Fiber只是完成后再挂载到this的例如:pendingProps pendingState memoizedProps memoizedState)
- 串联整个应用形成树结构
// 位于 react-reconciler/src/ReactFiber.js
export function createHostRootFiber(tag: RootTag): Fiber {let mode;if (tag === ConcurrentRoot) {mode = ConcurrentMode | BlockingMode | StrictMode;} else if (tag === BlockingRoot) {mode = BlockingMode | StrictMode;} else {mode = NoMode;}return createFiber(HostRoot, null, null, mode);
}
const createFiber = function(tag: WorkTag,pendingProps: mixed,key: null | string,mode: TypeOfMode,
): Fiber {return new FiberNode(tag, pendingProps, key, mode);
};
// FiberNode结构
function FiberNode(tag: WorkTag,pendingProps: mixed,key: null | string,mode: TypeOfMode,
) {// Instance// 标记不同的组件类型this.tag = tag;// ReactElement里面的keythis.key = key;// ReactElement.type,也就是我们调用`createElement`的第一个参数this.elementType = null;// 异步组件lazy component resolved之后返回的内容,一般是`function`或者`class`组件this.type = null;// 对应节点的实例,比如类组件就是class的实例,如果是dom组件就是dom实例,如果是function component就没有实例这里为空this.stateNode = null;// Fiber Fiber是个链表通过child和Sibling连接,遍历的时候先遍历child如果没有子元素了则访问return回到上级查询是否有sibling// 指向他在Fiber节点树中的‘parent’,用来在处理完这个节点之后向上返回this.return = null;// 指向第一个子节点this.child = null;// 指向自己的兄弟节点,兄弟节点的return指向同一个副节点this.sibling = null;this.index = 0;this.ref = null;// 新的变动带来的新的propsthis.pendingProps = pendingProps;// 上次渲染完成后的propsthis.memoizedProps = null;// 该Fiber对应的组件产生的update会存放在这个队列(比如setState和forceUpdate创建的更新)this.updateQueue = null;// 上一次的statethis.memoizedState = null;this.dependencies = null;// this.mode = mode;// Effects// 用来记录副作用this.effectTag = NoEffect;// 单链表用来快速查找下一个side effectthis.nextEffect = null;// 子树中第一个side effectthis.firstEffect = null;// 子树中最后一个side effectthis.lastEffect = null;// 代表任务在未来的哪个时候应该被完成 就是过期时间// 不包括他的子树产生的任务this.expirationTime = NoWork;// 快速确定子树中是否有不再等待的变化this.childExpirationTime = NoWork;// Fiber树更新过程中,每个FIber都会有一个跟其对应的Fiber// 我们称他为`current <==> workInProgress`// 渲染完成后他们会交换位置this.alternate = null;// 调试相关的去掉了
}
updateQueue
initializeUpdateQueue(uninitializedFiber);
位于:react-reconciler/src/ReactUpdateQueue.js
作用:单向链表,用来存放update,next来串联update关于Update和UpdateQueue涉及到的东西比较多打算单独一章来讲解
export function initializeUpdateQueue<State>(fiber: Fiber): void {const queue: UpdateQueue<State> = {// 每次操作完更新阿之后的statebaseState: fiber.memoizedState,// 队列中的第一个`Update`firstBaseUpdate: null,// 队列中的最后一个`Update`lastBaseUpdate: null,shared: {pending: null,},effects: null,};fiber.updateQueue = queue;
}
流程图
最后是画的大致流程图
最后
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享