「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。
实现
我们将分为8步,一步一步的实现一个小型的React
- 实现createElement函数
- 实现render函数
- Currnet Mode模式
- fibers
- render和commit阶段
- 协调器
- function组件
- hooks
render和commit阶段
React中,render阶段主要的工作是完成fiber tree的构建,commit阶段主要是执行dom操作,接下来我们继续改造代码。
function performUnitOfWork(fiber) {
// 创建dom
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
if (fiber.parent) { // 每次添加新节点
fiber.parent.dom.appendChild(fiber.dom);
}
// 为每个child创建fiber节点 省略。。。
// 返回下一个任务 省略。。。
}
我们来看一下上次实现的代码,这里存在一个问题,我们在每个小任务中都会向dom添加新节点。
还记得前面说过,当浏览器有空闲时间才执行任务,也就是说浏览器会打断我们的任务,如果还没有完成构建整个fiber tree,用户可能会看到不完整的UI,这并不是我们想要的效果。
所以我们要移除这段代码,怎么解决这个问题呢?我们可以用一个全局变量来记录这个未完成的fiber tree,因为所有这些任务都是在内存中实现的,将这个变量命名为workInProcessFiber
function performUnitOfWork(fiber) {
// 创建dom
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
// 为每个child创建fiber节点 省略。。。
// 返回下一个任务 省略。。。
}
function render(element, container) {
workInProcessFiber = {
dom: continer,
props: {
children: [element]
},
}
nextUnitOfWork = workInProcessFiber
}
let nextUnitOfWork = null;
let workInProcessFiber = null;
当完成所有任务时,也就是完成构建fiber tree时,render阶段已经完成,我们提交fiber tree进入commit阶段。
function performUnitOfWork(fiber) {
// 创建dom
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
// 为每个child创建fiber节点 省略。。。
// 返回下一个任务 省略。。。
}
function commitRoot() {
// TODO 执行dom操作
}
function render(element, container) {
workInProcessFiber = {
dom: continer,
props: {
children: [element]
},
}
nextUnitOfWork = workInProcessFiber
}
let nextUnitOfWork = null;
let workInProcessFiber = null;
function workLoop(deadline) {
let shouldYield = false
while (nextUnitOfWork && !shouldYield) { // 存在下一个任务并且存在空闲时间
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
shouldYield = deadline.timeRemaining() < 1
}
if (!nextUnitOfWork && wipRoot) { // 构建完成,提交root fiber
commitRoot();
}
requestIdleCallback(workLoop)
}
下面实现commitRoot方法,我们递归地将子节点加入到dom节点中,在这个方法里一次性执行完daom操作,就解决了可能会被浏览器打断从而会看到不完整UI的情况。
function performUnitOfWork(fiber) {
// 创建dom
if (!fiber.dom) {
fiber.dom = createDom(fiber);
}
// 为每个child创建fiber节点 省略。。。
// 返回下一个任务 省略。。。
}
function commitRoot() {
commitWork(workInProcessFiber.child);
workInProcessFiber = null;
}
function commitWork(fiber) {
// 执行dom操作
if (!fiber) {
return;
}
const domParent = fiber.parent.dom;
domParent.appendChild(fiber.dom);
commitWork(fiber.child);
commitWork(fiber.sibling);
}
function render(element, container) {
workInProcessFiber = {
dom: continer,
props: {
children: [element]
},
}
nextUnitOfWork = workInProcessFiber
}
总结
- 浏览器可能会打断fiber tree的构建,所以记录在内存中构建的workInProcessFiber
- render阶段的主要任务是从root开始,为每个child创建fiber节点,直到完成fiber tree的构建
- 当构建完成,就会提交root fiber,进入commit阶段