从零实现一个简易版 React:深入理解 Fiber 架构与协调算法

2 阅读1分钟

引言

在现代前端开发中,React 已经成为最流行的 UI 库之一。然而,许多开发者只是停留在 API 使用层面,对 React 内部的核心机制——特别是 Fiber 架构和协调算法——了解有限。本文将带你从零实现一个简易版的 React,深入剖析其核心原理,帮助你真正理解 React 是如何工作的。

一、React 核心架构演进

1.1 Stack Reconciler 的局限性

在 React 16 之前,React 使用的是 Stack Reconciler(栈协调器)。这个协调器使用递归的方式遍历虚拟 DOM 树,存在一个致命缺陷:一旦开始渲染,就无法中断

// 旧版递归渲染(伪代码)
function render(element, container) {
  // 深度优先遍历整个树
  const dom = createDOM(element);
  container.appendChild(dom);
  
  if (element.children) {
    element.children.forEach(child => {
      render(child, dom);  // 递归调用,无法中断
    });
  }
}

这种模式在大型应用或复杂组件树中会导致主线程长时间被占用,造成页面卡顿,用户体验下降。

1.2 Fiber 架构的革命

React 16 引入的 Fiber 架构解决了这个问题。Fiber 的核心思想是:

  • 增量渲染:将渲染工作拆分成多个小任务
  • 可中断、可恢复:利用浏览器的空闲时间执行任务
  • 优先级调度:不同更新有不同的优先级

二、实现简易版 React 核心

2.1 定义基础数据结构

首先,我们需要定义几个核心的数据结构:

// 1. 虚拟 DOM 元素
class Element {
  constructor(type, props) {
    this.type = type;
    this.props = props;
    this.key = props.key || null;
  }
}

// 2. Fiber 节点
class FiberNode {
  constructor(type, props) {
    // 节点基本信息
    this.type = type;           // 组件类型(div、span、函数组件等)
    this.props = props;         // 属性
    this.key = props.key;       // key 值
    
    // 节点关系
    this.return = null;         // 父节点
    this.child = null;          // 第一个子节点
    this.sibling = null;        // 兄弟节点
    
    // 渲染状态
    this.stateNode = null;      // 对应的真实 DOM 节点
    this.alternate = null;      // 上一次渲染的 Fiber 节点(用于 diff)
    
    // 副作用标记
    this.effectTag = null;      // 更新类型(PLACEMENT、UPDATE、DELETION)
    this.firstEffect = null;    // 第一个有副作用的子节点
    this.lastEffect = null;     // 最后一个有副作用的子节点
  }
}

// 3. 更新类型常量
const PLACEMENT = 'PLACEMENT';    // 新增节点
const UPDATE = 'UPDATE';          // 更新节点
const DELETION = 'DELETION';      // 删除节点

2.2 实现 createElement 函数

这是 JSX 编译后的产物,类似于 React.createElement:

function createElement(type, config, ...children) {
  const props = {};
  
  // 处理属性
  if (config) {
    for (let propName in config) {
      if (config.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }
  
  // 处理 children
  props.children = children.map(child => {
    // 处理文本节点
    if (typeof child === 'string' || typeof child === 'number') {
      return createTextElement(child);
    }
    return child;
  }).filter(child => child != null);
  
  return new Element(type, props);
}

function createTextElement(text) {
  return new Element('TEXT_ELEMENT', {
    nodeValue: text,
    children: []
  });
}

三、Fiber 协调算法实现

3.1 工作循环(Work Loop)

这是 Fiber 架构的核心,实现了可中断的渲染:

let nextUnitOfWork = null;      // 下一个工作单元
let wipRoot = null;             // 正在构建的 Fiber 树根节点
let currentRoot = null;         // 上一次渲染的 Fiber 树
let deletions = null;           // 需要删除的节点列表

// 开始渲染
function render(element, container) {
  wipRoot = new FiberNode('ROOT', { children: [element] });
  wipRoot.stateNode = container;
  deletions = [];
  nextUnitOfWork = wipRoot;
  
  // 启动工作循环
  requestIdleCallback(workLoop);
}

// 工作循环
function workLoop(deadline) {
  let shouldYield = false;
  
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }
  
  if (!nextUnitOfWork && wipRoot) {
    commitRoot();
  }
  
  if (nextUnitOfWork || wipRoot) {
    requestIdleCallback(workLoop);
  }
}

// 执行单个工作单元
function performUnitOfWork(fiber) {
  // 1. 创建当前节点的 DOM(如果有)
  if (!fiber.stateNode && fiber.type !== 'TEXT_ELEMENT') {
    fiber.stateNode = createDOM(fiber);
  }
  
  // 2. 协调子节点
  reconcileChildren(fiber);
  
  // 3. 返回下一个工作单元
  // 深度优先遍历:child -> sibling -> return
  if (fiber.child) {
    return fiber.child;
  }
  
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.return;
  }
  
  return null;
}

3.2 协调子节点(Reconciliation)

这是 React 的 diff 算法核心:

function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;
  
  elements = elements || wipFiber.props.children;
  
  while (index < elements.length || oldFiber != null) {
    const element = elements[index];
    let newFiber = null;
    
    // 比较新旧节点
    const sameType = oldFiber && element && element.type === oldFiber.type;
    
    if (sameType) {
      // 类型相同,更新节点
      newFiber = new FiberNode(
        oldFiber.type,
        element.props
      );
      newFiber.stateNode = oldFiber.stateNode;
      newFiber.alternate = oldFiber;
      newFiber.effectTag = UPDATE;
    }
    
    if (element && !sameType) {
      // 类型不同,创建新节点
      newFiber = new FiberNode(
        element.type,
        element.props
      );
      newFiber.effectTag = PLACEMENT;
    }
    
    if (oldFiber && !sameType) {
      // 删除旧节点
      oldFiber.effectTag = DELETION;
      deletions.push(oldFiber);
    }
    
    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }
    
    // 构建 Fiber 树结构
    if (index === 0) {
      wipFiber.child = newFiber;
    } else if (element) {
      prevSibling.sibling = newFiber;
    }
    
    prevSibling = newFiber;
    index++;
  }
}

3.3 提交阶段(Commit Phase)

协调完成后,一次性更新 DOM:

function commitRoot() {
  // 处理删除的节点
  deletions.forEach(commitWork);
  
  // 提交所有变更
  commitWork(wipRoot.child);
  
  // 保存当前 Fiber 树
  currentRoot = wipRoot;
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) return;
  
  let domParentFiber = fiber.return;
  while (!domParentFiber.stateNode) {
    domParentFiber = domParentFiber.return;
  }
  const domParent = domParentFiber.stateNode;
  
  if (fiber.effectTag === PLACEMENT && fiber.stateNode != null) {
    domParent.appendChild(fiber.stateNode);
  } else if (fiber.effectTag === UPDATE && fiber.stateNode != null) {
    updateDOM(
      fiber.stateNode,
      fiber.alternate.props,
      fiber.props
    );
  } else if (fiber.effectTag === DELETION) {
    commitDeletion(fiber, domParent);
  }
  
  // 递归提交子