手工实现 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
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()
};
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'); // 函数组件
export const PLACEMENT = Symbol.for('PLACEMENT');
export const UPDATE = Symbol.for('UPDATE');
export const DELETION = Symbol.for('DELETION');
高级篇
调度器 Scheduler
- 入口 sheduleRoot,从跟节点开始渲染和调度
- 每次调度两个阶段:
- diff & render 阶段,创建 Fiber,实例化 stateNode,收集 Effect;执行单元以 fiber 为维度拆分,可中断。
- 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 });
- 一个执行单元
- 开始 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
- 创建 DOM
- 创建子 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() { ... }
首次渲染
- 执行
ReactDOM.render()
- render 中创建 rootFiber,并
sheduleRoot(rootFiber)
- 设置
workInProgressRoot = rootFiber
,开始进入工作循环 - 执行
performWorkofUnit(fiber)
,遍历所有节点,创建 Fiber,收集 Effect,生成 DOM 节点 - 执行
commitRoot(rootFiber)
,将所有节点挂在 DOM 树上 currentRoot = workInProgressRoot
,然后workInProgressRoot = null
(currentRoot
指向页面当前的 Fiber Tree)
第一次更新
- 如果有 currentRoot,
workInProgressRoot.alternate = currentRoot
- 每次 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;
}
第二次+更新
workInProgressRoot = currentRoot.alternate;
workInProgressRoot.alternate = currentRoot;
- 每次 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 实现了另一套渲染更新机制。