实现 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;
  }
相关推荐
胡斌附体10 分钟前
小程序难调的组件
前端·小程序·apache·datepicker·自定义组件·checkbox
Mintopia26 分钟前
AIGC Claude(Anthropic)接入与应用实战:从字节流到智能交互的奇妙旅程
前端·javascript·aigc
Mintopia28 分钟前
Next.js 样式魔法指南:CSS Modules 与 Tailwind CSS 实战
前端·javascript·next.js
用户214118326360240 分钟前
dify案例分享-解锁 AI 搜索新玩法:Dify 秘塔搜索工作流搭建教程与效果展示
前端
Stefan的技术笔记1 小时前
LangChain入门指南:5大核心组件解析,快速上手AI应用开发!
前端·langchain
悟空和大王1 小时前
video标签自定义控制按钮--全屏与非全屏--播放与暂停
前端
excel1 小时前
javascript 简介
前端
国家不保护废物1 小时前
跨域问题:从同源策略到JSONP、CORS实战,前端必知必会
前端·javascript·面试
顾辰逸you1 小时前
React+Ts项目(网易云音乐)四
react.js
暮星1 小时前
从 HTML 到屏幕像素:一次性讲清浏览器渲染流程
前端·浏览器