深入react-ReactDOM.render
前言
这篇主要就是介绍render的一个流程,只到调度为止,并没有深入到每个点,涉及到的数据结构我会在下一篇专门列出来,这里只要知道创建了什么数据结构就可以了,我主要是按16.12.0这个版本讲的,今天发现已经更新到16.13.0了不过具体改了什么还没看
render
export function render(element, container, callback) { // validate container return legacyRenderSubtreeIntoContainer( null, element, container, false, callback ); }
- ReactDOM.render首先会调用上面这个函数
- 该函数返回一个legacyRenderSubtreeIntoContainer函数调用的结果,传入5个参数
- null:parentComponet
- element:ReactDOM.render的第一个参数,前面讲API的时候讲过是一个ReactElement
- container : ReactDOM.render的第二个参数,一个element容器
- false: forceHydrate 服务端渲染用的,可以忽略
- callback:ReactDOM.render的第三个参数,一个回调函数
legacyRenderSubtreeIntoContainer
function legacyRenderSubtreeIntoContainer( parentComponent, children, container, forceHydrate, callback ) { let root = container._reactRootContainer; let fiberRoot; if (!root) { // ReactDOMBlockingRoot实例,属性_internalRoot上挂载着fiberRootNode root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate ); fiberRoot = root._internalRoot; unbatchedUpdates(() => { updateContainer(children, fiberRoot, parentComponent, callback); }); } }
- 首次渲染container._reactRootContainer肯定为空
- 直接进入到if(!root)中
- 调用legacyCreateRootFromDOMContainer函数创建ReactDOMBlockingRoot赋值给root和container._reactRootContainer,这里也同时创建了fiberRootNode保存在_internalRoot属性中
- 赋值fiberRootNode给fiberRoot
- 调用unbatchedUpdates函数,取消批量更新
到这里是一个极简化的render,没有进入任何分支,下面我们看下ReactDOMBlockingRoot实例的创建,以及挂载在该实例下的FiberRoot
legacyCreateRootFromDOMContainer
function legacyCreateRootFromDOMContainer(container, forceHydrate) { // 一般Hydrate的情况是在服务端渲染和预渲染(prerender-spa-plugin) const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); if (!shouldHydrate) { let rootSibling; while ((rootSibling = container.lastChild)) { container.removeChild(rootSibling); } } return createLegacyRoot( container, shouldHydrate ? { hydrate: true } : undefined ); }
- 这个函数其实就做了一件事,就是遍历删除container中的dom标签
- 然后调用createLegacyRoot
- hydrate这个东西可以完全忽略
createLegacyRoot
export function createLegacyRoot(container, options = {}) { return new ReactDOMBlockingRoot(container, LegacyRoot, options); }
- new了一个ReactDOMBlockingRoot 传递了三个参数
- container:DOM ELement
- options可以忽略,就是那个hydrate
- LegacyRoot:一个常量,这里表示blockingRoot
ReactDOMBlockingRoot
class ReactDOMBlockingRoot { // container:domElement, tag:rootType, options: {hydrate:boolean} constructor(container, tag, options) { this._internalRoot = createRootImpl(container, tag, options); } }
- 一个ReactDOMBlockingRoot class
- 在_internalRoot挂了 createRootImpl函数调用的结果,这就是之前讲render说到的ReactDOMBlockingRoot上有一个_internalRoot属性上面挂这fiberRootNode
createRootImpl
function createRootImpl(container, tag, options) { const hydrate = options !== null && options.hydrate === true; const hydrationCallbacks = (options != null && options.hydrationOptions) || null; // 拿到fiberRootNode const root = createContainer(container, tag, hydrate, hydrationCallbacks); // 讲fiberNode 挂载到container对象上 markContainerAsRoot(root.current, container); return root; }
- 这里创建fiberRoot的分支有点多,再次调用了createContainer,这也是我将render和createFiberNode分开讲的原因,不过无论他调用多少层最终目的就是返回一个FIberNode对象
- markContainerAsRoot函数只是将最终返回的fiberRoot对象上的current属性挂载到了container上面
createContainer
export function createContainer(container, tag, hydrate, hydrationCallbacks) { return createFiberRoot(container, tag, hydrate, hydrationCallbacks); }
- 继续再调用下一个分支createFiberRoot
createFiberRoot
export function createFiberRoot(container, tag, hydrate, hydrationCallbacks) { // 创建fiber树root节点 const root = new FiberRootNode(container, tag, hydrate); const uninitializedFiber = createHostRootFiber(tag); root.current = uninitializedFiber; uninitializedFiber.stateNode = root; initializeUpdateQueue(uninitializedFiber); return root; }
- 到这里其实就差不多结束了整个创建的过程
- new FiberRootNode: 就是实例化了一个FiberRoot对象
- createHostRootFiber:创建的是一个Fiber对象,这个Fiber也经常被叫RootFiber
- 他会被挂到FiberRoot.current下面
- initializeUpdateQueue:创建一个更新队列,挂载fiber.updateQueue下面
- 最后进行返回
- 这里总共会涉及到三个数据结构,分别是:FiberRoot,Fiber和updateQueue 我放到后面再讲,但是这个创建的流程到这里就算结束了,可以看下下面的图,对照源码再过一遍
unbatchedUpdates
接下来是render的创建完fiberRoot后的另外一个分支unbatchedUpdates
const NoContext = /* */ 0b000000; const BatchedContext = /* */ 0b000001; const EventContext = /* */ 0b000010; const DiscreteEventContext = /* */ 0b000100; const LegacyUnbatchedContext = /* */ 0b001000; const RenderContext = /* */ 0b010000; const CommitContext = /* */ 0b100000; let executionContext = NoContext; export function unbatchedUpdates(fn, a) { const prevExecutionContext = executionContext; // 去除executionContext上的BatchedContext executionContext &= ~BatchedContext; // 往executionContext上添加LegacyUnbatchedContext executionContext |= LegacyUnbatchedContext; // 进行回调 try { return fn(a); } finally { executionContext = prevExecutionContext; if (executionContext === NoContext) { // 刷新同步任务队列 flushSyncCallbackQueue(); } } }
- 修改executionContext这个变量,让他含有LegacyUnbatchedContext,也就是非批量更新模式
- 然后就是fn()执行传入的回调函数updateContainer
- 最后会执行flushSyncCallbackQueue,刷新同步任务队列
updateContainer
/** * * @param {*} element render的第一个参数 * @param {*} fiberRoot fiberRoot * @param {*} parentComponent 第一次渲染为null * @param {*} callback render的第三个参数,一个回调 */ export function updateContainer(element, fiberRoot, parentComponent, callback) { const current = fiberRoot.current; // 这里得到的是到目前为止 react还能处理多少单位时间(1单位时间是10ms) const currentTime = requestCurrentTimeForUpdate(); const suspenseConfig = requestCurrentSuspenseConfig(); // 计算过期时间,主要用在concurrent模式时使用 const expirationTime = computeExpirationForFiber( currentTime, current, suspenseConfig ); // 创建一个更新链表 const update = createUpdate(expirationTime, suspenseConfig); update.payload = { element }; // 处理回调函数 callback = callback === undefined ? null : callback; // 把创建的update添加到fiber的updateQueue上面 enqueueUpdate(current, update); // 进入调度 scheduleWork(current, expirationTime); return expirationTime; }
- 这里主要是计算expirationTime,会在后面调度中使用到
- 创建一个更新链表的数据结构
- enqueueUpdate: 将创建的更新链表添加到fiber的fiber的updateQueue中
- scheduleWork: 进入调度
感觉写的不好,太难写了,但是,看在我辛苦的份上动动小手点个赞哈哈,3q
原文地址:https://segmentfault.com/a/1190000021883117