实现 React 文本节点渲染

基于文章 实现 React 多个原生标签子节点渲染

本文将实现文本节点渲染,文本节点渲染分两种情况:

  1. 文本根节点
  2. 文本子节点

文本根节点

js 复制代码
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render('omg');

Render 阶段

render 阶段的 beginWorkcompleteWork 都需要修改。

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;
  }
相关推荐
whyutianict_vv2 小时前
从 Web 前端到 HarmonyOS ArkTS:一次 AI 鸿蒙全栈智能体开发的迁移实录
前端·人工智能·harmonyos
qziovv3 小时前
前端转flutter——项目架构、初始化
前端·flutter
Ali885203 小时前
Python字符串方法速查表大全
前端·python
前端_刘师兄3 小时前
FAE工程师学习路线-进程
前端
执子念的飞鱼4 小时前
浏览器直接预览 Pages、Numbers、Keynote:iWork 格式真正难在哪
前端·javascript
alloc4 小时前
从延迟聚合到可解释诊断:MetricKit 的原理与工程化实践
前端
颜进强4 小时前
06 - OpenSpec change 从模糊想法到完整契约:new change / explore / propose 三连
前端·后端·ai编程
Cache技术分享4 小时前
499. Java 反射 - 获取类型上的注解
前端·后端
用户921080262864 小时前
左侧历史对话模块:使用 Conversations 搭建会话入口
前端
用户69371750013844 小时前
9531 款 AI 工具流量真相:当 90% 的访问涌向 100 个平台,普通创业者还有机会吗?
前端·后端