实现 React 多个原生标签子节点渲染

基于文章 简易实现 React 页面初次渲染

本文将实现多个原生标签子节点渲染,主要涉及 render 阶段的修改。

js 复制代码
// 多个原生标签子节点
const jsx = (
  <div className="box border">
    <h1 className="border">omg</h1>
    <h2>react</h2>
  </div>
)

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(jsx);

BeginWork 阶段

reconcileChildFibers 函数增加子节点是数组的判断。

js 复制代码
// 协调子节点
function createChildReconciler(shouldTrackSideEffects: boolean) {
  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any
  ) {
    // 检查 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;
}

reconcileChildrenArray 函数协调节点数组。

js 复制代码
// 根据 TypeAndProps 创建fiber
export function createFiberFromTypeAndProps(
  type: any,
  key: null | string,
  pendingProps: any
) {
  let fiberTag: WorkTag = IndeterminateComponent;

  if (isStr(type)) {
    // 原生标签
    fiberTag = HostComponent;
  }

  const fiber = createFiber(fiberTag, pendingProps, key);
  fiber.elementType = type;
  fiber.type = type;
  return fiber;
}

// 根据 ReactElement 创建Fiber
export function createFiberFromElement(element: ReactElement) {
  const { type, key } = element;
  const pendingProps = element.props;
  const fiber = createFiberFromTypeAndProps(type, key, pendingProps);
  return fiber;
}

function createChild(returnFiber: Fiber, newChild: any): Fiber | null {
    if (typeof newChild === "object" && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE: {
          const created = createFiberFromElement(newChild);
          created.return = returnFiber;
          return created;
        }
      }
    }

    return null;
}
  
function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array<any>
  ) {
    let resultFirstChild: Fiber | null = null; // 头结点
    let previousNewFiber: Fiber | null = null;
    let oldFiber = currentFirstChild;
    let newIdx = 0; // 会在后面多个 for 循环中使用,所以没有定义到 for 循环语句中

    // 初次渲染没有 oldFiber
    if (oldFiber === null) {
      for (; newIdx < newChildren.length; newIdx++) {
        const newFiber = createChild(returnFiber, newChildren[newIdx]);
        
        // null 在 react 中不渲染
        if (newFiber === null) {
          continue;
        }

        // 组件更新阶段,判断在更新前后的位置是否一致,如果不一致,需要移动
        // 和 Vue 不同,react 存储使用的是链表,必须记录 index
        // Vue 存储使用的是数组,天然带下标,不需要记录 index
        newFiber.index = newIdx

        // 第一个节点,不要用 newIdx 判断,因为有可能有 null,而 null 不是有效的 fiber 
        if (previousNewFiber === null) {
          previousNewFiber = newFiber
        } else {
          previousNewFiber.sibling = newFiber
        }
        previousNewFiber = newFiber
      }

      return resultFirstChild
    }

    return resultFirstChild
  }

CompleteWork 阶段

有了兄弟节点,需要遍历。

js 复制代码
function appendAllChildren(parent: Element, workInProgress: Fiber) {
  let nodeFiber = workInProgress.child; // 链表结构
  while (nodeFiber !== null) {
    parent.appendChild(nodeFiber.stateNode); // nodeFiber.stateNode是DOM节点
    nodeFiber = nodeFiber.sibling;
  }
}
相关推荐
OpenTiny社区35 分钟前
Coding Agent 到底是怎么工作的,它又为什么会在复杂代码库里失真?
前端·github·agent
奇奇怪怪的40 分钟前
Graph RAG:知识图谱增强检索
前端
默_笙1 小时前
🌏 MCP 是 AI 界的 USB-C:一个协议让所有模型都能用上所有工具
前端·javascript
以和为贵1 小时前
🔥前端也能搞懂流式输出:从 SSE 到打字机效果
前端·人工智能·架构
阿成学长_Cain2 小时前
Linux telinit 命令详解:运行级别切换|关机重启|系统维护一站式掌握
linux·运维·前端·网络
Patrick_Wilson2 小时前
最佳实践是有保质期的:从一次 CDN external 白屏事故说起
前端·性能优化·前端工程化
YHHLAI2 小时前
[特殊字符] Agent 智能体开发实战 · 第一课:Tool Use —— 让大模型自动干活
前端·人工智能
nuIl3 小时前
我把 5 个编码 Agent 塞进了一个 npm 包
前端·agent·claude
L-影3 小时前
FastAPI 静态文件:Web 页面的“固定展柜”与“加速引擎”
前端·fastapi
陪我去看海3 小时前
受够了 AI 写完代码留下一堆端口?我做了一个端口管理App!
前端·macos·vibecoding