基于文章 实现 React 文本节点渲染。
本文将从三方面介绍如何渲染 Fragment 节点。
- 子节点
<>
- 根节点
<>
- 标签
<Fragment>
子节点 <>
js
let fragment1: any = (
<>
<h3>1</h3>
<h4>2</h4>
</>
)
const jsx = (
<div className="box border">
<h1 className="border">omg</h1>
<h2>react</h2>
omg2
{fragment1}
</div>
)
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(jsx);
Render 阶段
BeginWork 阶段
beginWork
函数增加 Fragment 点的 case。
js
function updateHostFragment(current: Fiber | null, workInProgress: Fiber) {
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren);
return workInProgress.child;
}
// 1. 处理当前 fiber,因为不同组件对应的 fiber 处理方式不同,
// 2. 返回子节点 child
export function beginWork(
current: Fiber | null,
workInProgress: Fiber
): Fiber | null {
switch (workInProgress.tag) {
// 根节点
case HostRoot:
return updateHostRoot(current, workInProgress);
// 原生标签,div、span...
case HostComponent:
return updateHostComponent(current, workInProgress);
// 文本节点
case HostText:
return updateHostText(current, workInProgress);
// Fragment
case Fragment:
return updateHostFragment(current, workInProgress);
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
"React. Please file an issue."
);
}
修改 createFiberFromTypeAndProps
函数,增加 Fragment 的 case。
js
// 根据 TypeAndProps 创建fiber
export function createFiberFromTypeAndProps(
type: any,
key: null | string,
pendingProps: any
) {
let fiberTag: WorkTag = IndeterminateComponent;
if (isStr(type)) {
// 原生标签
fiberTag = HostComponent;
} else if (type === REACT_FRAGMENT_TYPE) {
fiberTag = Fragment;
}
const fiber = createFiber(fiberTag, pendingProps, key);
fiber.elementType = type;
fiber.type = type;
return fiber;
}
CompleteWork 阶段
completeWork
函数增加 Fragment 的 case。
js
function completeWork(
current: Fiber | null,
workInProgress: Fiber
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case Fragment:
case HostRoot: {
return null;
}
case HostComponent: {
// 原生标签,type 是标签名
const { type } = workInProgress;
// 1. 创建真实 DOM
const instance = document.createElement(type);
// 2. 初始化 DOM 属性
finalizeInitialChildren(instance, newProps);
// 3. 把子 dom 挂载到父 dom 上
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
return null;
}
case HostText: {
workInProgress.stateNode = document.createTextNode(newProps);
return null;
}
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
"React. Please file an issue."
);
}
appendAllChildren
需要做相应的修改。
js
// fiber.stateNode是DOM节点
export function isHost(fiber: Fiber): boolean {
return fiber.tag === HostComponent || fiber.tag === HostText;
}
function appendAllChildren(parent: Element, workInProgress: Fiber) {
let nodeFiber = workInProgress.child; // 链表结构
while (nodeFiber !== null) {
if (isHost(nodeFiber)) {
parent.appendChild(nodeFiber.stateNode); // nodeFiber.stateNode是DOM节点
} else if (nodeFiber.child !== null) {
// 如果 node 这个 fiber 本身不直接对应 DOM 节点,那么就往上找它的子节点,直到找到有真实的 DOM 节点的 fiber 为止
nodeFiber = nodeFiber.child;
continue;
}
if (nodeFiber === workInProgress) {
return;
}
// 如果 nodeFiber 没有兄弟节点了,那么就往上找它的父节点
while (nodeFiber.sibling === null) {
if (nodeFiber.return === null || nodeFiber.return === workInProgress) {
return;
}
nodeFiber = nodeFiber.return;
}
nodeFiber = nodeFiber.sibling;
}
}
根节点 <>
js
let fragment1: any = (
<>
<h3>1</h3>
<h4>2</h4>
</>
)
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(fragment1);
Commit 阶段
commitPlacement
增加 Fragment 的 case。
js
function commitPlacement(finishedWork: Fiber) {
if (finishedWork.stateNode && isHost(finishedWork)) {
// finishedWork 是有 dom 节点
const domNode = finishedWork.stateNode
// 找 domNode 的父 DOM 节点对应的 fiber
const parentFiber = getHostParentFiber(finishedWork);
let parentDom = parentFiber.stateNode;
if (parentDom.containerInfo) {
// HostRoot
parentDom = parentDom.containerInfo;
}
parentDom.appendChild(domNode)
} else {
// Fragment
let kid = finishedWork.child;
while (kid !== null) {
commitPlacement(kid);
kid = kid.sibling;
}
}
}
标签 <Fragment>
js
let fragment1: any = (
<Fragment key='sy'>
<h3>1</h3>
<h4>2</h4>
</Fragment>
)
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(fragment1);
实现比较简单,就两句话。
js
export const REACT_FRAGMENT_TYPE: symbol = Symbol.for("react.fragment");
export { REACT_FRAGMENT_TYPE as Fragment } from "shared/ReactSymbols";