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

2 阅读1分钟

前言

在当今前端开发领域,React 无疑是最受欢迎的 UI 库之一。然而,对于许多开发者来说,React 的内部工作机制仍然像一个"黑盒子"。本文将带你从零开始,实现一个极简版的 React,重点解析其核心的 Fiber 架构和协调算法。通过这个实践过程,你不仅能深入理解 React 的工作原理,还能掌握现代前端框架设计的核心思想。

一、React 核心架构演进

在深入代码实现之前,让我们先了解一下 React 架构的演进历程:

1.1 Stack Reconciler(React 15 及之前)

传统的协调算法使用递归方式遍历虚拟 DOM 树,这个过程是同步且不可中断的。当组件树很大时,会导致主线程被长时间占用,造成页面卡顿。

1.2 Fiber Reconciler(React 16+)

Fiber 架构引入了可中断的异步渲染机制,将渲染工作分割成多个小任务单元(Fiber 节点),允许浏览器在任务之间处理用户交互,从而实现更流畅的用户体验。

二、实现极简版 React 的核心模块

2.1 虚拟 DOM 结构

首先,我们定义虚拟 DOM 的基本结构:

// 虚拟 DOM 元素类型
const ElementTypes = {
  TEXT_ELEMENT: 'TEXT_ELEMENT',
  HOST_COMPONENT: 'HOST_COMPONENT',
  CLASS_COMPONENT: 'CLASS_COMPONENT',
  FUNCTION_COMPONENT: 'FUNCTION_COMPONENT'
};

// 创建虚拟 DOM 元素的工厂函数
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === 'object'
          ? child
          : createTextElement(child)
      )
    }
  };
}

// 创建文本元素
function createTextElement(text) {
  return {
    type: ElementTypes.TEXT_ELEMENT,
    props: {
      nodeValue: text,
      children: []
    }
  };
}

2.2 Fiber 节点结构

Fiber 是 React 的最小工作单元,每个 Fiber 节点对应一个组件或 DOM 元素:

class FiberNode {
  constructor(type, props) {
    // 节点标识
    this.type = type;
    this.props = props;
    this.key = props.key;
    
    // 节点关系
    this.parent = null;
    this.child = null;
    this.sibling = null;
    this.alternate = null; // 指向旧的 Fiber 节点
    
    // 节点状态
    this.stateNode = null; // 对应的真实 DOM 或组件实例
    this.pendingProps = props;
    this.memoizedProps = null;
    this.memoizedState = null;
    
    // 副作用标记
    this.effectTag = null;
    this.firstEffect = null;
    this.lastEffect = null;
    this.nextEffect = null;
    
    // 工作单元状态
    this.expirationTime = 0;
    this.updateQueue = null;
  }
}

2.3 协调算法核心实现

协调算法(Reconciliation)是 React 最核心的部分,它负责比较新旧虚拟 DOM 树的差异:

class Reconciler {
  constructor() {
    this.nextUnitOfWork = null;
    this.currentRoot = null;
    this.wipRoot = null;
    this.deletions = [];
  }
  
  // 开始渲染
  render(element, container) {
    this.wipRoot = {
      dom: container,
      props: {
        children: [element]
      },
      alternate: this.currentRoot
    };
    
    this.deletions = [];
    this.nextUnitOfWork = this.wipRoot;
    
    // 启动工作循环
    this.workLoop();
  }
  
  // 工作循环 - 模拟 React 的调度机制
  workLoop() {
    while (this.nextUnitOfWork) {
      this.nextUnitOfWork = this.performUnitOfWork(this.nextUnitOfWork);
    }
    
    if (!this.nextUnitOfWork && this.wipRoot) {
      this.commitRoot();
    }
  }
  
  // 执行单个工作单元
  performUnitOfWork(fiber) {
    // 1. 创建或更新 DOM
    if (!fiber.dom) {
      fiber.dom = this.createDom(fiber);
    }
    
    // 2. 协调子元素
    const elements = fiber.props.children;
    this.reconcileChildren(fiber, elements);
    
    // 3. 返回下一个工作单元
    if (fiber.child) {
      return fiber.child;
    }
    
    let nextFiber = fiber;
    while (nextFiber) {
      if (nextFiber.sibling) {
        return nextFiber.sibling;
      }
      nextFiber = nextFiber.parent;
    }
    
    return null;
  }
  
  // 协调子节点
  reconcileChildren(wipFiber, elements) {
    let index = 0;
    let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
    let prevSibling = null;
    
    while (index < elements.length || oldFiber) {
      const element = elements[index];
      let newFiber = null;
      
      // 比较新旧 Fiber
      const sameType = oldFiber && element && element.type === oldFiber.type;
      
      if (sameType) {
        // 类型相同,更新属性
        newFiber = {
          type: oldFiber.type,
          props: element.props,
          dom: oldFiber.dom,
          parent: wipFiber,
          alternate: oldFiber,
          effectTag: 'UPDATE'
        };
      }
      
      if (element && !sameType) {
        // 类型不同,创建新节点
        newFiber = {
          type: element.type,
          props: element.props,
          dom: null,
          parent: wipFiber,
          alternate: null,
          effectTag: 'PLACEMENT'
        };
      }
      
      if (oldFiber && !sameType) {
        // 删除旧节点
        oldFiber.effectTag = 'DELETION';
        this.deletions.push(oldFiber);
      }
      
      if (oldFiber) {
        oldFiber = oldFiber.sibling;
      }
      
      if (newFiber) {
        if (index === 0) {
          wipFiber.child = newFiber;
        } else {
          prevSibling.sibling = newFiber;
        }
        
        prevSibling = newFiber;
      }
      
      index++;
    }
  }
  
  // 创建真实 DOM
  createDom(fiber) {
    const dom =
      fiber.type === ElementTypes.TEXT_ELEMENT
        ? document.createTextNode('')
        : document.createElement(fiber.type);
    
    this.updateDom(dom, {}, fiber.props);
    return dom;
  }
  
  // 更新 DOM 属性
  updateDom(dom, prevProps, nextProps) {
    // 移除旧的属性
    Object.keys(prevProps)
      .filter(key => key !== 'children')
      .forEach(name => {
        if (name.startsWith('on')) {
          const eventType = name.toLowerCase().substring(2);
          dom.removeEventListener(eventType, prevProps[name]);
        } else {
          dom[name] = '';
        }
      });
    
    // 添加新的属性
    Object.keys(nextProps)
      .filter(key => key !== 'children')
      .forEach(name => {
        if (name.startsWith('on')) {
          const eventType = name.toLowerCase().substring(2);
          dom.addEventListener(eventType, nextProps[name]);
        } else {
          dom[name] = nextProps[name];
        }
      });
  }
  
  // 提交变更到 DOM
  commitRoot() {
    this.deletions.forEach(this.commitWork);
    this.commitWork(this.wipRoot.child);
    this.currentRoot = this.wipRoot;
    this.wipRoot = null;
  }
  
  commitWork(fiber) {
    if (!fiber) return;
    
    let domParentFiber = fiber.parent;
    while (!domParentFiber.dom) {
      domParentFiber = domParentFiber.parent;
    }
    const domParent = domParentFiber.dom;
    
    if (fiber.effectTag === 'PLACEMENT' && fiber.dom != null) {
      domParent.appendChild(fiber.dom);
    } else if (fiber.effectTag === 'UPDATE' && fiber.dom != null) {
      this.updateDom(
        fiber.dom,
        fiber.alternate.props,
        fiber.props
      );
    } else if (fiber.effectTag === 'DELETION') {
      this.commitDeletion(fiber, domParent);
    }
    
    this.commitWork(fiber.child);
    this.commitWork(fiber.sibling);
  }
  
  commitDeletion(fiber, domParent) {
    if (fiber.dom) {
      domParent.removeChild(fiber.dom);
    } else {
      this.commitDeletion(fiber.child, dom