React 17.x 原理篇
React 是 Meta(原 Facebook)推出的开源 JavaScript 库,在 MVC(Model-View-Controller)架构中承担视图层,专注用户界面构建。
React 核心特性:
- 声明式编写 UI:描述期望界面状态,框架负责 DOM 实际更新,代码健壮易调试。
- 组件化:封装拥有自身状态的组件,通过组件组合搭建复杂 UI。
- 状态驱动渲染:基于状态定义视图,数据变更后 React 高效完成组件更新渲染。
- 跨平台能力:可渐进接入已有项目,支持 Node 服务端渲染;依托 React Native 开发移动端原生应用。
React 的核心抽象是组件,类似可复用的自定义 HTML 元素。同时依靠 state、props 管理组件数据。
备注:React 17 不支持完全不兼容 ES5 的老旧浏览器。如需在 IE9 等环境运行,需要自行引入 es5-shim、es5-sham 等 polyfill,React 本身不会提供相关兼容补丁。
核心原理
虚拟 DOM(Virtual DOM)
React 在内存中为每一个组件维护对应的虚拟 DOM 树。业务代码不会直接操作真实 DOM。
组件状态变更时,React 会完整重建虚拟 DOM,对比新旧两棵虚拟 DOM,找出变化补丁(Patches),最后将补丁应用到真实 DOM。整套比对逻辑运行在内存,以此提升渲染性能。
React 将每一个组件视作状态机,组件拥有完整生命周期,分为初始化、更新、销毁三大阶段。生命周期钩子反映组件状态,开发者可以在对应阶段执行业务逻辑。
通俗理解:React 生成不同状态下的组件,以最小开销完成 DOM 更新。
快速响应
官方文档对 React 的定位:
我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式。它在 Facebook 和 Instagram 上表现优秀。
大型 Web 应用会遇到两类制约快速响应的瓶颈:
- CPU 瓶颈:大量 JS 计算、设备性能不足造成页面掉帧卡顿;
- IO 瓶颈:网络请求等待,用户需要等待接口返回才能继续交互。
浏览器刷新频率普遍为 60Hz,单帧预算 1000ms / 60 = 16.6ms。
GUI 渲染线程与 JS 线程互斥,JS 脚本、样式布局、样式绘制必须在一帧 16.6ms 窗口内完成。JS 执行时间超过预算,浏览器就没有时间布局绘制,页面发生卡顿。
CPU 瓶颈解决方案:时间切片(time slice)
在浏览器每一帧中预留一部分时间执行 JS(React 源码预留初始时间为 5ms);时间耗尽,交还主线程给浏览器渲染 UI,下一帧再继续未完成任务。
实现时间切片的底层关键:同步更新改造为可中断的异步更新。
IO 瓶颈解决方案
网络延迟前端无法消除。React 通过 Suspense 做交互优化,降低用户对网络延迟的感知;同样依赖可中断异步更新底层能力。
总结:React 实现大型应用快速响应的底层核心 —— 将同步的更新变为可中断的异步更新。
React 15 架构
React 15 是两层架构:
- Reconciler(协调器):识别发生变化的组件;
- Renderer(渲染器):将变更渲染到宿主环境。
Reconciler(协调器)
触发更新入口:this.setState、this.forceUpdate、ReactDOM.render。
协调器工作流程:
- 执行 class、函数组件
render,JSX 转换为虚拟 DOM; - 新旧虚拟 DOM 对比;
- 提取变更的虚拟 DOM;
- 通知 Renderer 执行渲染。
React 15 的协调器使用递归实现,调用信息保存在调用栈,称为 Stack Reconciler;递归一旦启动不可中断。
Renderer(渲染器)
React 多平台靠不同 Renderer 实现:
- ReactDOM:浏览器 DOM;
- ReactNative:移动端原生组件;
- ReactTest:输出 JS 对象,单元测试;
- ReactArt:渲染 Canvas、SVG、VML。
Renderer 接收协调器通知,把变更渲染到对应宿主。
架构核心缺陷
Stack Reconciler 递归过程无法暂停。组件树层级很深时,递归耗时超过 16.6ms,阻塞主线程,用户交互卡顿。
React 16 架构
React 16 重构为可中断异步更新,三层架构:
- Scheduler(调度器):管理任务优先级,高优任务优先进入协调器;
- Reconciler(协调器):识别变化组件;
- Renderer(渲染器):执行宿主渲染。
Scheduler 是 React 16 新增模块,是异步可中断更新的关键。
Scheduler(调度器)
目标:浏览器有空闲的时候执行任务。
浏览器原生 API requestIdleCallback 本可以实现该能力,但 React 放弃直接使用,原因:
- 浏览器兼容性;
- 触发频率不稳定,切换标签页后回调执行会被大幅节流。
React 自研一套增强版 polyfill,即独立库 Scheduler,除空闲回调外,还支持多优先级任务设置。
原生 requestIdleCallback API:浏览器空闲时期执行回调,不抢占动画、输入等高优先级任务,可以设置超时时间。
Reconciler(协调器)
React 15 使用递归;React 16 使用可中断循环。每次循环执行单元任务前调用 shouldYield() 判断帧剩余时间。
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
}
Scheduler、Reconciler 全部运行在内存,不会操作真实 DOM;全部协调工作结束,一次性交给 Renderer。
React 16 的协调器基于 Fiber 节点实现,称为 Fiber Reconciler。
Renderer(渲染器)
读取协调器打上的增、删、更新标记,同步执行宿主 DOM 操作。
Scheduler、Reconciler 和平台无关,抽离为独立包 react-reconciler。
性能优化思路
性能开销主要集中在 update 阶段 Diff 比对。
优化两大方向:
减少 update 流程触发:触发更新来源
setState、父组件 render、forceUpdate。class 组件使用shouldComponentUpdate根据业务条件跳过不必要渲染,函数组件使用React.memo、useMemo、useCallback。Diff 算法预设约束(降低复杂度)
仅做同层级 Diff;节点跨层级直接销毁重建,不复用;
元素类型不同直接销毁旧子树重建;例如
<div>→<p>;使用
key标记子元素稳定身份,提升列表节点复用。
核心概念
Virtual DOM(虚拟 DOM)
Virtual DOM 是编程概念:UI 使用 JS 对象存放 DOM 信息在内存,再同步到真实 DOM,同步过程叫协调(Reconciliation)。
实现声明式 API:开发者描述期望 UI 状态,React 保证 DOM 和状态匹配,减少手动 DOM 操作。
Virtual DOM 是一种模式而非单一类,React 体系中包含两套对象承担虚拟 DOM:
- React Element(对外描述 UI)
- React Fiber(内部协调运算)
React Element
React Element 是轻量 JS 对象,描述屏幕上应该渲染什么内容,是虚拟 DOM 的对外表现。
和浏览器 DOM 对象不是同一个东西,React 根据 Element 生成、更新 DOM。
React Fiber
Fiber 是计算机术语纤程。
React Fiber 是 React 16 全新协调引擎,属于内部虚拟 DOM 实现。
核心能力增量渲染:把渲染任务拆分成小块分散到多帧执行,改善动画、布局、手势交互体验。
React Fiber 保存组件实例、DOM 引用、更新标记、链表指针等上下文,React Fiber 树是双向链表结构,依靠指针遍历,不再是简单递归树。
function FiberNode(tag, pendingProps, key, mode) {
// ...
this.stateNode = null; // Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode; // Effects
this.flags = NoFlags;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
// ...
}
组件
组件是可复用代码片段,返回 React Element。概念类似 JS 函数,接收入参 props。
组件组成要素:
- 由原生 HTML、其他组件组合;
- Props:组件外部传入属性;
- State:组件内部状态;
- 生命周期钩子(class 组件)。
组件分为函数组件、class 组件:
// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// class 组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
JSX
JSX(JavaScript + XML)是 JS 语法扩展,拥有完整 JS 能力。
JSX 是 React.createElement(component, props, ...children) 的语法糖,最终被编译为 React.createElement() 函数调用,编译产出 React Element JS 对象。
import React from 'react';
const element = <h1>Hello, world!</h1>;
React 认为渲染逻辑和事件、状态、数据天然耦合,所以选择 JSX。JSX 不是强制使用的,可以直接使用 createElement。
生命周期(Class 组件)
class 组件生命周期分为挂载、更新、卸载三大阶段:
- 挂载 Mounting(组件插入 DOM)
constructor():组件挂载前执行构造函数;getDerivedStateFromProps():render 前执行,挂载、更新都会触发;render():class 组件唯一必须实现方法;componentDidMount():组件挂载到 DOM 之后执行。
- 更新 Updating(state、props 发生变化)
getDerivedStateFromProps()shouldComponentUpdate():props、state 变化触发,控制是否继续渲染;render()getSnapshotBeforeUpdate():提交 DOM 更新前获取快照;componentDidUpdate():DOM 更新完成后执行。
- 卸载 Unmounting(组件从 DOM 移除)
componentWillUnmount():卸载销毁前执行,清理定时器、事件、请求。
工作流程
使用 React.createElement() 生成 React Element 树(JSX 是语法糖);ReactDOM.render() 将根 Element 挂载到真实 DOM。
React 的整体工作流程分为编译阶段、初始化渲染流程、更新渲染流程三大部分。
完整流程:
- 元素类型声明阶段:原生 HTML 标签、自定义组件;
- 创建 React 元素:
React.createElement产出 React Element; - render 准备阶段:处理属性、事件、children,做浏览器兼容,将 React Element 转换为 Fiber 节点,构建 Fiber 树;
- render 阶段:遍历 Fiber 树,收集副作用标记(内存运算,不操作 DOM);
- 更新阶段:
state、props变更触发更新。props变更来自父组件状态更新;state变更来自组件自身调用更新 API。
备注:初始化渲染与更新渲染,共享一套调度 render、commit 流程,区别在于初始化没有旧的 current Fiber,更新会拿 current 和 wip Fiber 做 Diff。
编译阶段
JSX 并不是浏览器原生可识别语法,需要 Babel 预编译。
- Babel 将 JSX 语法编译为
React.createElement()函数调用; createElement返回普通 JavaScript 对象,即 React Element(React 元素,虚拟 DOM 对象),用来描述页面 UI 长什么样。
初始化渲染
从调用 ReactDOM.render() 开始,到页面真实 DOM 渲染完成。
ReactDOM.render(element, container)
└── legacyRenderSubtreeIntoContainer
├─ 首次渲染:legacyCreateRootFromDOMContainer 创建 FiberRoot + HostRootFiber
└── updateContainer 封装更新任务
└── enqueueUpdate:将更新加入 Fiber 更新队列
└── scheduleUpdateOnFiber 【统一更新入口】
└── performSyncWorkOnRoot
├─ renderRootSync 【render协调阶段,内存中构建Fiber树】
│ ├── prepareFreshStack:创建 workInProgress(wip)双缓存副本
│ └── workLoopSync 循环执行单元任务
│ ├─ performUnitOfWork
│ │ ├─ beginWork:向下遍历,生成子Fiber节点,调用render,执行reconcileChildren做diff
│ │ └─ completeUnitOfWork:向上回溯,HostComponent创建DOM对象,保存到stateNode
│ └─ 收集effect副作用链表(增、删、改标记)
└── commitRoot 【commit提交阶段,真正操作DOM】
├─ commitBeforeMutationEffects:DOM修改前,执行getSnapshotBeforeUpdate
├─ commitMutationEffects:执行DOM增删改、移动
└─ commitLayoutEffects:DOM修改后,执行componentDidMount,执行事件绑定
render(协调阶段)
- 全部工作在内存中完成,不会操作浏览器真实 DOM;
- 基于双向链表 Fiber 节点遍历,不是递归;
- 给需要改动的 Fiber 打上副作用 flags(Placement、Update、Deletion);
- 构建出 effectList 副作用链表,交给 commit 阶段处理。
commit(提交阶段)
- 同步、不可中断,按照 effectList 依次执行;
- 分三个子阶段:变更前、DOM 变更、变更后;
- 执行真实 DOM API,执行生命周期钩子,绑定合成事件。
更新渲染
setState、forceUpdate、父组件重渲染都会触发更新。
this.setState(partialState)
└── Component.prototype.setState
└── classComponentUpdater.enqueueSetState
├─ createUpdate:生成更新对象update,payload存放状态变更内容
├─ enqueueUpdate:把update追加到对应Fiber的updateQueue更新队列
└── scheduleUpdateOnFiber 【统一更新入口,和初始化共用】
└── performSyncWorkOnRoot
├─ renderRootSync:复用current Fiber树,双缓存wip Fiber树做diff对比
│ └─ workLoopSync → performUnitOfWork
│ ├─ beginWork:执行组件render,reconcileChildren,diff新旧子节点,打flags标记
│ └─ completeUnitOfWork
│ └─ 生成effectList副作用链表
└── commitRoot
├─ commitBeforeMutationEffects:getSnapshotBeforeUpdate
├─ commitMutationEffects:执行DOM修改
└─ commitLayoutEffects:执行componentDidUpdate
注意:setState 不会立刻修改 this.state;state 的更新要等到 render 阶段处理完更新队列之后,memoizedState 才会变成最新值。
Fiber 双缓存机制
React 使用两棵 Fiber 树:
current:页面当前已经渲染完成对应的 Fiber 树;workInProgress(wip):正在内存中计算、构建的新 Fiber 树。
alternate 属性互相指向对方,实现两棵树切换。更新时全部操作 wip 树,计算全部完成后,直接把 root.current 指向 wip,wip 变成新的 current,上一棵 current 变成下一次更新的 wip,避免重复创建大量对象。
React 源码解读
两个核心包职责:
react.js:核心是虚拟 DOM,平台无关,负责定义组件、Element、hooks、context、refs 等 API,只做描述,不操作 DOM;react-dom.js:浏览器渲染器,负责把虚拟 DOM 落地为真实 DOM。
v17.0.2 版本:react.js 的源码仅有 3000 多行,而 react-dom.js 的源码则有 2 万多行。实际上,大部分的框架逻辑都在 react-dom 当中。
<div id="app"></div>
<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>
<script>
class Hellow extends React.Component {
constructor(props) {
super(props);
this.state = { message: `Hellow, Lizhao. This is ${new Date()}` };
}
render() {
return React.createElement(
'p',
{
onClick: () => this.setState({
message: `Hellow, Lizhao. This is ${new Date()}`
})
},
this.state.message
);
}
}
const container = document.querySelector('#app');
const hellowInst = ReactDOM.render(React.createElement(Hellow), container);
</script>
react 包对外导出核心 API:
exports.Children = Children;
exports.Component = Component;
exports.PureComponent = PureComponent;
exports.cloneElement = cloneElement$1;
exports.createContext = createContext;
exports.createElement = createElement$1;
exports.createRef = createRef;
exports.forwardRef = forwardRef;
// Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
exports.useCallback = useCallback;
exports.useContext = useContext;
exports.useEffect = useEffect;
exports.useMemo = useMemo;
exports.useReducer = useReducer;
exports.useRef = useRef;
exports.useState = useState;
// ...
createElement
接收类型、属性、子元素,创建并返回 ReactElement 对象。
React.createElement(
type,
[props],
[...children]
)
type 指代 ReactElement 的类型:
- 字符串(
div、p):原生 DOM,HostComponent; - Class:继承
Component、PureComponent,class 组件; - function:函数组件;
- Symbol:
Fragment、StrictMode等内置组件。
ReactElement 只是信息载体对象,核心属性:$$typeof、type、key、ref、props、_owner。
var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner
};
element._store = {};
Object.defineProperty(element._store, 'validated', { /**...省略**/});
Object.defineProperty(element, '_self', { /**...省略**/});
Object.defineProperty(element, '_source', { /**...省略**/});
return element;
};
function createElement(type, config, children) {
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
// ...
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
function createElementWithValidation(type, props, children) {
// ...
var element = createElement.apply(this, arguments);
// ...
return element;
}
var createElement$1 = createElementWithValidation;
exports.createElement = createElement$1;
备注:JSX 会被 Babel 编译为 createElement 调用,写 JSX 时一般不需要直接调用该 API。
Component
class 组件必须继承 React.Component,子类必须实现 render() 方法。
class Hellow extends React.Component {
render() {
return <p>Hellow, Lizhao. This is {new Date()}</p>
}
}
var ReactNoopUpdateQueue = {
isMounted: function (publicInstance) {
return false;
},
enqueueForceUpdate: function (publicInstance, callback, callerName) {
warnNoop(publicInstance, 'forceUpdate');
},
enqueueReplaceState: function (publicInstance, completeState, callback, callerName) {
warnNoop(publicInstance, 'replaceState');
},
enqueueSetState: function (publicInstance, partialState, callback, callerName) {
warnNoop(publicInstance, 'setState');
}
};
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function (partialState, callback) {
// ...
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
备注:react.js 只负责生成 ReactElement 对象,DOM 渲染逻辑全部交给渲染器实现。
ReactDOM 源码解读
ReactDOM 是浏览器端专属渲染器,职责:将 ReactElement 对象转化真实 DOM,挂载页面节点。
同类渲染器还有:
- ReactNative:原生组件渲染器;
- ReactTest:渲染出纯 JavaScript 对象用于测试;
- ReactArt:渲染到 Canvas、SVG 或 VML(IE8)。
react-dom 包对外全部导出 API:
exports.createPortal = createPortal$1;
exports.findDOMNode = findDOMNode;
exports.flushSync = flushSync;
exports.hydrate = hydrate;
exports.render = render;
exports.unmountComponentAtNode = unmountComponentAtNode;
exports.unstable_batchedUpdates = batchedUpdates$1;
exports.unstable_createPortal = unstable_createPortal;
exports.unstable_renderSubtreeIntoContainer = renderSubtreeIntoContainer;
render()
render(element, container[, callback]):在指定 container 容器渲染 React 元素;返回组件实例,无状态组件返回 null。
render(element, container[, callback])
核心函数:legacyCreateRootFromDOMContainer、updateContainer。
render
└── legacyRenderSubtreeIntoContainer
├── legacyCreateRootFromDOMContainer(首次无root时)
│ └── createLegacyRoot
│ └── new ReactDOMBlockingRoot
│ └── createRootImpl
│ ├── createContainer
│ │ └── createFiberRoot
│ └── listenToAllSupportedEvents
└── updateContainer
├── createUpdate
├── enqueueUpdate
└── scheduleUpdateOnFiber
function render(element, container, callback) {
// ...
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
// ...
var root = container._reactRootContainer;
var fiberRoot;
if (!root) {
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
// ...
unbatchedUpdates(function () {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
// ...
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
legacyCreateRootFromDOMContainer
根据传入的 DOM 容器,创建 Fiber 树根节点,并且注册全部支持的原生事件监听器。
legacyCreateRootFromDOMContainer(container, forceHydrate)
└── createLegacyRoot(container, options)
└── new ReactDOMBlockingRoot(container, tag, options)
└── createRootImpl(container, tag, options)
└── createContainer(container, tag, hydrationCallbacks, ...)
└── createFiberRoot(containerInfo, tag, ...)
└── new FiberRootNode() → 创建 Fiber 根节点
各函数职责:
legacyCreateRootFromDOMContainer:返回createLegacyRoot的执行结果createLegacyRoot:创建并返回ReactDOMBlockingRoot实例对象new ReactDOMBlockingRoot:构造函数,调用createRootImpl创建 Fiber 根节点,将结果赋值给实例的_internalRoot属性,并挂载render和unmount实例方法createRootImpl:根据 DOM 容器创建 Fiber 树的根节点,为容器元素注册所有支持的事件监听createContainer:调用createFiberRoot创建 Fiber 根节点createFiberRoot:实例化FiberRootNode,创建根 Fiber 节点(HostRoot),建立FiberRoot与rootFiber的双向引用关系,最终返回 FiberRoot 对象
function legacyCreateRootFromDOMContainer(container, forceHydrate) {
// ...
return createLegacyRoot(container, shouldHydrate ? {
hydrate: true
} : undefined);
}
function createLegacyRoot(container, options) {
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
function ReactDOMBlockingRoot(container, tag, options) {
this._internalRoot = createRootImpl(container, tag, options);
}
function createRootImpl(container, tag, options) {
// ...
var root = createContainer(container, tag, hydrate);
// ...
// 为 container 添加所有支持的监听事件
listenToAllSupportedEvents(rootContainerElement);
// ...
return root;
}
function createContainer(containerInfo, tag, hydrate, hydrationCallbacks) {
return createFiberRoot(containerInfo, tag, hydrate);
}
createFiberRoot 会创建两个核心对象,并建立双向引用绑定:
FiberRootNode:Fiber 树的根对象,保存挂载的 DOM 容器信息,是应用层的根节点;HostRootFiber:宿主根FiberNode实例,是 Fiber 树的起点,tag为HostRoot(即 RootFiber)。
createFiberRoot(containerInfo, tag, ...)
├── 1. 创建 FiberRootNode
│ ├── 初始化 containerInfo(DOM 容器)
│ ├── 初始化 current → 指向 null(待绑定)
│ └── 初始化其他属性(pendingLanes, finishedWork, ...)
├── 2. 创建 HostRootFiber(rootFiber)
│ ├── tag = HostRoot(代表根节点)
│ ├── stateNode → 指向 FiberRootNode(待绑定)
│ └── 初始化其他属性(mode, pendingProps, ...)
└── 3. 建立双向引用绑定
├── FiberRootNode.current → HostRootFiber
└── HostRootFiber.stateNode → FiberRootNode
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
// 创建 Fiber 树的根节点
var root = new FiberRootNode(containerInfo, tag, hydrate);
var uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
// 初始化更新器
initializeUpdateQueue(uninitializedFiber);
return root;
}
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
this.containerInfo = containerInfo;
this.pendingChildren = null;
this.current = null;
this.pingCache = null;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.hydrate = hydrate;
this.callbackNode = null;
this.callbackPriority = NoLanePriority;
// ...
}
function createHostRootFiber(tag) {
// ...
return createFiber(HostRoot, null, null, mode);
}
var createFiber = function (tag, pendingProps, key, mode) {
return new FiberNode(tag, pendingProps, key, mode);
};
function FiberNode(tag, pendingProps, key, mode) {
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null; // Fiber
this.return = null;
this.child = null;
this.sibling = null;
// ...
}
生成根对象结构示意:
{
- containerInfo: div # container 容器节点
- current: FiberNode # 宿主组件的 Fiber 根节点
- alternate: # 新节点,将替换该节点
- child: # 子节点
- elementType: # 元素类型
- ref: #
- return: # 父节点
- sibling: # 兄弟节点
- stateNode: # Fiber 树的根节点
- ...
- entangledLanes:
- entanglements:
- eventTimes:
- ...
}
updateContainer
将传入的 element 封装为更新任务,加入 Fiber 的更新队列,调用调度入口开启渲染。
updateContainer(element, container, parentComponent, callback)
├── 获取 container._reactRootContainer._internalRoot → root
├── 获取 root.current → parentFiber
├── 创建更新对象
│ └── createUpdate(eventTime, lane)
│ └── update.payload = { element }
├── 将更新对象入队
│ └── enqueueUpdate(root.current, update, lane)
│ └── 修改 root.current.updateQueue.shared.pending
│ └── 将 update 加入循环链表(pending → update)
├── 进入调度入口
│ └── scheduleUpdateOnFiber(root.current, lane, eventTime)
│ ├── 标记更新优先级(root.pendingLanes)
│ └── 进入渲染调度(最终触发 performSyncWorkOnRoot / performConcurrentWorkOnRoot)
└── 返回渲染优先级(lane)
各函数职责:
updateContainer:获取 Fiber 根节点,创建更新并调度,最终触发视图更新createUpdate:创建一个更新对象,将element放入update.payload.element中enqueueUpdate:将更新对象放入宿主根 Fiber 节点的更新队列(updateQueue.shared.pending)中,形成一个循环链表scheduleUpdateOnFiber:标记更新优先级,进入 Fiber 树的渲染调度流程
function updateContainer(element, container, parentComponent, callback) {
// ...
var update = createUpdate(eventTime, lane);
update.payload = {
element: element
};
// ...
enqueueUpdate(current$1, update);
scheduleUpdateOnFiber(current$1, lane, eventTime);
return lane;
}
function enqueueUpdate(fiber, update) {
var updateQueue = fiber.updateQueue;
// ...
var sharedQueue = updateQueue.shared;
var pending = sharedQueue.pending;
// ...
sharedQueue.pending = update;
// ...
}
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
// ...
if (lane === SyncLane) {
if (/** ... **/) {
schedulePendingInteractions(root, lane);
performSyncWorkOnRoot(root);
}
// ...
}
// ...
}
scheduleUpdateOnFiber
所有更新统一入口:首次渲染、setState、useState 更新全部走这个函数。
scheduleUpdateOnFiber
└── performSyncWorkOnRoot
├── renderRootSync
│ └── workLoopSync
│ └── performUnitOfWork
│ ├── beginWork(递阶段)
│ │ ├── HostRoot → updateHostRoot
│ │ ├── ClassComponent → updateClassComponent
│ │ └── HostComponent → updateHostComponent
│ └── completeWork(归阶段)
│ └── HostComponent → 创建 DOM 节点
│
└── commitRoot
├── commitBeforeMutationEffects
├── commitMutationEffects
└── commitLayoutEffects
function performSyncWorkOnRoot(root) {
// ...
if (/* ... */) { /* ... */ } else {
lanes = getNextLanes(root, NoLanes);
exitStatus = renderRootSync(root, lanes);
}
// ...
commitRoot(root);
ensureRootIsScheduled(root, now());
return null;
}
renderRootSync
准备工作环境,初始化 work-in-progress,执行 workLoopSync 循环处理 Fiber 单元任务。
renderRootSync
├── prepareFreshStack
│ └── createWorkInProgress // 创建wip副本(双缓存)
└── workLoopSync
└── performUnitOfWork
├── beginWork$1
│ └── beginWork
└── completeUnitOfWork
└── completeWork
各函数职责:
renderRootSync:同步渲染根节点,协调整个构建流程prepareFreshStack:清理之前的工作状态,基于root.current创建workInProgress副本,建立双缓存关系createWorkInProgress:克隆当前 Fiber 节点作为 work-in-progress(双缓存机制)workLoopSync:同步循环,只要workInProgress不为空,持续执行performUnitOfWorkperformUnitOfWork:执行单个 Fiber 单元的工作,包含递(beginWork)和归(completeWork)两个阶段beginWork:递阶段,处理当前 Fiber,构建子 Fiber,返回下一个工作单元completeUnitOfWork:归阶段,处理当前 Fiber 完成后的收尾工作completeWork:构建 DOM 节点,冒泡副作用,返回父 Fiber
createWorkInProgress 实现双缓存机制,基于 current Fiber 创建 work-in-progress 副本;workLoopSync 循环调用 performUnitOfWork 处理每一个 Fiber 节点。
function renderRootSync(root, lanes) {
// ...
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
prepareFreshStack(root, lanes);
startWorkOnPendingInteractions(root, lanes);
}
var prevInteractions = pushInteractions(root);
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// ...
}
function prepareFreshStack(root, lanes) {
// ...
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null);
// ...
}
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
// ...
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// ...
return workInProgress;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
performUnitOfWork
处理单个 Fiber 单元任务,采用深度优先遍历策略,分为向下遍历(beginWork)和向上回溯(completeUnitOfWork)两个阶段。
- 递阶段(beginWork):调用
beginWork处理当前 Fiber,根据tag分发执行对应逻辑(如类组件执行生命周期、函数组件执行函数体、宿主组件创建子 Fiber 等),通过reconcileChildren生成子 Fiber 节点。- 返回子 Fiber → 继续向下遍历,将子 Fiber 作为下一个工作单元
- 返回
null→ 当前节点向下遍历结束,进入归阶段,调用completeUnitOfWork
- 归阶段(completeUnitOfWork):处理当前 Fiber 的完成收尾工作,调用
completeWork执行节点完成逻辑。完成后获取父 Fiber 作为下一个工作单元,继续向上回溯,最终回到根节点。
function performUnitOfWork(unitOfWork) {
// ...
if (/** ... ***/) {
// ...
} else {
next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
}
resetCurrentFiber();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner$2.current = null;
}
beginWork$1 = function (current, unitOfWork, lanes) {
// ...
try {
return beginWork(current, unitOfWork, lanes);
} catch (originalError) {
// ...
}
};
beginWork
beginWork 函数为不同类型的组件调用不同的更新函数,创建对应组件的 Fiber 节点。三种核心类型:HostRoot(宿主根,即 DOM 树的根节点)、ClassComponent、HostComponent(浏览器原生 DOM 节点)。
HostRoot(根节点):
updateHostRoot:处理根节点的更新。从updateQueue中解析出ReactDOM.render传入的element,调用reconcileChildren创建子 FiberprocessUpdateQueue:遍历workInProgress.updateQueue.shared.pending更新链表,解析出最新的宿主 Element,存入workInProgress.memoizedState.elementreconcileChildren:调用reconcileChildFibers函数,将其返回值赋值给workInProgress.childreconcileChildFibers:根据nextChildren类型分发到不同创建函数(reconcileSingleElement、reconcileChildrenArray、createFiberFromText),生成子 Fiber 节点。
ClassComponent(类组件):
updateClassComponent:类组件更新入口。负责实例化组件、调用 render 方法,最终返回子 ReactElement 对应的 Fiber 节点constructClassInstance:构造类实例,将实例赋值给workInProgress.stateNode,将实例的state赋值给workInProgress.memoizedStatemountClassInstance:初始化实例的props、state、refs等属性,将state同步到workInProgress.updateQueue.baseStatefinishClassComponent:调用实例的render方法生成子 ReactElement,通过reconcileChildren创建子 FiberreconcileChildren:调用reconcileChildFibers函数,将其返回值赋值给workInProgress.childreconcileChildFibers:根据nextChildren类型分发到不同创建函数(reconcileSingleElement、reconcileChildrenArray、createFiberFromText),生成子 Fiber 节点。
HostComponent(原生 DOM 节点):
updateHostComponent:处理原生 DOM 节点的更新,执行diffProperties对比新旧 props,标记需要更新的 DOM 属性;返回null(无子节点时进入归阶段)reconcileChildren:调用reconcileChildFibers函数,将其返回值赋值给workInProgress.childreconcileChildFibers:根据nextChildren类型分发到不同创建函数(reconcileSingleElement、reconcileChildrenArray、createFiberFromText),生成子 Fiber 节点。有子节点时返回子 Fiber,无子节点时返回null(进入归阶段)
beginWork(current, workInProgress, renderLanes)
└── 根据 workInProgress.tag 分发
├── HostRoot(根节点)
│ ├── updateHostRoot(current, workInProgress, renderLanes)
│ │ └── processUpdateQueue(workInProgress, renderLanes)
│ ├── reconcileChildren(current, workInProgress, nextChildren, renderLanes)
│ │ └── reconcileChildFibers(current, workInProgress, nextChildren, renderLanes)
│ └── 返回 workInProgress.child → 子 Fiber
│
├── ClassComponent(类组件)
│ ├── updateClassComponent(current, workInProgress, Component, renderLanes)
│ │ ├── 未实例化 → constructClassInstance(workInProgress, Component, ...)
│ │ ├── mountClassInstance(workInProgress, Component, ...)
│ │ ├── finishClassComponent(current, workInProgress, Component, ...)
│ │ │ └── reconcileChildren(current, workInProgress, nextChildren, renderLanes)
│ │ │ └── reconcileChildFibers(...)
│ └── 返回 workInProgress.child → 子 Fiber
│
└── HostComponent(原生 DOM 节点)
├── updateHostComponent(current, workInProgress, renderLanes)
│ ├── diffProperties(...) → 对比新旧 props,标记需要更新的 DOM 属性
│ ├── reconcileChildren(current, workInProgress, nextChildren, renderLanes)
│ │ └── reconcileChildFibers(...)
│ └── 返回 null(无子节点时进入归阶段)
└── 返回 null
function beginWork(current, workInProgress, renderLanes) {
// ...
switch (workInProgress.tag) {
case IndeterminateComponent: { /** ... **/ }
case LazyComponent: { /** ... **/ }
case FunctionComponent: {
// ...
return updateFunctionComponent(current, workInProgress, _Component, resolvedProps, renderLanes);
}
case ClassComponent: {
// ...
return updateClassComponent(current, workInProgress, _Component2, _resolvedProps, renderLanes);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
// ...
}
// ...
}
备注:HostRoot、ClassComponent、HostComponent 三个分支,最终全部都会调用 reconcileChildren,进入 Diff 协调逻辑。
completeWork
只有 HostComponent(原生 DOM 标签)在这里创建真实 DOM 对象,存放到 workInProgress.stateNode;函数组件、class 组件没有对应 DOM 节点,直接返回 null。
HostComponent 内部子调用:
completeWork(current, workInProgress, renderLanes)
└── HostComponent(原生 DOM 节点)
├── createInstance(type, props, ...)
│ └── createElement(type, props, ...)
├── appendAllChildren(workInProgress, domElement, ...)
├── finalizeInitialChildren(domElement, type, props, ...)
│ └── setInitialProperties(domElement, type, props)
│ ├── 特殊元素处理(video / audio / img / input / select / textarea)
│ └── setInitialDOMProperties(domElement, type, props)
├── workInProgress.stateNode = domElement
└── 返回 null
各函数职责:
createInstance:创建 DOM 节点,并在 DOM 节点中缓存 Fiber 节点和 props(通过internalInstanceKey挂载)createElement:调用 DOM 容器的ownerDocument.createElement方法,创建对应类型的 DOM 元素appendAllChildren:遍历子 Fiber 链表,将所有子节点的 DOM 元素递归追加到当前 DOM 节点finalizeInitialChildren:在首次挂载时完成子节点的初始化和属性设置setInitialProperties:为 DOM 元素设置初始属性,分发到特殊元素处理和通用属性设置setInitialDOMProperties:通用的 DOM 属性设置,包括className、style、普通属性、事件监听等
function completeWork(current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
// ...
return null;
}
case HostRoot: {
// ...
return null;
}
case HostComponent: {
// ...
if (/* ... */) { /* ... */ } else {
if (/* ... */) { /* ... */ } else {
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
}
}
return null;
}
// ...
}
}
commitRoot
commitRoot 函数将 Fiber 树中节点的真实 DOM 节点渲染到宿主环境下,核心实现在 commitRootImpl 中。
commitRootImpl 函数分为三个主要阶段:
BeforeMutation 阶段:
commitBeforeMutationEffects:处理 DOM 变更前的生命周期等commitBeforeMutationLifeCycles:执行类组件的getSnapshotBeforeUpdate方法
Mutation 阶段:
commitMutationEffects:将 Fiber 树中节点的真实 DOM 节点渲染到宿主环境下commitPlacement:执行 DOM 节点的插入操作insertOrAppendPlacementNodeIntoContainer:将节点插入到容器中appendChildToContainer:调用原生 DOM 的appendChild方法插入节点
Layout 阶段:
commitLayoutEffects:处理 DOM 变更后的生命周期等commitLifeCycles:执行类组件的componentDidMount(首次渲染)或componentDidUpdate(更新渲染)方法
commitRoot(root)
└── commitRootImpl(root)
├── commitBeforeMutationEffects()
│ └── commitBeforeMutationLifeCycles()
│ └── 执行 getSnapshotBeforeUpdate
├── commitMutationEffects()
│ └── commitPlacement()
│ └── insertOrAppendPlacementNodeIntoContainer()
│ └── appendChildToContainer()
│ └── 原生 appendChild 插入 DOM
└── commitLayoutEffects()
└── commitLifeCycles()
├── componentDidMount(首次渲染)
└── componentDidUpdate(更新渲染)
function commitRoot(root) {
var renderPriorityLevel = getCurrentPriorityLevel();
runWithPriority$1(ImmediatePriority$1, commitRootImpl.bind(null, root, renderPriorityLevel));
return null;
}
function commitRootImpl(root, renderPriorityLevel) {
// ...
if (firstEffect !== null) {
// ...
do {
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
// ...
} while (nextEffect !== null);
// ...
do {
invokeGuardedCallback(null, commitMutationEffects, null, root, renderPriorityLevel);
// ...
} while (nextEffect !== null);
// ...
nextEffect = firstEffect;
do {
invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
// ...
} while (nextEffect !== null);
}
// ...
}
function commitMutationEffects(root, renderPriorityLevel) {
while (nextEffect !== null) {
// ...
switch (primaryFlags) {
case Placement: {
commitPlacement(nextEffect);
// ...
}
// ...
}
// ...
}
}
function commitPlacement(finishedWork) {
// ...
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
insertOrAppendPlacementNode(finishedWork, before, parent);
}
}
function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
var tag = node.tag;
var isHost = tag === HostComponent || tag === HostText;
if (isHost || enableFundamentalAPI ) {
var stateNode = isHost ? node.stateNode : node.stateNode.instance;
if (before) {
insertInContainerBefore(parent, stateNode, before);
} else {
appendChildToContainer(parent, stateNode);
}
} else if (tag === HostPortal) {} else {
var child = node.child;
if (child !== null) {
insertOrAppendPlacementNodeIntoContainer(child, before, parent);
var sibling = child.sibling;
while (sibling !== null) {
insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
sibling = sibling.sibling;
}
}
}
}
function appendChildToContainer(container, child) {
var parentNode;
if (container.nodeType === COMMENT_NODE) {
parentNode = container.parentNode;
parentNode.insertBefore(child, container);
} else {
parentNode = container;
parentNode.appendChild(child);
}
// ...
}
事件机制
React 基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等。在 React 中这套事件机制被称之为合成事件。
React 17 合成事件三大改动:
- 事件委托挂载点改为根容器,不再挂载到
document; - 支持原生捕获事件;
onScroll不再冒泡;onFocus、onBlur使用focusin、focusout; - 移除事件池复用逻辑。
合成事件(SyntheticEvent)
SyntheticEvent 是浏览器原生事件的跨浏览器包装,遵循 W3C 事件规范。JSX 写的 onClick 并不是原生 click 事件,是合成事件。
合成事件带来收益:
- 抹平浏览器兼容性差异;
- 事件委托减少监听器数量,节省内存;
- 和 Scheduler 任务优先级打通;
- 统一事件管理与事务机制。
function createSyntheticEvent(Interface) {
function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget) {
this._reactName = reactName;
this._targetInst = targetInst;
this.type = reactEventType;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
// ...
return this;
}
_assign(SyntheticBaseEvent.prototype, {
preventDefault: function () { /** ... **/},
stopPropagation: function () { /** ... **/},
persist: function () { /** ... **/},
isPersistent: functionThatReturnsTrue
});
return SyntheticBaseEvent;
}
事件绑定
React 在渲染 React 树的根 DOM 容器上注册所有合法的原生事件(React 16 挂在 document 上),并通过 dispatchEvent 作为统一回调。
render
└── legacyRenderSubtreeIntoContainer
└── legacyCreateRootFromDOMContainer
└── createRootImpl
└── listenToAllSupportedEvents
└── listenToNativeEvent
└── addTrappedEventListener
└── createEventListenerWrapperWithPriority
function listenToAllSupportedEvents(rootContainerElement) {
// ...
allNativeEvents.forEach(function (domEventName) {
if (!nonDelegatedEvents.has(domEventName)) {
listenToNativeEvent(domEventName, false, rootContainerElement, null);
}
listenToNativeEvent(domEventName, true, rootContainerElement, null);
});
}
function listenToNativeEvent(domEventName, isCapturePhaseListener, rootContainerElement, targetElement) {
// ...
if (!listenerSet.has(listenerSetKey)) {
// ...
addTrappedEventListener(target, domEventName, eventSystemFlags, isCapturePhaseListener);
listenerSet.add(listenerSetKey);
}
}
function addTrappedEventListener(targetContainer, domEventName, eventSystemFlags, isCapturePhaseListener, isDeferredListenerForLegacyFBSupport) {
var listener = createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags);
// ...
if (isCapturePhaseListener) {
// ...
} else {
if (isPassiveListener !== undefined) {
unsubscribeListener = addEventBubbleListenerWithPassiveFlag(targetContainer, domEventName, listener, isPassiveListener);
} else {
unsubscribeListener = addEventBubbleListener(targetContainer, domEventName, listener);
}
}
}
注意:addTrappedEventListener 函数中的 listener 根据不同事件优先级返回不同函数。
比如,onClick 事件调用的是 dispatchDiscreteEvent 函数:
function createEventListenerWrapperWithPriority(targetContainer, domEventName, eventSystemFlags) {
// ...
switch (eventPriority) {
case DiscreteEvent:
listenerWrapper = dispatchDiscreteEvent;
break;
case UserBlockingEvent:
listenerWrapper = dispatchUserBlockingUpdate;
break;
case ContinuousEvent:
default:
listenerWrapper = dispatchEvent;
break;
}
return listenerWrapper.bind(null, domEventName, eventSystemFlags, targetContainer);
}
非委托事件(不能冒泡,直接绑定 DOM 节点)调用栈:
render
└── legacyRenderSubtreeIntoContainer
└── updateContainer
└── scheduleUpdateOnFiber
└── performSyncWorkOnRoot
└── renderRootSync
└── workLoopSync
└── performUnitOfWork
└── completeUnitOfWork
└── completeWork
└── finalizeInitialChildren
└── setInitialProperties
function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
// ...
switch (tag) {
case 'dialog':
listenToNonDelegatedEvent('cancel', domElement);
listenToNonDelegatedEvent('close', domElement);
props = rawProps;
break;
case 'iframe':
case 'object':
case 'embed':
listenToNonDelegatedEvent('load', domElement);
props = rawProps;
break;
// ...
}
// ...
}
事件触发
React 事件机制采用委托形式,所有元素的事件都委托在 DOM 容器上。
事件触发时,dispatchEvent 函数被调用,依次执行以下流程:
- 从真实 DOM 上查找对应的 Fiber 节点
- 批量提取事件
- 根据不同事件类型合成不同的事件对象
- 在对应的 Fiber 节点上自下而上递归收集 props 中对应的事件函数
- 执行所有事件的监听函数
事件处理关键:
- 事件委托:所有事件绑定在容器上,通过
addEventListener注册,由dispatchEvent统一处理 - Fiber 查找:通过原生 DOM 元素上的
internalInstanceKey找到对应的 Fiber 节点 - 合成事件:根据原生事件类型创建对应的合成事件实例,抹平浏览器差异
- 事件收集:从目标 Fiber 节点开始,自下而上遍历祖先节点,收集所有 props 中的事件监听函数
- 事件执行:按捕获阶段 → 目标阶段 → 冒泡阶段的顺序执行收集到的监听函数
- 优先级:
dispatchDiscreteEvent对应离散事件(如 click),优先级最高;dispatchContinuousEvent对应连续事件(如 scroll),优先级较低
dispatchDiscreteEvent
└── dispatchEvent
└── attemptToDispatchEvent
└── dispatchEventForPluginEventSystem
└── dispatchEventsForPlugins
├── extractEvents$5
│ └── extractEvents$4
│ └── accumulateSinglePhaseListeners // 向上收集事件回调
└── processDispatchQueue
└── processDispatchQueueItemsInOrder // 执行回调
各函数职责:
dispatchEvent:事件分发的入口函数,根据事件类型和优先级触发后续处理流程attemptToDispatchEvent:根据原生事件的目标 DOM 元素,获取对应的 Fiber 节点或最近祖先的 Fiber 节点dispatchEventForPluginEventSystem:将事件分发到插件事件系统进行处理dispatchEventsForPlugins:遍历插件系统,提取事件并收集 Fiber 节点上的监听函数extractEvents$5:事件提取入口,对mouseover、mouseout、focusout、focusin、onCompositionStart、onBeforeInput等特殊事件做预处理,核心逻辑调用extractEvents$4extractEvents$4:核心提取函数,根据事件类型调用对应的合成事件构造函数(如SyntheticMouseEvent)生成合成事件实例,创建事件对象并追加到事件队列accumulateSinglePhaseListeners:从目标 Fiber 节点开始,自下而上遍历祖先节点,收集存储在 Fiber 节点 props 中的对应事件监听函数processDispatchQueue:处理事件队列的入口processDispatchQueueItemsInOrder:按捕获阶段 → 目标阶段 → 冒泡阶段的顺序,依次执行事件队列中的监听函数
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
// ...
discreteUpdates(dispatchEvent, domEventName, eventSystemFlags, container, nativeEvent);
}
function dispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
// ...
var blockedOn = attemptToDispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent);
// ...
}
function attemptToDispatchEvent(domEventName, eventSystemFlags, targetContainer, nativeEvent) {
// ...
dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer);
return null;
}
function dispatchEventForPluginEventSystem(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
// ...
batchedEventUpdates(function () {
return dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, ancestorInst);
});
}
function dispatchEventsForPlugins(domEventName, eventSystemFlags, nativeEvent, targetInst, targetContainer) {
var nativeEventTarget = getEventTarget(nativeEvent);
var dispatchQueue = [];
extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
processDispatchQueue(dispatchQueue, eventSystemFlags);
}
function extractEvents$5(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) {
extractEvents$4(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags);
// ...
}
function extractEvents$4(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, eventSystemFlags, targetContainer) {
// ..
var _listeners = accumulateSinglePhaseListeners(targetInst, reactName, nativeEvent.type, inCapturePhase, accumulateTargetOnly);
if (_listeners.length > 0) {
var _event = new SyntheticEventCtor(reactName, reactEventType, null, nativeEvent, nativeEventTarget);
dispatchQueue.push({
event: _event,
listeners: _listeners
});
}
}
function accumulateSinglePhaseListeners(targetFiber, reactName, nativeEventType, inCapturePhase, accumulateTargetOnly) {
// ...
while (instance !== null) {
// ...
if (tag === HostComponent && stateNode !== null) {
lastHostComponent = stateNode;
if (reactEventName !== null) {
var listener = getListener(instance, reactEventName);
if (listener != null) {
listeners.push(createDispatchListener(instance, listener, lastHostComponent));
}
}
}
// ...
instance = instance.return;
}
return listeners;
}
function getListener(inst, registrationName) {
// ...
var props = getFiberCurrentPropsFromNode(stateNode);
// ...
var listener = props[registrationName];
// ...
return listener;
}
function processDispatchQueue(dispatchQueue, eventSystemFlags) {
// ...
for (var i = 0; i < dispatchQueue.length; i++) {
var _dispatchQueue$i = dispatchQueue[i],
event = _dispatchQueue$i.event,
listeners = _dispatchQueue$i.listeners;
processDispatchQueueItemsInOrder(event, listeners, inCapturePhase);
}
// ...
}
function processDispatchQueueItemsInOrder(event, dispatchListeners, inCapturePhase) {
if (inCapturePhase) {
// ...
} else {
for (var _i = 0; _i < dispatchListeners.length; _i++) {
// ...
executeDispatch(event, _listener, _currentTarget);
}
}
}
更新(setState)
setState 来自 Component.prototype,核心调用 updater.enqueueSetState。
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function (partialState, callback) {
// ...
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
updater 实例注入完整调用栈:
render
└── legacyRenderSubtreeIntoContainer
└── updateContainer
└── scheduleUpdateOnFiber
└── performSyncWorkOnRoot
└── renderRootSync
└── workLoopSync
└── performUnitOfWork
└── beginWork$1
└── beginWork
└── updateClassComponent
updateClassComponent 函数的主要逻辑:
- constructClassInstance
- adoptClassInstance
- classComponentUpdater.enqueueSetState
- scheduleUpdateOnFiber
function updateClassComponent(current, workInProgress, Component, nextProps, renderLanes) {
// ...
if (instance === null) {
// ...
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
}
// ...
return nextUnitOfWork;
}
function constructClassInstance(workInProgress, ctor, props) {
// ...
var instance = new ctor(props, context);
var state = workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null;
adoptClassInstance(workInProgress, instance);
// ...
return instance;
}
function adoptClassInstance(workInProgress, instance) {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
set(instance, workInProgress);
{
instance._reactInternalInstance = fakeInternalInstance;
}
}
var classComponentUpdater = {
isMounted: isMounted,
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst);
var eventTime = requestEventTime();
var lane = requestUpdateLane(fiber);
var update = createUpdate(eventTime, lane);
update.payload = payload;
// ...
enqueueUpdate(fiber, update);
scheduleUpdateOnFiber(fiber, lane, eventTime);
},
enqueueReplaceState: function (inst, payload, callback) { /** ... **/},
enqueueForceUpdate: function (inst, callback) { /** ... **/}
};
classComponentUpdater.enqueueSetState:创建 update 对象入队,调用 scheduleUpdateOnFiber;后续流程和 updateContainer 大体一致。
setState 不会同步修改 this.state,状态更新结果要等待下一轮渲染完成。
协调器(Reconciler)
协调器运行在 render 阶段,整体调用链路:
performSyncWorkOnRoot
└── renderRootSync
└── workLoopSync
└── performUnitOfWork
└── beginWork$1
└── beginWork
└── reconcileChildren
└── reconcileChildFibers
reconcileChildren
Diff 入口函数,调用 reconcileChildFibers,根据子节点类型分发不同协调逻辑。
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
// ...
var isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
case REACT_PORTAL_TYPE:
return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes));
}
if (isArray$1(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes);
}
// ...
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
reconcileChildrenArray
专门处理数组子节点,列表 Diff,key 节点复用逻辑全部在这里实现。
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {
// ...
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// 旧节点位于新节点右边:下次循环,旧节点不变
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
// 根据 key 判断是否可以复用节点:若可复用则返回旧节点创建的 Fiber
var newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes);
// 不可复用节点,跳出循环
if (newFiber === null) {
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
// 删除旧节点
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
deleteChild(returnFiber, oldFiber);
}
}
// 更新操作
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// ...
oldFiber = nextOldFiber;
}
// 新节点遍历完,删除剩下的旧节点
if (newIdx === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
// 旧节点遍历完,新节点若还有,则剩下的都是需要新创建的
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (_newFiber === null) {
continue;
}
// ...
}
return resultingFirstChild;
}
// 保存旧节点信息
var existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 第一次 for 循环过程中,碰到无法复用的节点便会跳到这里
// 通过新节点的 key 值或索引,查找 existingChildren 是否有相同的旧节点
for (; newIdx < newChildren.length; newIdx++) {
var _newFiber2 = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx], lanes);
// ...
}
// ...
return resultingFirstChild;
}
Scheduler(调度器)原理
浏览器 JS 线程与 GUI 渲染线程互斥,JS 执行时间过长页面卡顿。主流屏幕刷新 60Hz,帧预算约 16.6ms。
Scheduler 策略:每帧预留约 5ms 执行 JS 任务;时间耗尽交还主线程给浏览器,下一帧继续未完成任务,实现可中断异步更新。
简易模拟 Scheduler 完整代码:
const syncSleep = (time) => {
const start = new Date().valueOf();
let countSleep = 0
while (new Date().valueOf() - start < time) {
console.log('sleep: ', ++countSleep)
}
}
const add = (a, b) => {
syncSleep(1);
return a + b;
}
const accumulate = () => {
let count = 0;
let i = 1;
const func = () => {
if (i <= 200) {
console.log('add: ', i);
count = add(count, i);
i ++;
return func;
}
console.log("count:", count)
return null
}
return func;
}
let expireTime;
const workLoop = (task) => {
let taskForNextTime = task;
while (new Date().valueOf() < expireTime && task) {
taskForNextTime = task();
}
return taskForNextTime;
}
let nextTask = accumulate()
const channel = new MessageChannel();
channel.port1.onmessage = () => {
expireTime = new Date().valueOf() + 5;
nextTask = workLoop(nextTask);
if (nextTask) {
channel.port2.postMessage(null)
}
}
channel.port2.postMessage(null)
一个简单的 React
<div id="app"></div>
<script src="./react.js"></script>
<script src="./react-dom.js"></script>
<script>
class Hellow extends React.Component {
constructor(props) {
super(props);
this.state = { message: `Hellow, Lizhao. This is ${new Date()}!` };
}
render() {
return React.createElement(
'p',
{
onClick: (name) => this.setState({
message: `Hellow, Lizhao. This is ${new Date()}!`
})
},
this.state.message
);
}
}
const container = document.querySelector('#app');
const hellowInst = ReactDOM.render(React.createElement(Hellow), container);
</script>
// react.js
const createElement = (tag, attrs, ...childs) => {
return { tag, attrs, childs }
}
function Component(props) {
this.props = props
this.state = {}
}
Component.prototype.setState = function (updateState) {
this.state = Object.assign({}, this.state, updateState)
ReactDOM.updateClassComponent(this)
}
const React = {
createElement,
Component
}
window.React = React
// react-dom.js
const NOOP = function () {}
const setAttribute = (dom, attr, value) => {
if (/on\w+/.test(attr)) {
attr = attr.toLowerCase()
dom[attr] = value || NOOP
return
}
dom.setAttribute(attr, value)
}
const updateClassComponent = (component) => {
const {
props,
state,
base,
shouldComponentUpdate = NOOP,
componentWillReceiveProps = NOOP,
componentWillUpdate = NOOP,
componentWillMount = NOOP,
componentDidUpdate = NOOP,
componentDidMount = NOOP
} = component
const isUpdateComponent = !!base
if (isUpdateComponent) {
const bool = shouldComponentUpdate(props, state)
if (!bool && bool !== undefined) {
return false
}
componentWillReceiveProps()
componentWillUpdate()
} else {
componentWillMount()
}
const element = component.render()
const hostDom = createHostDom(element)
if (isUpdateComponent) {
base.parentNode && base.parentNode.replaceChild(hostDom, base)
componentDidUpdate()
} else {
componentDidMount()
}
component.base = hostDom
}
const createHostDom = (element) => {
if (['string', 'number'].includes(typeof element)) {
return document.createTextNode(element)
}
if (Object.getPrototypeOf(element.tag) === React.Component) {
const component = new element.tag(element.attrs)
updateClassComponent(component)
return component.base
}
if (['string'].includes(typeof element.tag)) {
const hostDom = document.createElement(element.tag)
if (element.attrs) {
for (let key in element.attrs) {
setAttribute(hostDom, key, element.attrs[key])
}
}
if (element.childs) {
element.childs.forEach(child => {
hostDom.appendChild(createHostDom(child))
})
}
return hostDom
}
}
const ReactDOM = {
render: (element, root) => {
root.innerText = ""
root.appendChild(createHostDom(element))
},
updateClassComponent
}
window.ReactDOM = ReactDOM
相关问题
React 有什么优点?
- 速度很快:采取虚拟 DOM 的方式,避免直接对 DOM 进行操作。在 UI 渲染过程中,React 通过在虚拟 DOM 中的微操作来实现对实际 DOM 的局部更新。
- 跨浏览器兼容:虚拟 DOM 帮助解决了跨浏览器问题,它提供了标准化的 API,配合 polyfill 后 IE9 也能运行。
- 单向数据流。
- 纯粹的 JavaScript。
什么是进程、线程、协程、纤程?
进程:一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序、数据集合和进程控制块三部分组成。
线程:程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间。一个标准的线程由线程 ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
协程:协程全称为协同程序,又称作微线程。它与多线程情况下的线程比较类似。协程有自己的堆栈、局部变量和指令指针,通常,多个协程共享全局变量等很多信息。其思想是,一系列相互依赖的协程依次使用 CPU,每次只有一个协程运行,其他协程处于休眠状态。协程实际上是在一个线程当中,每个协程对 CPU 进行分时访问。
- 纤程:纤程是比线程更小的一个运行单位。一个线程可以拆分为多个纤程,然后通过人工转换,从而让各个纤程工作。纤程其实就是线程里面创建的多个执行任务。
进程:资源分配最小单位;线程:CPU 调度最小单位。
协程是语言层面实现,运行在单线程内部;协程主动让出执行权,异步完成之后恢复执行,不需要操作系统内核抢占打断。
协程与纤程核心差异:协程主动让出;纤程可以被调度器强制中断、交出执行权。
React Fiber 就是纤程思想在前端渲染的落地。