React 是前端非常常用的一个 JavaScript 库,我们在日常开发的时候经常会用到这个库,它可以极大程度上模块化我们的代码,避免冗长的代码。
那么用过的朋友一定非常好奇,React 实现的原理到底是怎样的呢?我们今天就先来看看 React 的基础和初次是如何渲染的:
ReactElement
ReactDOM.render() 会根据用户创建一个虚拟树结构(ReactElement 和 Fiber),根据虚拟 DOM 节点生成真正的DOM并插入到页面中才算渲染完成。
其中,ReactElement 包含五个属性:
-
type。表明类型 -
props。html 标签的大部分属性,id、className、style、children -
key。在数组处理和 diff 过程中有用 -
ref。引用标识 -
$$typeof。这个属性指向 Symbol(React.element),是React元素的唯一标识
至于更新,React 使用更新队列 updateQueue 链表结构来合并更新,将多个状态更新在组件的更新队列中合并,计算出组件的新状态 newState,而初次渲染时只需要挂载一个update标识即可
Fiber
Fiber 是 React16 中新的调和引擎,其本质是单链表,每一个节点代表一个 React 组件的实例,是新协调引擎,主要目标是支持虚拟 DOM 的渐进式渲染:对大型任务分片、对任务划分优先级、调度过程中实现挂起恢复终止等
而且使用单指针就可以完成操作,发现用户操作或者更高优先级的任务时就可以暂停当前工作,指针自上而下,从左往右
每一个节点还包含三个属性:
- child 指向当前节点的第一个子元素
- return 指向当前节点的父元素
- sibling 指向同级的下一个兄弟节点
而 Fiber 的基本结构如下:
-
tag,fiber的类型,函数组件、类组件、portal(挂载到其他节点)等 -
type,React类型 -
alternate,代表双向缓冲对象 -
effectTag,代表这个fiber在下一次渲染中将会被如何处理 -
expirationTime,过期时间,过期时间越靠前则优先级越高 -
firstEffect和lastEffect, 也是链表结构,通过 nextEffect 连接,代表即将更新的 fiber 状态 -
memorizeState和memoriseProps, 代表上次渲染中组件的 state 和 props,如果成功更新了,新的 pendingProps 和 newState 将会代替这两个的值 -
ref,引用标识 -
stateNode,代表 fiber 节点的对应真实状态。- 原生组件,则指向一个dom节点
- 类组件,指向对应的类实例
- 函数组件,指向Null
- RootFiber,指向FiberRoot
渲染过程
-
PrepareFreshStack
准备干净的栈,最主要是要创建双向缓冲变量 WorkInProgress
双区缓冲变量对应的是 current,current 表示当前页面上显示的 fiber 节点,双区表示下一秒将要渲染的状态,处于 pending 状态,会取代之前的 current
在渲染到页面之前,current 和 WIP 的 alternate 字段分别指向彼此
WIP 继承了 current 的核心属性,elementType、type、stateNode 等
-
WorkLoop 工作循环
会执行一个 while 语句,每执行一次循环都会完成对一个 fiber 的处理,其中有一个指针 workInProgress 指向正在处理的 fiber,不断向链尾直到 null
跳出的条件只有 fiber 遍历结束、当前线程的权限移交给外部队列
-
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 的基本原理中的初次渲染进行分析阐述,后面还会介绍它如何进行事件更新进行分析,如果各位需要更详细的资料和源码那就还是去看官方文档比较好。