执行顺序
ReactDOM.createRoot(root).render(App);
createRoot 方法
export function createRoot(container: Container) {
const root = createContainer(container);
return {
render(element: ReactElementType) {
updateContainer(element, root);
}
};
}
进入createContainer
/**
* 创建容器
* @param container
* @returns
*/
export function createContainer(container: Container) {
const hostRootFiber = new FiberNode(HostRoot, {}, null);
const root = new FiberRootNode(container, hostRootFiber);
hostRootFiber.updateQueue = createUpdateQueue();
return root;
}
进入 FiberRootNode
/**
* FiberRootNode
*/
export class FiberRootNode {
container: Container;
current: FiberNode;
finishedWork: FiberNode | null; // 最终更新完成以后的fiber树
/**
* @param container 容器 div#root
* @param hostRootFiber React的根节点
*/
constructor(container: Container, hostRootFiber: FiberNode) {
this.container = container;
this.current = hostRootFiber;
hostRootFiber.stateNode = this;
this.finishedWork = null;
}
}
接着创建了一个空的更新队列
最后返回root
形成一下的一个关联
我们接着调试,当创建好上面的一个FiberRootNode 与 hostRootFiber 链接关系后,就回去运行render方法
我们去调用自己的demo中可以看见,创建root后调用了render这个方法
import React from 'react';
import ReactDOM from 'react-dom';
const App = (
<div>
<span>hello react</span>
</div>
);
const root = document.getElementById('root');
debugger;
ReactDOM.createRoot(root).render(App);
console.log(React);
console.log(App);
console.log(ReactDOM);
而render里面渲染的是我们定义的App
const App = (
<div>
<span>hello react</span>
</div>
);
进入render方法后可以看见的是他直接去了updateContainer这个方法,
updateContainer的参数是element ,element是我们传入的jsx这个函数,root参数就 div#root这个根元素
import {
createContainer,
updateContainer
} from 'react-reconciler/src/fiberReconciler';
import { ReactElementType } from 'shared/ReactTypes';
import { Container } from './hostConfig';
export function createRoot(container: Container) {
const root = createContainer(container);
return {
render(element: ReactElementType) {
updateContainer(element, root);
}
};
}
updateContainer 这个方法接着进入了 createUpdate 其实就是创建了一个update对象
/**
* 更新容器
* @param element
* @param root
* @returns
*/
export function updateContainer(
element: ReactElementType | null,
root: FiberRootNode
) {
const hostRootFiber = root.current;
// update对象就是接下来要更新的element对象
const update = createUpdate<ReactElementType | null>(element);
// 给update赋值
enqueueUpdate(
hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,
update
);
scheduleUpdateOnFiber(hostRootFiber);
return element;
}
enqueueUpdate接受的就是 hostRootFiber.updateQueue 也就是在上文中所提到的创建了一个更新队列。
可以看到enqueueUpdate就是更新 update。 update 目前就是 我们的App函数组件,可能小伙伴已经忘了App 函数了。
const App = (
<div>
<span>hello react</span>
</div>
);
/**
* 向队列中添加更新 向updateQueue 里面增加一个update
* @param updateQueue 要更新的队列
* @param update 更新值
*/
export const enqueueUpdate = <State>(
updateQueue: UpdateQueue<State>,
update: Update<State>
) => {
updateQueue.shared.pending = update;
};
那么目前的内存中的一个状态
接着就调试到了scheduleUpdateOnFiber这个方法
这个方法就是去调用markUpdateFromFiberToRoot方法 接受的fiber是 hostRootFiber 对象
/**
* 在fiber上调度更新
* @param fiber
*/
export function scheduleUpdateOnFiber(fiber: FiberNode) {
// todo 调度功能
const root = markUpdateFromFiberToRoot(fiber);
renderRoot(root);
}
我们进入 markUpdateFromFiberToRoot 方法。
function markUpdateFromFiberToRoot(fiber: FiberNode) {
let node = fiber;
let parent = node.return;
// 找到根节点
while (parent !== null) {
node = parent;
parent = node.return;
}
if (node.tag === HostRoot) {
return node.stateNode;
}
return null;
}
因为进入的fiber参数是 hostRootFiber 所以 parent变量是一个 null 如果 不是一个null 就会去遍历寻找他的顶层节点 也就是 fiberRootNode。这里的意思就是我们更新就是从顶层向下更新的。
然后开始renderRoot, 他接受的是FiberRootNode 这个顶层节点。
function renderRoot(root: FiberRootNode) {
// 初始化
prepareFreshStack(root);
do {
try {
workLoop();
break;
} catch (e) {
if (__DEV__) {
console.error('workLoop发生错误', e);
}
wip = null;
}
} while (true);
const finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
// 这里已经生成了wip fiberNode树 树中带有 flags
commitRoot(root);
}
接着就去了prepareFreshStack方法。他做的是就是一个初始化一个 work in progress 可以理解为复制了一份节点
wip这个变量是一个全局变量哦,相当于链表的一个指针,指向我们正在处理的位置。
/**
* 创建一个新的fiberNode
* @param current 当前工作的fiberNode
* @param pendingProps 刚开始工作的props
* @returns 返回一个新的fiberNode
*/
export const createWorkInProgress = (
current: FiberNode,
pendingProps: Props
): FiberNode => {
let wip = current.alternate;
if (wip == null) {
// 首屏渲染时(mount)
wip = new FiberNode(current.tag, pendingProps, current.key);
wip.stateNode = current.stateNode;
wip.alternate = current;
current.alternate = wip;
} else {
// 非首屏渲染时(update)
wip.pendingProps = pendingProps;
wip.flags = NoFlags;
wip.subtreeFlags = NoFlags;
}
wip.type = current.type;
wip.updateQueue = current.updateQueue; // 共用同一个 updateQueue对象
wip.child = current.child;
wip.memoizedState = current.memoizedState;
wip.memoizedProps = current.memoizedProps;
return wip;
};
此时属于首屏渲染时 所以 wip现在是null
调试到这里可以理解为wip是复制了一份 hostRootFiber
目前的内存模型变成
然后把一些属性复制到wip上面去。
这里就是react的一个双缓存机制
随后进入workLoop方法,
这里的 do while 是确保 workLoop 成功的执行
而workLoop方法是去执行 preformUnitOfWork 方法
/**
* render阶段的入口
*/
function workLoop() {
while (wip !== null) {
preformUnitOfWork(wip);
}
}
preformUnitOfWork他接受的fiber 是 wip 并且目前的链表表头是在 hostRoot tag = 3
function preformUnitOfWork(fiber: FiberNode) {
// 代表子节点 递
const next = beginWork(fiber);
fiber.memoizedProps = fiber.pendingProps;
// 遍历到底层节点
if (next === null) {
// 就开始归
completeUnitOfWork(fiber);
} else {
wip = next;
}
}
而 beginWork 方法就是去判断目前是那个节点,首屏渲染 wip.tag = 3 进入 updateHostRoot方法
export function beginWork(wip: FiberNode) {
// 比较 返回子fiberNode
switch (wip.tag) {
case HostRoot:
return updateHostRoot(wip);
case HostComponent:
return updateHostComponent(wip);
case HostText:
return null;
case FunctionComponent:
return updateFunctionComponent(wip);
default:
if (__DEV__) {
console.warn('beginWork: 未知的tag类型');
}
break;
}
return null;
}
可以看到的是 updateHostRoot 中
baseState 参数是null 因为现在是初始化 这里这个 memoizedState 是没有的。
updateQueue 是拿出了 wip的updateQueue
pending 是 获得了 wip 中的App 这个element对象 也就是下面这个
const App = (
<div>
<span>hello react</span>
</div>
);
然后我们进入 processUpdateQueue 里面,由于我们的App是一个 element 对象随意直接走的else的逻辑 ,可以看到返回的 result 返回的是一个 element 对象
推出这个函数,此时的内存模型如下:
接着我们进入 reconcileChildren 这个方法
reconcileChildren 这个方法就是构建 fiber树的过程 ,然后它接受的参数
wip 就是 work in progress
nextChildren 就是拿到了 wip.memoizedState的 也就是 APP element 对象
/**
* 构建 fiber树的过程
* @param wip workInProgress
* @param children element对象
*/
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
// 获取父节点的currentNode
const current = wip.alternate;
if (current !== null) {
// update
wip.child = reconcileChildFibers(wip, current?.child, children);
} else {
// mount
wip.child = mountChildFibers(wip, null, children);
}
}
current 就是 hostRootFiber 这个节点
为什么在初次渲染走的update逻辑呢?
可以看到页面的内存图是这样的
第一挂载的时候 wip 是复制的了一份 hostRootFiber 会有属性altertnate 属性 左边是旧的节点,右边是新的节点。所以说这类似一个新节点替换旧节点是一个更新过程
如果说 没有alternate属性 就是挂在
if (current !== null) {
// update
wip.child = reconcileChildFibers(wip, current?.child, c hildren);
} else {
// mount
wip.child = mountChildFibers(wip, null, children);
}
reconcileChildFibers和mountChildFibers的逻辑是一样的, 只是shoulJTrackEffects的参数不一样,返回了一个新的函数。
update接受的参数是
returnFiber: wip
currentFiber: current?.child 也就是 hostRootFiber.child 属性 目前是null
newChild: app的element
现在我们进入 reconcileChildFibers 函数 我们发现 他是调用的另一个函数 childReconciler
export const reconcileChildFibers = childReconciler(true);
export const mountChildFibers = childReconciler(false);
进入
/**
* 用于处理子节点的协调器
* @param shouldTrackSideEffects 是否追踪副作用
*/
function childReconciler(shouldTrackSideEffects: boolean) {
function reconcileSingleElement(
returnFiber: FiberNode,
currentFiber: FiberNode | null,
element: ReactElementType
) {
// 根据element创建fiber
// 在这里给子节点挂在return 和 父fiber进行关联的
const fiber = createFiberFromElement(element);
fiber.return = returnFiber;
// 返回的是新子元素创建的fiber对象
return fiber;
}
function reconcileSingleTextNode(
returnFiber: FiberNode,
currentFiber: FiberNode | null,
content: string | number
) {
const fiber = new FiberNode(HostText, { content }, null);
fiber.return = returnFiber;
return fiber;
}
/**
* 插入单一的节点
* @param fiber
*/
function placeSingleChild(fiber: FiberNode) {
// fiber是wip的fiber 这是一个首屏渲染的过程
// 首屏渲染且追踪副作用时,才添加更新 flags
if (shouldTrackSideEffects && fiber.alternate === null) {
// TODO
fiber.flags |= Placement;
}
return fiber;
}
return function reconcileChildFibers(
returnFiber: FiberNode,
currentFiber: FiberNode | null,
newChild?: ReactElementType
) {
// 判断当前fiber的类型
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(returnFiber, currentFiber, newChild)
);
default:
if (__DEV__) {
console.warn(
'reconcileChildFibers: 未知的ReactElementType',
newChild
);
}
break;
}
}
// TODO 多节点的情况 ul>li*3
// 多个 Fragment 节点
if (Array.isArray(newChild)) {
// TODO: 暂时不处理
if (__DEV__) {
console.warn('未实现的 reconcile 类型', newChild);
}
}
// HOST_TEXT 文本节点的情况
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(returnFiber, currentFiber, newChild)
);
}
if (__DEV__) {
console.warn('reconcileChildFibers: 未知的ReactElementType', newChild);
}
return null;
};
}
一进来会进入reconcileSingleElement这个函数,根据element创建fiber,然后给子节点挂在return 和 父fiber进行关联的最后返回的是新子元素创建的fiber对象。
最后placeSingleChild 返回的fiber如下
更新完成之后 就返回出去
然后 Root节点更新完成
第一轮beginWork执行完后的状态如下:
wip 与 div标签建立联系,并且为flags打上Placement标记