一、函数式编程
函数式编程是一种编程范式,在 React 中应用有两个特点:
- 纯函数
- 不可变值
二、vdom 和 diff 算法
- vdom 是实现 vue 和 react 的重要基石
- diff 算法是 vdom 中最核心、最关键的部分
1、vdom
vdom - 用 JS 模拟 DOM 结构,计算出最小的变更,数据驱动视图,操作 DOM
2、 Snabbdom (Virtual DOM库)
详细可参考教程:snabbdom 源码阅读分析
2.1 Snabbdom的核心
- 使用 h() 函数创建 JavaScript 对象(VNode)描述真实 DOM
- init() 设置模块,创建 patch()
- patch() 比较新旧两个 VNode,它有两个参数,第一个参数可以是真实的 DOM 或者 VNode,第二个参数是新的 VNode
- 把变化的内容更新到真实 DOM 树上
2.2 h 函数
snabbdom的h函数是用来创建vnode的,它利用了函数重载的思想,根据传入的参数个数或类型的不同,执行不同函数
// h 函数的重载export function h(sel: string): VNode;export function h(sel: string, data: VNodeData | null): VNode;export function h(sel: string, children: VNodeChildren): VNode;export function h(sel: string, data: VNodeData | null, children:VNodeChildren): VNode;export function h(sel: any, b?: any, c?: any): VNode {var data: VNodeData = {}, children: any, text: any, i: number;// 处理参数,实现重载的机制if (c !== undefined) {// 处理三个参数的情况// sel、data、children/textif (b !== null) { data = b; }if (is.array(c)) { children = c; }// 如果 c 是字符串或者数字else if (is.primitive(c)) { text = c; }// 如果 c 是 VNodeelse if (c && c.sel) { children = [c]; }} else if (b !== undefined && b !== null) {// 处理两个参数的情况// 如果 b 是数组if (is.array(b)) { children = b; }// 如果 b 是字符串或者数字else if (is.primitive(b)) { text = b; }// 如果 b 是 VNodeelse if (b && b.sel) { children = [b]; }else { data = b; }}if (children !== undefined) {// 处理 children 中的原始值(string/number)for (i = 0; i < children.length; ++i) {// 如果 child 是 string/number,创建文本节点if (is.primitive(children[i])) children[i] = vnode(undefined,undefined, undefined, children[i], undefined);}}if (sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&(sel.length === 3 || sel[3] === '.' || sel[3] === '#')) {// 如果是 svg,添加命名空间addNS(data, children, sel);}// 返回 VNodereturn vnode(sel, data, children, text, undefined);};// 导出模块export default h;
2.3 VNode
一个 VNode 就是一个虚拟节点,用来描述一个 DOM 元素
export interface VNode {// 选择器sel: string | undefined;// 节点数据:属性/样式/事件等data: VNodeData | undefined;// 子节点,和 text 只能互斥children: Array<VNode | string> | undefined;// 记录 vnode 对应的真实 DOMelm: Node | undefined;// 节点中的内容,和 children 只能互斥text: string | undefined;// 优化用key: Key | undefined;}export function vnode(sel: string | undefined,data: any | undefined,children: Array<VNode | string> | undefined,text: string | undefined,elm: Element | Text | undefined): VNode {let key = data === undefined ? undefined : data.key;return {sel, data, children, text, elm, key};}export default vnode;
2.4 patch 函数
patch 函数是 snabbdom 的核心,调用 init 会返回这个函数,用来做 dom 相关的更新
function patch(oldVnode: VNode | Element, vnode: VNode): VNode {}
3、diff
diff 即对比前后 vdom 的变化
传统 diff 算法,即树 diff 的时间复杂度是O(n^3),新的 diff 算法的时间复杂度是 O(n),方法是:
- 只比较同一层级,不跨级比较
- tag 不相同,则直接删掉重建,不再深度比较
- tag 和 key,两者都相同,则认为是相同节点,不再深度比较
三、JSX 本质
- React.createElement 类似 Snabbdom 的 h 函数,返回 vNode
组件名,首字母必须大写
// 第一个参数,可能是组件,也可能是 html 的 tag// 第二个参数是标签的属性// 后面参数为子元素,可逐个添加,也可使用数组形式React.createElement(tag | component, props, child1, child2, child3)
四、合成事件
React 中所有事件挂载到 document 上
- event 不是原生的,是 SyntheticEvent 合成事件对象

合成事件的意义:
- 更好的兼容性和跨平台
- 挂载到 document,可减少内存消耗,避免频繁解绑
- 方便事件的统一管理(如事务机制)
五、setState 和 batchUpdate
- setState 有时异步(普通使用),有时同步(setTimeout,DOM 事件)
- setState 有时合并(对象形式),有时不合并(函数形式)
setState 运行机制:
上图中判断是否处于 batch update 的机制是 React 内置的 isBatchingUpdates 变量是否为 false,仅在 React 可管理的入口存在这个机制,如:
- 生命周期(和它调用的函数)
- React 中注册的事件(和它调用的函数)
而 setTimeout 和 自定义 Dom 则不能触发这个机制,举例如下:
class App extends React.Component {increse = () => {// 开始:处于 batchUpdate// isBatchingUpdates = truethis.setState({ count: this.state.count + 1 })console.log('count:' this.state.count) // 异步的,拿不到最新值// 结束// isBatchingUpdates = false}increse = () => {// 开始:处于 batchUpdate// isBatchingUpdates = truesetTimeout(() => {// 此时 isBatchingUpdates 是 falsethis.setState({ count: this.state.count + 1 })console.log('count:' this.state.count) // 可获取最新值})// 结束// isBatchingUpdates = false}componentDidMount() {// 开始:处于 batchUpdate// isBatchingUpdates = truedocument.body.addEventListener('click', () => {// 此时 isBatchingUpdates 是 falsethis.setState({ count: this.state.count + 1 })console.log('count:' this.state.count) // 可获取最新值})// 结束// isBatchingUpdates = false}}
这种在函数开始执行一些操作,结束后执行一些操作的方式叫事务机制(transaction)
六、组件渲染过程
1、渲染和更新过程
渲染过程
- 获取 props、state
- render() 生成 vNode
- patch(elem, vnode) 操作 DOM
更新过程
- setState(newState) —> dirtyComponents(可能有子组件)
- render() 生成 newVNode
- patch(vnode, newVNode) 操作 DOM
上述的 patch 被拆分为两个阶段
- reconciliation 阶段 - 执行 diff 算法,纯 JS 计算
- commit 阶段 - 将 diff 结果渲染 DOM
2、性能问题与 fiber
渲染可能存在性能问题,由于 JS 是单线程,并且和 DOM 渲染公用一个线程,当组件足够复杂,组件更新时计算和渲染压力都大,同时如再有 DOM 操作需求(动画、鼠标拖拽等),将造成卡顿
React 针对上述问题,有一个解决方案,即 fiber
- 将 reconciliation 阶段进行任务拆分(commit 无法拆分)
- DOM 需求渲染时暂停,空闲时恢复(可通过 window.requestIdleCallback 知悉是否需要渲染)


