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

90 阅读4分钟

仓库地址:github.com/zhuxin0/min…

整体架构流程图

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<br/>原生标签"]
    F -->|"isFn(type) && !isReactComponent"| H["FunctionComponent<br/>函数组件"]
    F -->|"isFn(type) && isReactComponent"| I["ClassComponent<br/>类组件"]
    F -->|"isUndefined(type)"| J["HostText<br/>文本节点"]
    F -->|"其他 (Fragment)"| K["Fragment<br/>空标签"]
    
    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 元素<br/>document.createElement(type)"]
    R1 --> R2["updateNode(stateNode, props)<br/>设置属性"]
    R2 --> R3["reconcileChildren(wip, props.children)"]
    
    Q -->|"FunctionComponent"| S["updateFunctionComponent(wip)"]
    S --> S1["调用函数组件<br/>children = type(props)"]
    S1 --> S2["reconcileChildren(wip, children)"]
    
    Q -->|"ClassComponent"| T["updateClassComponent(wip)"]
    T --> T1["实例化类组件<br/>instance = new type(props)"]
    T1 --> T2["调用 render 方法<br/>children = instance.render()"]
    T2 --> T3["reconcileChildren(wip, children)"]
    
    Q -->|"HostText"| U["updateTextComponent(wip)"]
    U --> U1["创建文本节点<br/>document.createTextNode(text)"]
    
    Q -->|"Fragment"| V["updateFragmentComponent(wip)"]
    V --> V1["reconcileChildren(wip, props.children)<br/>直接处理子节点"]
    
    R3 --> W["遍历子节点创建 Fiber"]
    S2 --> W
    T3 --> W
    V1 --> W
    
    W --> X["深度优先遍历<br/>如果有 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 创建根容器

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

1.2 触发渲染

// 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 组件类型判断逻辑

// 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 组件类型常量定义

// 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 调度入口

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

3.2 时间切片工作循环

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

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

3.3 工作单元处理

// 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 🟢 函数组件渲染流程

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

特点

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

4.2 🔴 类组件渲染流程

// 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);
}

类组件基类

// src/ClassComponent.js
function Component(props) {
  this.props = props;
}

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

特点

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

4.3 🟡 Fragment 渲染流程

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

特点

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

4.4 🔵 原生标签渲染流程

// 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 🟣 文本节点渲染流程

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

5. 子节点协调机制

5.1 reconcileChildren 函数

// 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 树结构

Fiber
├── child ──→ 第一个子 Fiber
                ├── sibling ──→ 第二个子 Fiber
                                ├── sibling ──→ 第三个子 Fiber
                                └── return ──→ 父 Fiber

6. 深度优先遍历策略

6.1 遍历逻辑

// 深度优先遍历 (国王的故事)
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 遍历示例

     A
   /   \
  B     C
 / \   /
D   E F

遍历顺序:AB → D → E → C → F

7. 提交阶段 (Commit Phase)

7.1 提交入口

// src/ReactFiberWorkLoop.js
function commitRoot(wipRoot) {
  commitWork(wipRoot);
  wipRoot = null;
}

7.2 DOM 操作提交

// 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透传子节点createElementcreateTextNode
性能开销最低最低
生命周期
状态管理Hooksthis.state
使用场景无状态 UI复杂逻辑避免嵌套实际 DOM纯文本

9. 核心设计理念

9.1 Fiber 架构优势

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

9.2 组件设计原则

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

9.3 性能优化策略

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

10. 实际使用示例

10.1 组件定义

// 函数组件
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 渲染调用

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);