从0到1实现react(二):函数组件、类组件和空标签(Fragment)的初次渲染流程

整体架构流程图

graph TD A["ReactDOM.createRoot(container)"] --> B["创建 RootDOMRoot 实例"] B --> C["root.render(jsx)"] C --> D["updateContainer(children, containerInfo)"] D --> E["createFiber(children, returnFiber)"] E --> F{判断节点类型} F -->|"isStr(type)"| G["HostComponent
原生标签"] F -->|"isFn(type) && !isReactComponent"| H["FunctionComponent
函数组件"] F -->|"isFn(type) && isReactComponent"| I["ClassComponent
类组件"] F -->|"isUndefined(type)"| J["HostText
文本节点"] F -->|"其他 (Fragment)"| K["Fragment
空标签"] G --> L["scheduleUpdateOnFiber(rootFiber)"] H --> L I --> L J --> L K --> L L --> M["设置 wip = fiber, wipRoot = fiber"] M --> N["requestIdleCallback(workloop)"] N --> O["workloop 开始循环"] O --> P["performUnitOfWork()"] P --> Q{检查 wip.tag} Q -->|"HostComponent"| R["updateHostComponent(wip)"] R --> R1["创建 DOM 元素
document.createElement(type)"] R1 --> R2["updateNode(stateNode, props)
设置属性"] R2 --> R3["reconcileChildren(wip, props.children)"] Q -->|"FunctionComponent"| S["updateFunctionComponent(wip)"] S --> S1["调用函数组件
children = type(props)"] S1 --> S2["reconcileChildren(wip, children)"] Q -->|"ClassComponent"| T["updateClassComponent(wip)"] T --> T1["实例化类组件
instance = new type(props)"] T1 --> T2["调用 render 方法
children = instance.render()"] T2 --> T3["reconcileChildren(wip, children)"] Q -->|"HostText"| U["updateTextComponent(wip)"] U --> U1["创建文本节点
document.createTextNode(text)"] Q -->|"Fragment"| V["updateFragmentComponent(wip)"] V --> V1["reconcileChildren(wip, props.children)
直接处理子节点"] R3 --> W["遍历子节点创建 Fiber"] S2 --> W T3 --> W V1 --> W W --> X["深度优先遍历
如果有 child,继续处理"] X --> Y{是否还有 wip?} Y -->|"有"| P Y -->|"无"| Z["commitRoot(wipRoot)"] Z --> AA["commitWork(fiber)"] AA --> BB["递归提交所有节点"] BB --> CC{检查 flags} CC -->|"Placement"| DD["appendChild 到父节点"] CC -->|"其他"| BB DD --> EE["渲染完成"] style A fill:#e1f5fe style G fill:#fff3e0 style H fill:#e8f5e8 style I fill:#fce4ec style J fill:#f3e5f5 style K fill:#fff8e1 style EE fill:#e8f5e8

1. 渲染入口阶段

1.1 创建根容器

javascript 复制代码
// src/react-dom.js
function createRoot(container) {
  const root = {
    containerInfo: container,
  };
  return new RootDOMRoot(root);
}

1.2 触发渲染

javascript 复制代码
// src/react-dom.js
RootDOMRoot.prototype.render = function (children) {
  const { containerInfo } = this._internalRoot;
  updateContainer(children, containerInfo);
};

function updateContainer(children, containerInfo) {
  const rootFiber = createFiber(children, {
    type: containerInfo.nodeName.toLowerCase(),
    stateNode: containerInfo,
  });
  scheduleUpdateOnFiber(rootFiber);
}

2. Fiber 节点创建与类型识别

2.1 组件类型判断逻辑

javascript 复制代码
// src/ReactFiber.js
export function createFiber(vnode, returnFiber) {
  const fiber = {
    type: vnode.type,
    key: vnode.key,
    props: vnode.props,
    stateNode: null,
    child: null,
    sibling: null,
    return: returnFiber,
    flags: Placement,
    index: null,
    alternate: null,
  };

  // 判断 tag,确定 fiber 任务节点类型
  const { type } = vnode;
  if (isStr(type)) {
    // 原生标签:div, span, h1 等
    fiber.tag = HostComponent;
  } else if (isFn(type)) {
    // 函数组件、类组件
    fiber.tag = type.prototype.isReactComponent
      ? ClassComponent   // 类组件
      : FunctionComponent; // 函数组件
  } else if (isUndefined(type)) {
    // 文本节点
    fiber.tag = HostText;
    fiber.props = { children: vnode };
  } else {
    // Fragment 空标签
    fiber.tag = Fragment;
  }

  return fiber;
}

2.2 组件类型常量定义

javascript 复制代码
// src/ReactWorkTags.js
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;

3. 工作循环与调度

3.1 调度入口

javascript 复制代码
// src/ReactFiberWorkLoop.js
export function scheduleUpdateOnFiber(fiber) {
  wip = fiber;        // work in progress
  wipRoot = fiber;    // work in progress root
}

3.2 时间切片工作循环

javascript 复制代码
// src/ReactFiberWorkLoop.js
function workloop(deadline) {
  // 判断浏览器空闲时间是否可以执行任务
  while (wip && deadline.timeRemaining() > 1) {
    performUnitOfWork();
  }
  
  if (!wip && wipRoot) {
    commitRoot(wipRoot);
  }
}

// 使用浏览器空闲时间
requestIdleCallback(workloop);

3.3 工作单元处理

javascript 复制代码
// src/ReactFiberWorkLoop.js
function performUnitOfWork() {
  const { tag } = wip;
  
  // 根据组件类型分发处理函数
  switch (tag) {
    case HostComponent:
      updateHostComponent(wip);
      break;
    case HostText:
      updateTextComponent(wip);
      break;
    case ClassComponent:
      updateClassComponent(wip);
      break;
    case FunctionComponent:
      updateFunctionComponent(wip);
      break;
    case Fragment:
      updateFragmentComponent(wip);
      break;
    default:
      break;
  }

  // 深度优先遍历
  if (wip.child) {
    wip = wip.child;
    return;
  }

  let next = wip;
  while (next) {
    if (next.sibling) {
      wip = next.sibling;
      return;
    }
    next = next.return;
  }
  wip = null;
}

4. 各组件类型的具体处理

4.1 🟢 函数组件渲染流程

javascript 复制代码
// src/ReactFiberReconciler.js
export function updateFunctionComponent(wip) {
  const { type, props } = wip;
  
  // 直接调用函数组件获取子元素
  const children = type(props);
  
  // 协调子节点
  reconcileChildren(wip, children);
}

特点

  • ✅ 直接函数调用,性能高效
  • ✅ 无实例化开销
  • ❌ 不支持生命周期方法
  • ❌ 不创建 DOM 节点

4.2 🔴 类组件渲染流程

javascript 复制代码
// src/ReactFiberReconciler.js
export function updateClassComponent(wip) {
  const { type, props } = wip;
  
  // 1. 实例化类组件
  const instance = new type(props);
  
  // 2. 调用 render 方法获取子元素
  const children = instance.render();
  
  // 3. 协调子节点
  reconcileChildren(wip, children);
}

类组件基类

javascript 复制代码
// src/ClassComponent.js
function Component(props) {
  this.props = props;
}

// 区分函数组件与类组件的标识
Component.prototype.isReactComponent = {};

特点

  • ✅ 支持生命周期方法
  • ✅ 支持实例方法和状态
  • ❌ 实例化开销较大
  • ❌ 不创建 DOM 节点

4.3 🟡 Fragment 渲染流程

javascript 复制代码
// src/ReactFiberReconciler.js
export function updateFragmentComponent(wip) {
  // 直接处理子节点,不创建包装 DOM
  reconcileChildren(wip, wip.props.children);
}

特点

  • ✅ 透明容器,不产生额外 DOM
  • ✅ 可以返回多个子元素
  • ✅ 避免不必要的 DOM 嵌套
  • ❌ 不能接收除 children 外的 props

4.4 🔵 原生标签渲染流程

javascript 复制代码
// src/ReactFiberReconciler.js
export function updateHostComponent(wip) {
  // 1. 创建 DOM 元素
  if (!wip.stateNode) {
    const stateNode = document.createElement(wip.type);
    wip.stateNode = stateNode;
  }
  
  // 2. 更新节点属性
  updateNode(wip.stateNode, wip.props);
  
  // 3. 协调子节点
  reconcileChildren(wip, wip.props.children);
}

4.5 🟣 文本节点渲染流程

javascript 复制代码
// src/ReactFiberReconciler.js
export function updateTextComponent(wip) {
  const { props } = wip;
  const text = props.children;
  
  // 创建文本节点
  wip.stateNode = document.createTextNode(text);
}

5. 子节点协调机制

5.1 reconcileChildren 函数

javascript 复制代码
// src/ReactFiberReconciler.js
function reconcileChildren(wip, children) {
  // 跳过字符串和数字类型的直接子节点
  if (isStringOrNumber(children)) {
    return;
  }

  let newFiber = null;
  let previousNewFiber = null;
  
  // 确保 children 是数组
  let arr = Array.isArray(children) ? children : [children];
  
  // 为每个子元素创建 Fiber 节点
  for (let i = 0; i < arr.length; i++) {
    newFiber = createFiber(arr[i], wip);
    
    if (previousNewFiber === null) {
      // 第一个子节点
      wip.child = newFiber;
    } else {
      // 建立兄弟节点链表
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
  }
}

5.2 Fiber 树结构

php 复制代码
父 Fiber
├── child ──→ 第一个子 Fiber
                ├── sibling ──→ 第二个子 Fiber
                                ├── sibling ──→ 第三个子 Fiber
                                └── return ──→ 父 Fiber

6. 深度优先遍历策略

6.1 遍历逻辑

javascript 复制代码
// 深度优先遍历 (国王的故事)
if (wip.child) {
  // 优先处理子节点
  wip = wip.child;
  return;
}

let next = wip;
while (next) {
  if (next.sibling) {
    // 处理兄弟节点
    wip = next.sibling;
    return;
  }
  // 回到父节点
  next = next.return;
}
wip = null; // 遍历完成

6.2 遍历示例

css 复制代码
     A
   /   \
  B     C
 / \   /
D   E F

遍历顺序:A → B → D → E → C → F

7. 提交阶段 (Commit Phase)

7.1 提交入口

javascript 复制代码
// src/ReactFiberWorkLoop.js
function commitRoot(wipRoot) {
  commitWork(wipRoot);
  wipRoot = null;
}

7.2 DOM 操作提交

javascript 复制代码
// src/ReactFiberWorkLoop.js
function commitWork(fiber) {
  if (!fiber) {
    return;
  }
  
  const { flags, stateNode } = fiber;
  let parentNode = getParentNode(fiber);
  
  // 根据 flags 执行相应的 DOM 操作
  if (flags === Placement && stateNode) {
    parentNode.appendChild(stateNode);
  }
  
  // 递归提交子节点和兄弟节点
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

function getParentNode(fiber) {
  let next = fiber.return;
  while (next) {
    if (next.stateNode) {
      return next.stateNode;
    }
    next = next.return;
  }
}

8. 组件类型对比总结

特性 函数组件 类组件 Fragment 原生标签 文本节点
DOM 创建
执行方式 直接调用 实例化 + render 透传子节点 createElement createTextNode
性能开销 最低 最低
生命周期
状态管理 Hooks this.state
使用场景 无状态 UI 复杂逻辑 避免嵌套 实际 DOM 纯文本

9. 核心设计理念

9.1 Fiber 架构优势

  • 可中断渲染:支持时间切片,避免长时间阻塞主线程
  • 优先级调度:可以根据任务重要性调整执行顺序
  • 错误边界:更好的错误处理和恢复机制

9.2 组件设计原则

  • 单一职责:每种组件类型专注于特定功能
  • 组合优于继承:通过组合实现复杂 UI
  • 声明式编程:描述"是什么"而非"怎么做"

9.3 性能优化策略

  • 函数组件:适用于简单 UI,减少实例化开销
  • 类组件:适用于复杂逻辑,支持完整生命周期
  • Fragment:避免不必要的 DOM 嵌套,提升渲染性能

10. 实际使用示例

10.1 组件定义

javascript 复制代码
// 函数组件
function FunctionComponent(props) {
  return (
    <div className="border">
      <p>{props.name}</p>
      {props.children}
    </div>
  );
}

// 类组件
class ClassComponent extends Component {
  render() {
    return (
      <div className="border">
        <h3>{this.props.name}</h3>
        我是文本
      </div>
    );
  }
}

// Fragment 使用
function FragmentComponent() {
  return (
    <ul>
      <>
        <li>part1</li>
        <li>part2</li>
      </>
    </ul>
  );
}

10.2 渲染调用

javascript 复制代码
const jsx = (
  <div className="border">
    <h1>react</h1>
    <FunctionComponent name="函数组件">
      <span>子组件</span>
    </FunctionComponent>
    <ClassComponent name="类组件" />
    <>
      <div>Fragment 内容</div>
    </>
  </div>
);

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(jsx);
相关推荐
豆苗学前端10 分钟前
vue3+TypeScript 实现一个图片占位符生成器
前端·面试·github
neon120411 分钟前
Vue 3 父子组件通信核心机制详解:defineProps、defineEmits 与 defineExpose 完全指南
前端·javascript·vue.js·前端框架
Juchecar28 分钟前
Vue3 开发环境搭建及循序渐进学习指南
前端·javascript
Data_Adventure44 分钟前
@scqilin/phone-ui手机外观组件库
前端
一点一木1 小时前
Vue Vapor 事件机制深潜:从设计动机到源码解析
前端·vue.js·vapor
FSHOW1 小时前
记一次开源_大量SVG的高性能渲染
前端·react.js
小牛.7931 小时前
Web第二次作业
前端·javascript·css
二闹1 小时前
都2025了还要用Layui做下拉控件-我只能说你有水平
前端
Pikachu8031 小时前
揭秘 tyarn:一个为大型 TypeScript Monorepo 优化的 Yarn 性能猛兽
前端·javascript
用户49430538293802 小时前
大规模建筑自动贴图+单体化效果,cesium脚本
前端·javascript·算法