实现 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;
  }
相关推荐
DoraBigHead9 分钟前
this 的前世今生:谁在叫我,我听谁的
前端·javascript·面试
蓝婷儿1 小时前
每天一个前端小知识 Day 28 - Web Workers / 多线程模型在前端中的应用实践
前端
琹箐1 小时前
Ant ASpin自定义 indicator 报错
前端·javascript·typescript
小小小小小惠1 小时前
Responsetype blob会把接口接收的二进制文件转换成blob格式
前端·javascript
爱电摇的小码农1 小时前
【深度探究系列(5)】:前端开发打怪升级指南:从踩坑到封神的解决方案手册
前端·javascript·css·vue.js·node.js·html5·xss
kymjs张涛1 小时前
零一开源|前沿技术周报 #7
android·前端·ios
爱编程的喵1 小时前
React入门实战:从静态渲染到动态状态管理
前端·javascript
Tttian6222 小时前
npm init vue@latestnpm error code ETIMEDOUT
前端·vue.js·npm
患得患失9492 小时前
【前端】【组件库开发】【原理】【无框架开发】现代网页弹窗开发指南:从基础到优化
前端
AliciaIr2 小时前
深入React事件机制:解密“合成事件”与“事件委托”的底层奥秘
javascript·react.js