基于文章 简易实现 React 页面初次渲染。
本文将实现多个原生标签子节点渲染,主要涉及 render 阶段的修改。
js
// 多个原生标签子节点
const jsx = (
<div className="box border">
<h1 className="border">omg</h1>
<h2>react</h2>
</div>
)
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(jsx);
BeginWork 阶段
reconcileChildFibers
函数增加子节点是数组的判断。
js
// 协调子节点
function createChildReconciler(shouldTrackSideEffects: boolean) {
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any
) {
// 检查 newChild 类型,单个节点
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
return placeSingleChild(
reconcileSingleElement(returnFiber, currentFirstChild, newChild)
);
}
}
}
// 子节点是数组
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild);
}
return null;
}
return reconcileChildFibers;
}
reconcileChildrenArray
函数协调节点数组。
js
// 根据 TypeAndProps 创建fiber
export function createFiberFromTypeAndProps(
type: any,
key: null | string,
pendingProps: any
) {
let fiberTag: WorkTag = IndeterminateComponent;
if (isStr(type)) {
// 原生标签
fiberTag = HostComponent;
}
const fiber = createFiber(fiberTag, pendingProps, key);
fiber.elementType = type;
fiber.type = type;
return fiber;
}
// 根据 ReactElement 创建Fiber
export function createFiberFromElement(element: ReactElement) {
const { type, key } = element;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(type, key, pendingProps);
return fiber;
}
function createChild(returnFiber: Fiber, newChild: any): Fiber | null {
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild);
created.return = returnFiber;
return created;
}
}
}
return null;
}
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<any>
) {
let resultFirstChild: Fiber | null = null; // 头结点
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let newIdx = 0; // 会在后面多个 for 循环中使用,所以没有定义到 for 循环语句中
// 初次渲染没有 oldFiber
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx]);
// null 在 react 中不渲染
if (newFiber === null) {
continue;
}
// 组件更新阶段,判断在更新前后的位置是否一致,如果不一致,需要移动
// 和 Vue 不同,react 存储使用的是链表,必须记录 index
// Vue 存储使用的是数组,天然带下标,不需要记录 index
newFiber.index = newIdx
// 第一个节点,不要用 newIdx 判断,因为有可能有 null,而 null 不是有效的 fiber
if (previousNewFiber === null) {
previousNewFiber = newFiber
} else {
previousNewFiber.sibling = newFiber
}
previousNewFiber = newFiber
}
return resultFirstChild
}
return resultFirstChild
}
CompleteWork 阶段
有了兄弟节点,需要遍历。
js
function appendAllChildren(parent: Element, workInProgress: Fiber) {
let nodeFiber = workInProgress.child; // 链表结构
while (nodeFiber !== null) {
parent.appendChild(nodeFiber.stateNode); // nodeFiber.stateNode是DOM节点
nodeFiber = nodeFiber.sibling;
}
}