commit前导
从上文 我们实现了fiber 链表结构 这样保证 react 可以实现 更新时能够暂停,终止,复⽤渲染任务。
但是 也是有弊端的, 也就是终止时 渲染fiber dom 也是 渲染了一部分,看到的是不完整的UI
存在问题,就是每次处理一个元素,都要向DOM添加一个新的节点,在完成整个树的渲染之前,由于做了可中断操作,那将看到一个不完整的UI,这样显然是不行的
为了解决以上渲染UI不完整问题 我们要改良渲染步骤
即 将 渲染DOM 和 生成Fiber分离
- step1 生成所有Fiber
- step2 将fiber节点 递归生成DOM
commit实现
step1: 对fiber 直接生成DOM操作 这段代码 注销
function performUnitOfWork(fiber: FiberProps): FiberProps | null | undefined {
...
// if (fiber.parent?.dom) {
// fiber.parent.dom.appendChild(fiber.dom)
// }
}
step2:创建 wipRoot
创建 wipRoot(work in progress root) 在进程中的树
/**
* 初始化第一个fiber节点
* */
function render(vDom: VDOMProps , container: Element) {
wipRoot = {
dom: container,
props: {
children: [vDom],
},
} as FiberProps
nextUnitOfWork = wipRoot
}
// work in progress root
let wipRoot = null as FiberProps | null | undefined;
function workLoop(deadline: any) {
let shouldYield = false
while (nextUnitOfWork && !shouldYield) {
...
}
// 当所有fiber都生成结束 开始 commitRoot 渲染DOM
if (!nextUnitOfWork && wipRoot) {
commitRoot()
}
...
}
step3: 渲染DOM
将wipRoot 递归遍历生成DOM
// 生成节点
function commitRoot() {
commitWork(wipRoot!.child)
// 生成结束后 初始化 wipRoot
wipRoot = null
}
function commitWork(fiber: FiberProps | null | undefined) {
if (!fiber) {
return
}
const domParent = fiber.parent!.dom;
// 更新dom节点
domParent && domParent.appendChild(fiber.dom as Element)
// 先遍历子工作格
commitWork(fiber.child)
// 再遍历兄弟工作格
commitWork(fiber.sibling)
}
以上代码地址 github.com/beewolf233/…
协调器
到目前为止,我们只实现了向DOM添加内容,所以接下来的目标我们实现更新和删除节点;
当执行更新时,我们要对比两棵fiber树(diff),对有变化的DOM进行更新 diff相关文章参考
对于fiber 数据结构发生了改变, 增加了 alternate(备胎) 用于记载 前fiber 节点 方便和 最新的节点进行diff比较
大致过程就是 同层 同位置节点进行比较
// 单个工作格类型
export type FiberProps = VDOMProps & {
/** 真实dom节点*/
dom: Element | null;
/** 父节点工作格 */
parent?: FiberProps;
/** 子节点工作格 父节点下第一个子节点 */
child?: FiberProps;
/** 相邻工作格 相邻的下一个兄弟节点 */
sibling?: FiberProps;
/** 属性 */
props: Omit<VDOMProps, 'children'> & {
children: FiberProps[]
},
/** 前工作格流 用于对比 相当于 前一个fiber 进行diff比较 */
alternate: FiberProps | null;
/** 副作用 标签 */
effectTag: 'UPDATE' | 'PLACEMENT' | 'DELETION'
}
step1 初始化 Render
// 当前fiber树
let currentRoot = null as FiberProps | null | undefined;
// 要删除的节点
let deletions = [] as FiberProps[]
/**
* 初始化第一个fiber节点
* */
function render(vDom: VDOMProps , container: Element) {
wipRoot = {
dom: container,
props: {
children: [vDom],
},
alternate: currentRoot,
} as FiberProps
nextUnitOfWork = wipRoot
deletions = []
}
step2 diff 比较当前节点 reconcileChildren
生成fiber部分 变为方法 reconcileChildren
function performUnitOfWork(fiber: FiberProps): FiberProps | null | undefined {
...
// 生成fiber
const elements = fiber.props.children;
reconcileChildren(fiber, elements)
...
}
/**
* 相同层级 children 遍历
* diff算法就发生在 调和阶段
* */
function reconcileChildren(wipFiber: FiberProps, elements: FiberProps[]) {
let index = 0
let oldFiber =
wipFiber.alternate && wipFiber.alternate.child
let prevSibling = null;
while (
index < elements.length ||
oldFiber != null
) {
const element = elements[index]
let newFiber = null as FiberProps | null
const sameType =
oldFiber &&
element &&
element.type === oldFiber.type
if (sameType) {
// TODO update the node
newFiber = {
type: oldFiber!.type,
props: element.props,
dom: oldFiber!.dom,
parent: wipFiber,
alternate: oldFiber!,
effectTag: "UPDATE",
}
}
if (element && !sameType) {
// TODO add this node
newFiber = {
type: element.type,
props: element.props,
dom: null,
parent: wipFiber,
alternate: null,
effectTag: "PLACEMENT",
}
}
if (oldFiber && !sameType) {
// TODO delete the oldFiber's node
oldFiber.effectTag = "DELETION"
deletions.push(oldFiber)
}
if (oldFiber) {
oldFiber = oldFiber.sibling
}
if (index === 0) {
wipFiber.child = newFiber as FiberProps
} else if (element) {
// 如果有兄弟节点 返回相邻兄弟工作格
if (prevSibling) {
(prevSibling as FiberProps).sibling = newFiber as FiberProps
}
}
prevSibling = newFiber
index++
}
}
step3 在commit阶段 更新DOM
// 生成节点
function commitRoot() {
// 先删除相应要删除的节点
deletions.forEach(commitWork)
commitWork(wipRoot!.child)
// 生成结束后 更新最新fiber树
currentRoot = wipRoot
// 生成结束后 初始化 wipRoot
wipRoot = null
}
function commitWork(fiber: FiberProps | null | undefined) {
if (!fiber) {
return
}
const domParent = fiber.parent!.dom;
if (
fiber.effectTag === "PLACEMENT" &&
fiber.dom != null
) {
// 新建dom节点
domParent!.appendChild(fiber.dom as Element)
} else if (
fiber.effectTag === "UPDATE" &&
fiber.dom != null
) {
// 更新dom节点
updateDom(
fiber.dom,
fiber.alternate!.props,
fiber.props
)
} else if (fiber.effectTag === "DELETION") {
// 删除dom节点
domParent!.removeChild(fiber.dom as Element)
}
// 先遍历子工作格
commitWork(fiber.child)
// 再遍历兄弟工作格
commitWork(fiber.sibling)
}
以上就完成了 协调器 增删改查Fiber DOM元素