Skip to content

手工实现 React Fiber 原型

基础篇

一个例子

import React from 'react';
import ReactDOM from 'react-dom';

const element = (
  <div id="A1">
    <div id="B1" style={{ color: 'red' }}>
      B1 Text
      <div id="C1"></div>
    </div>
    <div id="B2"></div>
  </div>
);

ReactDOM.render(
  element,
  document.getElementById('root')
);

什么是 jsx

  • jsx 是语法糖
  • jsx 语法通过 babel 转化为 js 语法,内部调用了 React.createElement() 方法。

React.createElement

  • 创建虚拟 DOM
function createElement(type, config, ...children) {
  return {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    props: {
      ...config,
      children: children.map(child => {
        return typeof child === 'object' ? child : {
          type: ELEMENT_TEXT,
          props: { text: child, children: [] }
        };
      })
    }
  }
}

ReactDOM.render

  • 创建 rootFiber,
  • 从 rootFiber 开始调度 Fiber Tree
import { sheduleRoot } from './scheduler';

function render(element, container, callback) {
  const rootFiber = {
    tag: TAG_ROOT,

    stateNode: container, // 跟当前Fiber相关本地状态(比如浏览器环境就是DOM节点)

    props: {
      children: [element]
    }
  };

  sheduleRoot(rootFiber);
}

Fiber 数据结构

  • tag,Fiber 的 WorkTag
  • type,虚拟 DOM 的 type
  • stateNode,虚拟 DOM 的实例,可以是原生 DOM,或 FunctionComponent,或 ClassComponent 实例。
  • return,指向父亲 Fiber
  • child,长子 Fiber
  • sibling,右兄弟 Fiber
  • effectTag,副作用标签
  • nextEffect,串起一个 Effect List 单链表
  • firstEffect,Fiber Tree 中最左子孙 Effect
  • lastEffect,子孙中最后一次 Effect
  • updateQueue,更新 Fiber 的状态,单链表
let childFiber = {
  tag: tag,
  type: type,
  stateNode: null,
  props: props,

  return: fiber,
  child: null,
  sibling: null,

  effectTag: PLACEMENT,
  nextEffect: null,
  firstEffect: null,
  lastEffect: null,

  updateQueue:  new UpdateQueue()
};
  • Fiber.tag 枚举值
export const TAG_ROOT = Symbol.for('TAG_ROOT'); // root 
export const TAG_TEXT = Symbol.for('TAG_TEXT'); // text 
export const TAG_HOST_COMPONENT = Symbol.for('TAG_HOST_COMPONENT'); // 原生 DOM 组件
export const TAG_CLASS_COMPONENT = Symbol.for('TAG_CLASS_COMPONENT'); // 类组件
export const TAG_FUNCTION_COMPONENT = Symbol.for('TAG_FUNCTION_COMPONENT'); // 函数组件
  • Fiber.effectTag 枚举值
export const PLACEMENT = Symbol.for('PLACEMENT');
export const UPDATE = Symbol.for('UPDATE');
export const DELETION = Symbol.for('DELETION');

高级篇

调度器 Scheduler

  • 入口 sheduleRoot,从跟节点开始渲染和调度
  • 每次调度两个阶段:
    1. diff & render 阶段,创建 Fiber,实例化 stateNode,收集 Effect;执行单元以 fiber 为维度拆分,可中断。
    2. commit 阶段,执行 Effect,将 stateNode 挂在 DOM 树上;执行时不可中断。

sheduleRoot 入口

let workInProgressRoot = null; // 正在 schedule 的 fiber tree
let nextUnitOfWork = null; // 下一工作单元
function sheduleRoot(rootFiber) {
  console.log('------ 一次 schedule: diff & render 开始------');
  workInProgressRoot.firstEffect = null;
  workInProgressRoot.lastEffect = null;
  workInProgressRoot.nextEffect = null;

  nextUnitOfWork = workInProgressRoot;
}

workLoop

  • 基于 requestIdleCallback API 的循环
function workLoop(deadline) {
  let shoudYield = false; // 是否让出执行控制权
  while(nextUnitOfWork && !shoudYield) {
    nextUnitOfWork = performWorkofUnit(nextUnitOfWork);
    shoudYield = deadline.timeRemaining() < 1;
  }

  if (!nextUnitOfWork && workInProgressRoot) {
    console.log('----diff & render 结束, commit 开始-----');
    commitRoot(workInProgressRoot);
  }
  // 进入死循环
  requestIdleCallback(workLoop, { timeout: 500 });
}

requestIdleCallback(workLoop, { timeout: 500 });

performWorkofUnit 执行单元

  • 一个执行单元
  • 开始 beginWork
  • 结束 completeUnitOfWork
function performWorkofUnit(fiber) {
  beginWork(fiber);
  if (fiber.child) {
    return fiber.child;
  }

  while(fiber) {
    completeUnitOfWork(fiber);
    if (fiber.sibling) {
      return fiber.sibling;
    }
    fiber = fiber.return;
  }
}
beginWork
  1. 创建 DOM
  2. 创建子 Fiber
function beginWork(fiber) {
  if (fiber.tag === TAG_ROOT) {
    updateHostRoot(fiber);
  } else if (fiber.tag === TAG_TEXT) {
    updateHostText(fiber);
  } else if (fiber.tag === TAG_HOST_COMPONENT) {
    updateHost(fiber);
  } else if (fiber.tag === TAG_CLASS_COMPONENT) {
    updateClassComponent(fiber);
  } else if (fiber.tag === TAG_FUNCTION_COMPONENT) {
    updateFunctionComponent(fiber);
  } 
}

function updateHostRoot(fiber) {
  let newChildren = fiber.props.children;
  reconcileChildren(fiber, newChildren);
}
// ...
reconcileChildren
  • 通过 Vitual DOM 创建 Fiber && Diff
function reconcileChildren(fiber, newChildren) {
  let prevSibling;
  let newChildIndex = 0;
  let oldFiberChild = fiber.alternate && fiber.alternate.child;
  while(newChildIndex < newChildren.length || oldFiberChild) {
    let newFiberChild = { ... };

    // 移动指针
    if (newFiberChild) {
      if (!prevSibling) { // 长子
        fiber.child = newFiberChild;
      } else { // 其他孩子
        prevSibling.sibling = newFiberChild;
      }
      prevSibling = newFiberChild;
    }
    if (oldFiberChild) {
      oldFiberChild = oldFiberChild.sibling;
    }
    newChildIndex++;
  } 
}
completeUnitOfWork
  • 收集有 effect 的 fiber, 组成 effect list
function completeUnitOfWork(fiber) {
  console.log('completeUnitOfWork:', fiber.effectTag, fiber.type && fiber.type.name || fiber.type || fiber.tag, fiber.props.id || fiber.props.text);
  let returnFiber = fiber.return;

  if (returnFiber) {
    // firstEffect 指向最远最左孙子
    if (!returnFiber.firstEffect) {
      if (fiber.effectTag) {
        returnFiber.firstEffect = fiber;
      }
      if (fiber.firstEffect) {
        returnFiber.firstEffect = fiber.firstEffect;
      }
    }

    // lastEffect 指向最近完成的孩子
    if (fiber.lastEffect) {
      if (returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = fiber.firstEffect;
      }

      returnFiber.lastEffect = fiber.lastEffect;
    }

    if (fiber.effectTag) {
      if (returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = fiber;
      }

      returnFiber.lastEffect = fiber; // 向后移动 lastEffect 指针
    } 
  }
}

commitRoot

  • 渲染结束,将节点挂在 DOM 树上
  • 遍历 effect list,依次根据 effectTag 执行 DOM 操作
function commitRoot() {
  toDeletions.forEach(commitWork); // 先执行删除,再遍历 effect list
  toDeletions.length = 0;

  let fiber = workInProgressRoot.firstEffect;
  while(fiber) {
    commitWork(fiber);
    fiber = fiber.nextEffect;
  }

  currentRoot = workInProgressRoot;
  workInProgressRoot = null
}

function commitWork() { ... }

首次渲染

  1. 执行 ReactDOM.render()
  2. render 中创建 rootFiber,并 sheduleRoot(rootFiber)
  3. 设置 workInProgressRoot = rootFiber,开始进入工作循环
  4. 执行 performWorkofUnit(fiber),遍历所有节点,创建 Fiber,收集 Effect,生成 DOM 节点
  5. 执行 commitRoot(rootFiber),将所有节点挂在 DOM 树上
  6. currentRoot = workInProgressRoot,然后workInProgressRoot = nullcurrentRoot 指向页面当前的 Fiber Tree)

第一次更新

  1. 如果有 currentRoot,workInProgressRoot.alternate = currentRoot
  2. 每次 reconcileChildren,newChildFiber 与 fiber.alternate.child 建立关系,并 diff,然后移动指针
let workInProgressRoot = null; // 正在 schedule 的 fiber tree
let currentRoot = null; // 当前页面上的 fiber tree

let nextUnitOfWork = null; // 下一工作单元
function sheduleRoot(rootFiber) {
  console.log('------一次 schedule: diff & render 开始------');
  if (currentRoot) { // 第二次渲染(即第一次更新)
    if (rootFiber) {
      workInProgressRoot = rootFiber;
      workInProgressRoot.alternate = currentRoot;
    } else {
      workInProgressRoot = {
        ...currentRoot,
        alternate: currentRoot
      }
    }
  } else { // 首次渲染
    workInProgressRoot = rootFiber;
  }
  workInProgressRoot.firstEffect = null;
  workInProgressRoot.lastEffect = null;
  workInProgressRoot.nextEffect = null;

  nextUnitOfWork = workInProgressRoot;
}

第二次+更新

  1. workInProgressRoot = currentRoot.alternate; workInProgressRoot.alternate = currentRoot;
  2. 每次 reconcileChildren,newChildFiber = fiber.alternate.child.alternate(如果存在);
let workInProgressRoot = null; // 正在 schedule 的 fiber tree
let currentRoot = null; // 当前页面上的 fiber tree
let nextUnitOfWork = null; // 下一工作单元

function sheduleRoot(rootFiber) {
  console.log('------ 一次 schedule: diff & render 开始------');
  if (currentRoot && currentRoot.alternate) { // 第三次+渲染(即第二次+更新)
    workInProgressRoot = currentRoot.alternate;
    workInProgressRoot.alternate = currentRoot;
    if (rootFiber) {
      workInProgressRoot.props = rootFiber.props;
    }
  } else if (currentRoot) { // 第二次渲染(即第一次重新渲染)
    if (rootFiber) {
      workInProgressRoot = rootFiber;
      workInProgressRoot.alternate = currentRoot;
    } else {
      workInProgressRoot = {
        ...currentRoot,
        alternate: currentRoot
      }
    }
  } else { // 首次渲染
    workInProgressRoot = rootFiber;
  }

  workInProgressRoot.firstEffect = null;
  workInProgressRoot.lastEffect = null;
  workInProgressRoot.nextEffect = null;

  nextUnitOfWork = workInProgressRoot;
}

ClassComponent

一个例子

import React from './react';
import ReactDOM from './react-dom';

class ClassCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 10 };
  }

  handleClick() {
    this.setState(state => ({ count: state.count + 10 }));
  }

  render() {
    return (
      <div id="A1">
        <span id="B1">{this.state.count}</span>
        <button id="B2" onClick={this.handleClick.bind(this)}>+ADD</button>
      </div>
    )
  }
}

ReactDOM.render(
  <ClassCounter />,
  document.getElementById('root')
)

React.Component

class Component {
  constructor(props) {
    this.props = props;
  }
  setState(payload) {
    let update = new Update(payload);
    this._reactInternalFiber.updateQueue.enqueueUpdate(update);
    sheduleRoot();
  }
}
Component.prototype.isReactComponent = {};

UpdateQueue

  • Fiber 下的一个单链表
  • 每次 setState,enqueue 一个 Update,然后开始 sheduleRoot()
  • updateClassComponent 时,forceUpdate(),改变 baseState 状态,然后清空链表
  • 执行 fiber.stateNode.render(),然后 reconcileChildren
export class Update {
  constructor(payload, nextUpdate) {
    this.payload = payload; // 更新内容,比如`setState`接收的第一个参数
    this.nextUpdate = nextUpdate;
  }
}
export class UpdateQueue {
  constructor(baseState) {
    this.baseState = baseState; // 每次操作完更新之后的`state`
    this.firstUpdate = null; // 队首
    this.lastUpdate = null; // 队尾
  }

  enqueueUpdate(update) {
    if (this.firstUpdate === null) {
      this.firstUpdate = update;
      this.lastUpdate = update;
    } else {
      this.lastUpdate.nextUpdate = update;
      this.lastUpdate = update
    }
  }

  forceUpdate() {
    let currentState = this.baseState || {};
    let currentUpdate = this.firstUpdate;
    while(currentUpdate) {
      let nextState = typeof currentUpdate.payload === 'function'
        ? currentUpdate.payload(currentState)
        : currentUpdate.payload;
      currentState = {
        ...currentState,
        ...nextState
      };
      currentUpdate = currentUpdate.nextUpdate;
    }
    this.firstUpdate = this.lastUpdate = null; // 清空链表
    this.baseState = currentState;
  }
}

FunctionComponent 与 Hooks

一个例子

import React from './react';
import ReactDOM from './react-dom';

function reducer(state, action) {
  switch(action.type) {
    case 'ADD':
      return { count: state.count + 10 };
    default:
      return state;
  }
}

function FunctionCounter() {
  const [state1, dispatch] = React.useReducer(reducer, { count: 1000 }); // useReducer
  const [state2, setState2] = React.useState({ count: 2000 }); // useReducer

  return (
    <div id="A1">
      <span id="B1">{state1.count}</span>
      <button id="B2" onclick={() => dispatch({ type: 'ADD' })}>+ADD</button>
      <div id="Divider"></div>
      <span id="B3">{state2.count}</span>
      <button id="B4" onclick={() => setState2({ count: state2.count + 20 })}>+ADD</button>
    </div>
  )
}

ReactDOM.render(
  <FunctionCounter />,
  document.getElementById('root')
)

React.useReducer

  • hook 保存 updateQueue 和计算当前状态
  • 每次 dispatch,enqueueUpdate,并开始 sheduleRoot
  • 返回计算后的state,和 dispatch 函数
workInProgressFiber = null; // 全局变量:指向当前 Fiber
hookIndex = 0; // 全局变量:指向当前 Fiber 的当前 hook

function updateFunctionComponent(fiber) {
  workInProgressFiber = fiber;
  workInProgressFiber.hooks = [];
  hookIndex = 0;

  let newChildren = [fiber.type(fiber.props)];
  reconcileChildren(fiber, newChildren);
}
// reducer & hooks
function useReducer(reducer, initialState) {
  let newHook = workInProgressFiber.alternate &&
    workInProgressFiber.alternate.hooks &&
    workInProgressFiber.alternate.hooks[hookIndex];

  if (newHook) {
    newHook.updateQueue.forceUpdate();
    newHook.state = newHook.updateQueue.baseState;
  } else {
    newHook = {
      state: initialState,
      updateQueue: new UpdateQueue(initialState)
    };
  }

  function dispatch(action) {
    let payload = reducer ? reducer(newHook.state, action) : action;
    newHook.updateQueue.enqueueUpdate(new Update(payload));
    sheduleRoot();
  }

  workInProgressFiber.hooks[hookIndex++] = newHook;
  return [newHook.state, dispatch];
}

React.useState

function useState(initialState) {
  return useReducer(null, initialState);
}

总结

React 分层

  • Virtual DOM 层,描述页面长什么样。
  • Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。
  • Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative

设计思想

  • 默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象。
  • 解决主线程长时间被 JS 运算占用这一问题的基本思路,是将运算切割为多个步骤,分批完成。也就是说在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间进行页面的渲染。等浏览器忙完之后,再继续之前未完成的任务。
  • 旧版 React 通过递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。而Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。

React vs Vue

  • vue 把每次更新任务分割的很小;
  • react 每次更新任务还是很大,但分割成多个小任务,可以中断和恢复,不阻塞主进程执行高优先级任务;
  • vue 更忠于 javascript,而 react 实现了另一套渲染更新机制。