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. Fiber 架构
React Fiber 是 React 16 中引入的新的核心协调(reconciliation)引擎
“Fiber”是对一个组件实例或 DOM 节点的抽象表示。每个 Fiber 对象代表了某个组件或元素,并保存了与之相关的状态、属性、子节点等信息
- 每个 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 树的映射
- 在旧版 React 中,使用的是“递归渲染”,即从根组件开始逐层向下渲染,无法中断。
- Fiber 引入了一个可中断的渲染机制,将渲染过程拆分成多个小任务,由调度器来决定执行顺序。
2. Reconciliation(协调)阶段:Diffing 算法的升级
Fiber 使用了改进的 Diffing 算法,将更新操作分为两个阶段:
-
👉 阶段一:Render Phase(构建 Fiber 树)
- React 创建一个新的 Fiber 树(称为
workInProgress
树),基于当前的 JSX 和 props。 - 这个阶段可以被中断,以便优先处理更高优先级的任务(如用户输入)。
- React 创建一个新的 Fiber 树(称为
-
👉 阶段二:Commit Phase(提交到真实 DOM)
- 当 Render 阶段完成后,React 将差异一次性更新到真实 DOM。
- 此阶段不可中断,必须完整执行。
3. 任务调度与优先级管理
- Fiber 引入了任务优先级的概念,例如:
- 用户输入 > 动画 > 数据更新
- 可以通过
ReactDOM.flushSync()
手动提升某些更新的优先级
- React 使用内部的
Scheduler(调度器)
来决定哪些任务应该先执行,哪些可以延迟。
3. 虚拟dom + fiber初始化,简单实现react工作流程
// 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)
9. 消息通讯 MessageChannel
MessageChannel
是一种高效的跨上下文通信机制,利用两个端口进行消息传递。
它本质上是一个宏任务,但由于执行延迟极低,常被用来模拟微任务行为
常用于:
- Worker 与主线程之间的通信
- iframe 之间的通信
- 实现高性能、异步任务调度(如自定义微任务队列)
- 替代 requestIdleCallback() 或实现类似行为。
使用示例:
const channel = new MessageChannel()
channel.port1.onmessage = (res) => {
console.log('channel.port1.onmessage : ', res.data)
}
setTimeout(() => {
channel.port2.postMessage('我发送了一个消息')
}, 2000);
10. 模拟React调度器(基于MessageChannel)
// 模拟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')
})