实现 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;
  }
}
相关推荐
万少3 小时前
HarmonyOS 开发必会 5 种 Builder 详解
前端·harmonyos
橙序员小站5 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
炫饭第一名7 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
王晓枫8 小时前
flutter接入三方库运行报错:Error running pod install
前端·flutter
符方昊8 小时前
React 19 对比 React 16 新特性解析
前端·react.js
ssshooter8 小时前
又被 Safari 差异坑了:textContent 拿到的值居然没换行?
前端
曲折8 小时前
Cesium-气象要素PNG色斑图叠加
前端·cesium
Forever7_8 小时前
Electron 淘汰!新的桌面端框架 更强大、更轻量化
前端·vue.js
不会敲代码18 小时前
前端组件化样式隔离实战:React CSS Modules、styled-components 与 Vue scoped 对比
css·vue.js·react.js
Angelial8 小时前
Vue3 嵌套路由 KeepAlive:动态缓存与反向配置方案
前端·vue.js