基于文章 实现 React 多个原生标签子节点渲染。
本文将实现文本节点渲染,文本节点渲染分两种情况:
- 文本根节点
- 文本子节点
文本根节点
js
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render('omg');
Render 阶段
render 阶段的 beginWork
和 completeWork
都需要修改。
BeginWork 阶段
beginWork
函数增加文本节点的 case。
js
// 文本没有子节点,不需要协调
function updateHostText(current: Fiber | null, workInProgress: Fiber) {
return null;
}
// 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);
}
throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
"React. Please file an issue."
);
}
reconcileChildFibers
函数增加文本节点的判断。
js
// 创建文本 fiber
function createFiberFromText(content: string): Fiber {
const fiber = createFiber(HostText, content, null); // createFiber 参考文章 https://juejin.cn/post/7524141053446094875
return fiber;
}
// 文本
function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null, // todo 更新
textContent: string
) {
const created = createFiberFromText(textContent);
created.return = returnFiber;
return created;
}
// 协调子节点
function createChildReconciler(shouldTrackSideEffects: boolean) {
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any
) {
// 文本
if (isText(newChild)) {
return placeSingleChild(
reconcileSingleTextNode(returnFiber, currentFirstChild, newChild + "") // 如果文本节点是数字类型,需要转换为字符串类型
);
}
// 检查 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;
}
CompleteWork 阶段
completeWork
函数增加文本节点的 case。
js
function completeWork(
current: Fiber | null,
workInProgress: Fiber
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
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."
);
}
Commit 阶段
commitPlacement
中的 finishedWork.tag
增加 finishedWork.tag === HostText
判断。
js
function commitPlacement(finishedWork: Fiber) {
if (finishedWork.stateNode && (finishedWork.tag === HostComponent || finishedWork.tag === HostText)) {
// 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)
}
}
文本子节点
js
// 单个原生标签节点
const jsx = (
<div className="box border">
<h1 className="border">omg</h1>
<h2>react</h2>
omg2
</div>
)
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(jsx);
这种情况会走到 createChildReconciler
的数组分支中,需要修改 createChild
函数,增加 isText
的判断。
Render 阶段
BeginWork 阶段
js
function createChild(returnFiber: Fiber, newChild: any): Fiber | null {
if (isText(newChild)) {
const created = createFiberFromText(newChild + "");
created.return = returnFiber;
return created;
}
if (typeof newChild === "object" && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(newChild + ""); // 如果文本节点是数字类型,需要转换为字符串类型
created.return = returnFiber;
return created;
}
}
}
return null;
}