浅谈 React 原理(一)

1,310 阅读4分钟

React 是前端非常常用的一个 JavaScript 库,我们在日常开发的时候经常会用到这个库,它可以极大程度上模块化我们的代码,避免冗长的代码。

那么用过的朋友一定非常好奇,React 实现的原理到底是怎样的呢?我们今天就先来看看 React 的基础和初次是如何渲染的:

ReactElement

ReactDOM.render() 会根据用户创建一个虚拟树结构(ReactElementFiber),根据虚拟 DOM 节点生成真正的DOM并插入到页面中才算渲染完成。

其中,ReactElement 包含五个属性:

  1. type。表明类型

  2. props。html 标签的大部分属性,id、className、style、children

  3. key。在数组处理和 diff 过程中有用

  4. ref。引用标识

  5. $$typeof。这个属性指向 Symbol(React.element),是React元素的唯一标识

至于更新,React 使用更新队列 updateQueue 链表结构来合并更新,将多个状态更新在组件的更新队列中合并,计算出组件的新状态 newState,而初次渲染时只需要挂载一个update标识即可

Fiber

Fiber 是 React16 中新的调和引擎,其本质是单链表,每一个节点代表一个 React 组件的实例,是新协调引擎,主要目标是支持虚拟 DOM 的渐进式渲染:对大型任务分片、对任务划分优先级、调度过程中实现挂起恢复终止等

而且使用单指针就可以完成操作,发现用户操作或者更高优先级的任务时就可以暂停当前工作,指针自上而下,从左往右

每一个节点还包含三个属性:

  1. child 指向当前节点的第一个子元素
  2. return 指向当前节点的父元素
  3. sibling 指向同级的下一个兄弟节点

而 Fiber 的基本结构如下:

  1. tag,fiber的类型,函数组件、类组件、portal(挂载到其他节点)等

  2. type,React类型

  3. alternate,代表双向缓冲对象

  4. effectTag,代表这个fiber在下一次渲染中将会被如何处理

  5. expirationTime,过期时间,过期时间越靠前则优先级越高

  6. firstEffectlastEffect, 也是链表结构,通过 nextEffect 连接,代表即将更新的 fiber 状态

  7. memorizeStatememoriseProps, 代表上次渲染中组件的 state 和 props,如果成功更新了,新的 pendingProps 和 newState 将会代替这两个的值

  8. ref ,引用标识

  9. stateNode,代表 fiber 节点的对应真实状态。

    • 原生组件,则指向一个dom节点
    • 类组件,指向对应的类实例
    • 函数组件,指向Null
    • RootFiber,指向FiberRoot

渲染过程

  1. PrepareFreshStack

    准备干净的栈,最主要是要创建双向缓冲变量 WorkInProgress

    双区缓冲变量对应的是 current,current 表示当前页面上显示的 fiber 节点,双区表示下一秒将要渲染的状态,处于 pending 状态,会取代之前的 current

    在渲染到页面之前,current 和 WIP 的 alternate 字段分别指向彼此

    WIP 继承了 current 的核心属性,elementType、type、stateNode 等

  2. WorkLoop 工作循环

    会执行一个 while 语句,每执行一次循环都会完成对一个 fiber 的处理,其中有一个指针 workInProgress 指向正在处理的 fiber,不断向链尾直到 null

    跳出的条件只有 fiber 遍历结束、当前线程的权限移交给外部队列

  3. PerformUnitOfWork & beginWork

    单元工作 PUW 主要工作是通过 beginWork 完成,而 beginWork 的核心工作时判断 fiber 节点的 tag 来判断组件类型,通过改变 fiber.effectTag 和 pendingProps 来告诉后面的函数该对真实 DOM 进行怎样的操作

    过程中需要判断 fiber 有没有创建过这个实例,如果没有的话就会调用它的构建函数,并且将更新器 updater挂载到这个类的实例上(用于处理 setState,实际上类组件的更新器都是同一个对象)

    如果实例已经存在,就对比新旧 props 和 state,判断是否需要更新,并触发一些生命周期钩子(shouldComponentUpdate、getDerivedStateFromProps)

    属性计算完成后,调用类的 render 函数获得最终的 ReactElement,打上 Performed 标记,表示这个类已经执行过了

    reconcile,一开始只是构建第一个根节点 fiberRoot 和第一个无意义的空 root,而在单个元素的调和过程(reconcileSingleElement)中会根据之前 render 到的 ReactElement 元素构建出对应的 fiber 并插入到整个 fiber 链表中去

    最后通过 placeSingleChild 给这个 fiber的effectTag 打上 Placement 标签,然后 fiber 指针移到下一个节点

如果子元素是字符串或者数字,按照文字节点处理;如果是纯文字节点,就会作为父元素的prop处理

在到达最后一个节点之后,会执行一个 completeWork,会从链尾向上,根据 fiber 生成真正的 DOM 结构,并拼接成 dom 树

总结

本篇只是对 React 的基本原理中的初次渲染进行分析阐述,后面还会介绍它如何进行事件更新进行分析,如果各位需要更详细的资料和源码那就还是去看官方文档比较好。

©本总结教程版权归作者所有,转载需注明出处