前言
这篇主要就是介绍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