1. 虚拟dom
Virtual Dom 是指用javascript去描述一个DOM结构,虚拟DOM不是直接去操作浏览器DOM,而是在虚拟DOM中对UI进行更新,减少不必要的真实DOM操作。
优点
- 性能优化:减少不必要的真实DOM操作,节省性能开销。(主要体现在diff算法等)
- 跨平台性:虚拟DOM是不受限平台的,不同平台可以做不同的映射目标。比如:虚拟DOM可以映射出微信小程序,electron,react , react native 多套UI。
Virtaul dom 简单实现
const React = {
createElement (type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
if (typeof child === 'object') {
return child
} else {
return React.createTextElement(child)
}
})
}
}
},
createTextElement (text) {
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: []
}
}
}
}
const vdom = React.createElement('div', { title: 'KKK' }, React.createElement('H1', {}, 'nihao'))
console.log(JSON.stringify(vdom, null, 2))
/*
{
"type": "div",
"props": {
"title": "KKK",
"children": [
{
"type": "H1",
"props": {
"children": [
{
"type": "TEXT_ELEMENT",
"props": {
"nodeValue": "nihao",
"children": []
}
}
]
}
}
]
}
}
*/
2. react 的 Fiber 架构
React Fiber 是 React 16 中引入的新的核心协调(reconciliation)引擎。
“Fiber”是对一个组件实例或 DOM 节点的抽象表示。
- 每个 React 组件都会对应一个 Fiber,包含相关的状态、属性、子节点等信息。
- Fiber 构成了一个树状结构(Fiber Tree),用于描述整个 UI 的层级关系。
Fiber 的能力/目标:
- ✅ 支持异步渲染(Concurrent Mode)
- ✅ 实现任务可中断/恢复
- ✅ 支持不同更新的优先级处理
- ✅ 支持双缓存树(Fiber Tree):Fiber 架构中有两棵Fiber树
current fiber tree(当前渲染的Fiber树) 和work in progress fiber tree(正在处理的Fiber树)。React 使用这两棵树来保存更新前后的状态,从而更高效地进行比较和更新 - ✅ 支持任务切片:在浏览器的空闲时间内(利用 requestldleCallback思想),React 可以将渲染任务拆分成多个小片段,逐步完成 Fiber 树的构建,避免一次性完成所有渲染任务导致的阻塞。
Fiber 的工作原理
1. 虚拟 DOM 到 Fiber 树的映射:
- Fiber 引入了一个可中断的渲染机制,渲染工作被分割成多个可以独立完成的小工作单元,由调度器来决定执行优先级和顺序。
- 在旧版 React 中,使用的是“递归渲染”,即从根组件开始逐层向下渲染,无法中断。
2. Reconciliation(协调)阶段:Diffing 算法的升级,Fiber 使用了改进的 Diffing 算法,将更新操作分为两个阶段:
- 👉 阶段一:Render Phase(构建 Fiber 树)
- React 创建一个新的 Fiber 树(称为
workInProgress树),基于当前的 JSX 和 props。 - 这个阶段可以被中断,以便优先处理更高优先级的任务(如用户输入)。
- React 创建一个新的 Fiber 树(称为
- 👉 阶段二:Commit Phase(提交到真实 DOM)
- 当 Render 阶段完成后,React 将差异一次性更新到真实 DOM。
- 此阶段不可中断,必须完整执行。
3. 任务调度与优先级管理
- Fiber 引入了任务优先级的概念
- React 使用内部的
Scheduler(调度器)来决定哪些任务应该先执行,哪些可以延迟。
3. react工作流程(虚拟dom + fiber初始化,简单实现)
// virtaul dom
const React = {
// type 标签类型,props 标签包含属性 children 是子元素
createElement (type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
if (typeof child === 'object') {
return child
} else {
return React.createTextElement(child)
}
})
}
}
},
createTextElement (text) {
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: []
}
}
}
}
// 工作单元fiber初始化
let nextUnitWork = null // 下一个工作单元
let wipRoot = null // 工作中的根节点,fiber树
let currentRoot = null // 当前fiber树(旧的fiber树,因为马上要替换)
let deletions = null // 要删除的fiber节点单元
function render (element, container) {
wipRoot = {
dom: container,
props: {
children: [element]
},
alternate: currentRoot //关联旧的fiber树
}
deletions = []
nextUnitWork = wipRoot
}
// 工作单元fiber执行
function workloop (deadline) {
let shouldYield = false // 是否有空闲时间
// 有空闲时间 且 有任务
while (nextUnitWork && !shouldYield) {
nextUnitWork = performUnitWork(nextUnitWork)
shouldYield = deadline.timeRemaining() < 1
}
if (!nextUnitWork && wipRoot) {
commitRoot()
}
requestIdleCallback(workloop)
}
requestIdleCallback(workloop)
// 创建fiber节点
const createFiber = (element, parent) => {
return {
type: element.type,
props: element.props,
parent: parent,
dom: null,
alternate: null,
effectTag: null,
sibling: null,
child: null
}
}
// 构建子节点fiber树,遍历子节点和兄弟节点,diff算法实现
const reconcileChildren = (fiber, elements) => {
// 生成fiber three
let index = 0
let prevSibling = null // 上一个兄弟节点
// diff 算法
let oldFiber = fiber.alternate && fiber.alternate.child
while (index < elements.length || oldFiber !== null) {
const element = elements[index]
let newFiber = null
// 复用节点
const someFiber = oldFiber && element && element.type === oldFiber.type
if (someFiber) {
console.log('复用节点', element)
newFiber = {
type: oldFiber.type,
props: element.props,
parent: fiber,
dom: oldFiber.dom,
alternate: oldFiber,
effectTag: 'UPDATE' // 更新
}
}
// 新增节点
if (element && !someFiber) {
console.log('新增节点', element)
newFiber = createFiber(element, fiber)
newFiber.effectTag = 'PLACEMENT' // 新增
}
// 删除节点
if (oldFiber && !someFiber) {
console.log('删除节点', oldFiber)
oldFiber.effectTag = "DELETION"
deletions.push(oldFiber)
}
if (oldFiber) {
oldFiber = oldFiber.sibling
}
if (index === 0) {
fiber.child = newFiber
} else if (element) {
prevSibling.sibling = newFiber
}
prevSibling = newFiber
index++
}
return null
}
function creatDom (fiber) {
const dom = fiber.type === 'TEXT_ELEMENT' ?
document.createTextNode('') :
document.createElement(fiber.type)
updateDom(dom, {}, fiber.props)
return dom
}
const updateDom = (dom, prevProps, nextProps) => {
// 删除旧属性
Object.keys(prevProps).filter(key => key !== 'children').forEach(key => {
dom[key] = ''
})
// 添加新属性
Object.keys(nextProps).filter(key => key !== 'children').forEach(key => {
dom[key] = nextProps[key]
})
}
// 执行工作单元
const performUnitWork = (fiber) => {
if (!fiber.dom) {
fiber.dom = creatDom(fiber)
}
const elements = fiber.props.children
reconcileChildren(fiber, elements)
// 遍历子节点
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
// 如果有兄弟节点就返回兄弟节点
if (nextFiber.sibling) {
return nextFiber.sibling
}
// 如果没有兄弟节点就返回上级节点
nextFiber = nextFiber.parent
}
return null
}
const commitRoot = () => {
deletions.forEach(commitWork)
commitWork(wipRoot.child)
currentRoot = wipRoot
wipRoot = null
}
const commitWork = (fiber) => {
if (!fiber) {
return
}
const domParent = fiber.parent.dom
if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
domParent.appendChild(fiber.dom)
} else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
updateDom(fiber.dom, fiber.alternate.props, fiber.props
)
} else if (fiber.effectTag === "DELETION") {
domParent.removeChild(fiber.dom)
}
commitWork(fiber.child)
commitWork(fiber.sibling)
}
const vdom = React.createElement("div", { id: "1" }, React.createElement("span", null, "我靠"))
const vdom2 = React.createElement("div", { id: "2" }, React.createElement("H1", null, "你妹"))
render(vdom, document.getElementById('root'))
setTimeout(() => {
render(vdom2, document.getElementById('root'))
}, 2000)
4. Diff 算法中,层级比较逻辑
- 按层级逐层比较:从根节点开始,逐层向下进行
- 同层节点按顺序比:对同一层级的节点,从左到右,按索引顺序一一对应比较
- 元素类型比较:
- 元素类型不相同:直接销毁旧节点及整个子树,新建新节点。
- 元素类型相同:对比属性,
props(包括style、className、事件等)。- 属性不同:不重建节点,只更新变化的局部属性,其余保持不变。
- 属性相同:保持不变
- 递归子节点:对子节点列表重复步骤 2-3
5. react 中的核心包
5.1. react
5.2. react-dom
5.3. react-reconciler
5.4. scheduler
6. react 工作原理(简述)
6.1. 初始化阶段
- 创建根节点
- 调用
ReactDom.createRoot(contianer)创建FiberRootNode顶层根节点(根管理器)。 - 内部调用
createFiberRoot初始化FiberRootNode结构,设置current指针指向初始的Fiber 树根节点,并初始化其他必要字段,如:penddingLanesupdateQueue等penddingLanes:集中追踪所有待处理的更新任务及其优先级updateQueue:更新队列
- 调用
- 挂载应用
- 调用
render(element)将<App/>挂载到指定容器内。 - 内部通过
scheduleUpdateOnFiber函数,将一个更新任务加入到FiberRootNode的更新队列中。
- 调用
6.2. 协调/渲染阶段 (Reconciliation / Render Phase - 构建 Fiber 树)
- 任务调度
Scheduler调度器根据任务的优先级(通过lanes系统)来决定何时执行该任务。- 当有更高优先级的任务插入时,低优先级任务会被暂停或延迟执行。
- 进入工作循环
- 当任务被调度时,进入递归的“工作循环” (work loop),开始处理 FiberRootNode 中的更新(并发模式使用
performConcurrentWorkOnRoot处理更新)。 - 函数会进入工作循环,开始处理 Fiber 树上的每个节点,检查它们是否需要更新,并进行必要的计算。
- 当任务被调度时,进入递归的“工作循环” (work loop),开始处理 FiberRootNode 中的更新(并发模式使用
- 处理
Fiber节点beginWork函数,从FiberRootNode的current指针开始深度优先遍历 Fiber 树。- 对
Fiber每个节点调用beginWork函数,检查该节点是否需要更新。 beginWork函数会根据节点类型(如:HostComponent、FunctionComponent、ClassComponent等)调用处理相应的处理函数。
- 对
reconcileChildren函数- 在
beginWork中,对于需要更新的节点,调用reconcileChildren函数来比较新旧子节点。 reconcileChildren使用 diffing 算法来找出需要添加、删除或更新的子节点,并生成相应的Update对象。
- 在
completeWork函数:- 当一个 Fiber 节点的所有子节点都处理完毕后,调用
completeWork函数完成该节点的工作。 completeWork会创建或更新对应的 DOM 节点,但不立即插入到页面。
- 当一个 Fiber 节点的所有子节点都处理完毕后,调用
- 这个阶段是可中断的(在并发模式下),如果浏览器需要处理更高优先级的任务(如用户输入),React 可以暂停当前工作,稍后恢复。
6.3. 提交阶段 (Commit Phase - 更新真实 DOM)
- 提交更改
- 当整个 Fiber 树的“渲染/协调”阶段完成(生成了一个待提交的完成树 (finished work tree) ),进入不可中断的提交阶段。
- 将完成树替换为当前的当前树 (current tree) 。
- 调用
commitRoot函数commitRoot函数会遍历已完成的 Fiber 树- 将所有变更应用到实际 DOM 上(
commitMutationEffects执行插入、删除、更新 DOM 节点等 突变操作)。appendChild: 将新创建的 DOM 节点一次性插入到根容器(如#root)中。insertBefore,removeChild等。
- 并触发相应的生命周期方法或 Hooks 效果函数。
commitLayoutEffects: 在DOM更新后、浏览器绘制前,执行useLayoutEffect的回调。useEffect: 将useEffect的回调放入微任务队列,等待浏览器绘制完成后执行。
- 浏览器绘制
- 提交阶段完成后,浏览器引擎接管,将更新后的 DOM 树绘制到屏幕上,用户首次看到应用界面。
6.4. 更新队列 & 批量更新
- 更新队列管理
enqueueUpdate函数: 当组件调用setState或dispatch时,enqueueUpdate函数 会检查当前 Fiber 是否有正在进行的更新任务? 有,将其添加到相应 Fiber 节点的更新队列中;否则,会调用createUpdate创建一个新的Update对象,调度一个新的更新任务。
- 批量更新
batchedUpdates函数: 在事件处理函数中,会自动启用批量更新机制。batchedUpdates会将多个setState调用合并成一个批次进行处理,减少不必要的重渲染和 DOM 操作。
6.5. 优先级调度
lanes系统:- React 使用
lanes系统来管理不同优先级的更新。 - 每个更新都会被分配一个或多个
lane,表示其优先级。 scheduler根据lanes来决定何时执行更新任务。
- React 使用
6.6. 并发模式
使用 createRoot 替代 ReactDOM.render 启用并发模式。
并发模式核心特性:
- 可中断的渲染:允许 React 在渲染过程中暂停较不重要的任务,以便优先处理更重要的任务。
- 优先级调度:基于
lanes系统实现,React 根据任务的紧急程度来安排其执行顺序。 - 自动批处理:将多个状态更新合并成一个批次进行处理,减少不必要的重渲染次数。
scheduler会在浏览器空闲时间执行低优先级任务,以保证高优先级任务得到及时处理。
6.7. react 工作原理流程图
7. react 中hooks为什么不能写在判断里?
React 需要确保每次组件渲染时都以相同的顺序调用 Hooks,如果根据条件逻辑跳过某些 Hook 调用,Hooks 的调用无法正确入栈,无法按顺执行。
- React 使用一种链表结构来跟踪和管理组件内的每个Hook状态,顺序的错乱会导致链表节点无法对齐。
- Hooks 依赖于先前的渲染结果来维护组件的状态或执行相应的副作用,调用顺序不固定,会导致某些副作用未被正确执行或清理(内存泄漏)。
8. MessageChannel 消息通信
MessageChannel 是一种高效的跨上下文通信机制,利用两个端口进行消息传递。 它本质上是一个宏任务,但由于执行延迟极低,常被用来模拟微任务行为。常用于:
- Worker 与主线程之间的通信
- iframe 之间的通信
- 实现高性能、异步任务调度(如自定义微任务队列)
- 替代 requestIdleCallback() 或实现类似行为。
MessageChannel 代码通讯示例:
const channel = new MessageChannel()
channel.port1.onmessage = (res) => {
console.log('channel.port1.onmessage : ', res.data)
}
setTimeout(() => {
channel.port2.postMessage('我发送了一个消息')
}, 2000);
9. react 调度器(基于MessageChannel模拟react调度器)
模拟react调度器:
// 模拟react调度器,使用 MessageChannel 模拟 requestIdleCallback 来完成
const ImmediatePriority = 1 // 立即执行的优先级,优先级最高,如:点击事件
const UserBlockingPriority = 2 // 用户阻塞级别的优先级,如:滚动,校验
const NormalPriority = 3 // 一般优先级,如:render动画,异步请求
const LowPriority = 4 // 低优先级,如:数据埋点上报
const Idelpriority = 5 // 极低优先级,如:console
function getCurrentTime () {
return performance.now()
}
class SimpleScheduler {
constructor() {
this.taskQueue = [] // 任务队列
this.isPerformingTask = false // 是否在执行任务
const channel = new MessageChannel()
this.port = channel.port2
channel.port1.onmessage = this.performTaskUnitDeadline.bind(this)
}
schedulerCallback (priority, callback) {
var time = getCurrentTime()
this.port.postMessage('触发任务') // 触发任务
let timeout = null
switch (priority) {
case ImmediatePriority:
timeout = -1
break
case UserBlockingPriority:
timeout = 250
break
case NormalPriority:
timeout = 5000
break
case LowPriority:
timeout = 10000
break
case Idelpriority:
timeout = 13132131
break
default:
timeout = 5000
break
}
const newTask = {
callback,
priority,
expirationTime: time + timeout
}
this.push(this.taskQueue, newTask)
if (!this.isPerformingTask) {
this.isPerformingTask = true
this.port.postMessage(null)
}
}
performTaskUnitDeadline () {
this.isPerformingTask = true
this.teskloop()
this.isPerformingTask = false
}
teskloop () {
while (this.taskQueue.length > 0) {
const currentTask = this.peak(this.taskQueue)
if (currentTask) {
const cb = currentTask.callback
cb && cb()
this.pop(this.taskQueue)
} else {
break
}
}
}
push (queue, task) {
queue.push(task)
// 排序
queue.sort((a, b) => a.expirationTime - b.expirationTime)
}
peak (queue) {
return queue[0] || null
}
pop (queue) {
queue.shift()
}
}
const s = new SimpleScheduler()
s.schedulerCallback(UserBlockingPriority, () => {
console.log('2')
})
s.schedulerCallback(ImmediatePriority, () => {
console.log('1')
})
10. react 为什么自己开发一个Scheduler调度器? 而不用 requestIdleCallback?
- 兼容性与控制力:
requestIdleCallback浏览器支持有限,且调用频率低(约每秒30次),无法满足React高频、细粒度调度需求。 - 更灵活的优先级机制:React需实现不同优先级任务(如高优交互、低优渲染),Scheduler可自定义优先级策略,而
requestIdleCallback仅提供基础空闲回调。 - 时间切片(Time Slicing) :Scheduler能在一帧内预留时间执行任务,实现任务中断与恢复,
requestIdleCallback无法精确控制帧时间。 - 统一跨平台调度:Scheduler可在浏览器、服务端、原生App等环境统一运行,不依赖浏览器API。
- 更优的帧协调:结合
requestAnimationFrame,Scheduler能更精准判断帧结束,避免requestIdleCallback延迟不可控的问题。
11. react 合成事件是什么?
React的合成事件(SyntheticEvent)系统是为了解决跨浏览器兼容性问题,并提供一个统一的接口来处理各种类型的事件。
原理:
- 事件委托:组件内的元素绑定事件处理器时,通过事件委托的方式,将所有事件处理器都绑定到了根节点(通常是
document),由React的事件系统进行处理。 - 合成事件对象:React创建一个跨浏览器的、与W3C标准兼容的事件对象,封装原生浏览器事件。提供与原生事件相似的接口,但消除了不同浏览器之间的差异,使得事件处理逻辑可以一致地运行在不同的浏览器环境中。
- 事件池机制:React使用了一个事件池来管理合成事件对象。事件触发后,React会从事件池中取出一个合成事件对象,填充它并将其传递给相应的事件处理器。事件处理器执行完毕,该合成事件对象会被回收至事件池中以便重用。这种机制减少了垃圾回收的压力,提高了性能。
- 跨平台抽象:React的合成事件不局限于Web平台,它也为其他平台(如React Native)提供了一致的事件处理模型,代码可以在不同平台上共享或更容易移植。
主要特点
- 一致性:无论目标浏览器如何实现事件,React的合成事件都提供了一致的API和行为。
- 性能优化:通过事件委托和事件池机制减少了内存消耗和事件处理器的数量,提高了性能。
- 扩展性:允许框架自身以及第三方开发者对事件系统进行扩展,支持自定义事件类型等高级功能。
12. JSX 本质是什么? 为什么浏览器无法直接解析?
JSX(JavaScript XML)本质是为迎合声明式UI编程而设计的 JavaScript 语法扩展。(JSX是JS的扩展语法)
为什么浏览器无法直接解析?
- 浏览器只能执行符合ECMAScript标准的 JavaScript 代码。
- JSX 代码在运行之前转换成标准的 JS 代码。
react中把jsx转为标准js的方式:
- Create React App (CRA) 默认使用 Babel 来进行 JSX 语法的转换。CRA 内部配置Babel及其相关插件(如
@babel/preset-react)。 - Next.js:默认使用 SWC,支持 JSX、TS、React 编译,无需额外配置。
- Vite:使用插件
@vitejs/plugin-react-swc来启用 SWC 支持 JSX。
13. react 组件设计思想? 如何提高代码复用性?
组件的定义:
- UI 层 JSX 结构
- 逻辑层
- 状态层,useContent / useState / redux / jotai / Zustand
组件的特点:
- 独立性:封装自己的结构、样式、行为和状态。
- 可复用性:一个组件可以在多个地方重复使用,减少重复代码。
- 可组合性:组件之间可以嵌套、组合,形成更复杂的 UI。
- 状态与 UI 分离:状态变化自动驱动 UI 更新
如何提高代码复用性?
- 组件单一职责原则:一个组件只做一件事。
- UI 组件尽量无状态(stateless),由父组件或状态管理工具控制。
- 通过
props传递数据和行为,而不是在组件内部写死内容。 - 状态逻辑交给 Hook 或状态管理工具(如 Redux、Zustand)
- 通过
- 支持 children 和 render props。
- 使用自定义 Hook 复用逻辑 和 高阶组件(HOC)复用逻辑。
14. react 的“组件即函数”的理念? 类组件和函数组件本质区别?
组件即函数(Component as a Function)指的是:一个组件本质上就是一个JS函数。
- 这个函数组件接收
props(输入) - 返回一个描述 UI 的 JSX(输出)
- React 会根据这个输出更新 DOM
函数式编程思想:
- 纯函数:相同的输入,应返回相同的输出,且无副作用。
- 不可变性:任何输入都会返回新值,不会修改原值。
- 高阶函数:参数支持函数,也可以返回函数和值,如:函数柯里化。
- 函数组合:通过组合多个小函数来构建复杂逻辑。
类组件和函数组件本质区别?
| 本质 | JavaScript 类(Class) | JavaScript 函数 |
|---|---|---|
| 状态管理 | 使用 this.state + setState | 使用 useState Hook |
| 生命周期 | 依赖类生命周期方法(如 componentDidMount) | 使用 useEffect Hook 模拟生命周期 |
| this 的绑定 | 需要手动绑定 this | 无 this,更简洁 |
| 组件实例 | 有实例(this),可访问内部状态和方法 | 无实例,函数每次渲染都是独立的 |
| 性能优化 | 使用 React.memo、shouldComponentUpdate | 使用 React.memo 或 useCallback、useMemo |
15. react 严格模式有哪些弊端?
- 生命周期的双重调用:严格模式下会故意触发某些生命周期方法两次,这是为了帮助检测那些不应该依赖于单次调用的副作用。