工程化与框架系列(13)--虚拟DOM实现
虚拟DOM实现 🌳
虚拟DOM(Virtual DOM)是现代前端框架的核心技术之一,它通过在内存中维护UI的虚拟表示来提高渲染性能。本文将深入探讨虚拟DOM的实现原理和关键技术。
虚拟DOM概述 🌟
💡 小知识:虚拟DOM是对真实DOM的一种轻量级抽象表示,它以JavaScript对象的形式存在,通过diff算法计算最小更新路径,从而减少对实际DOM的操作。
为什么需要虚拟DOM
在现代前端开发中,虚拟DOM带来以下优势:
-
性能优化
- 批量DOM更新
- 最小化DOM操作
- 跨平台渲染
- 服务端渲染
-
开发体验
- 声明式编程
- 组件化开发
- 状态驱动UI
- 代码可维护性
-
跨平台能力
- 浏览器渲染
- 原生应用渲染
- 服务端渲染
- Canvas/WebGL渲染
-
调试能力
- 状态追踪
- 组件调试
- 性能分析
- 错误边界
核心实现 ⚡
虚拟DOM节点定义
// vnode.ts
export type VNodeType = string | Component;
export interface VNode {
type: VNodeType;
props: Record<string, any>;
children: (VNode | string)[];
key?: string | number;
el?: HTMLElement | Text;
}
export interface Component {
render: () => VNode;
props?: Record<string, any>;
setup?: (props: Record<string, any>) => Record<string, any>;
}
export function h(
type: VNodeType,
props: Record<string, any> = {},
children: (VNode | string)[] = []
): VNode {
return {
type,
props,
children,
key: props.key
};
}
// JSX类型定义
declare global {
namespace JSX {
interface Element extends VNode {}
interface IntrinsicElements {
[elemName: string]: any;
}
}
}
// 创建文本节点
export function createTextVNode(text: string): VNode {
return {
type: 'text',
props: {},
children: [text]
};
}
// 创建Fragment
export function Fragment(props: Record<string, any>): VNode {
return {
type: 'fragment',
props,
children: props.children || []
};
}
DOM渲染实现
// renderer.ts
export class Renderer {
private container: HTMLElement;
constructor(container: HTMLElement) {
this.container = container;
}
render(vnode: VNode | null) {
if (vnode === null) {
// 卸载
if (this.container.firstChild) {
this.container.innerHTML = '';
}
return;
}
// 挂载或更新
const prevVNode = this.container._vnode;
if (!prevVNode) {
// 首次挂载
this.mount(vnode, this.container);
} else {
// 更新
this.patch(prevVNode, vnode, this.container);
}
this.container._vnode = vnode;
}
private mount(vnode: VNode, container: HTMLElement, anchor?: Node | null) {
const { type, props, children } = vnode;
if (typeof type === 'string') {
// 创建元素
const el = document.createElement(type);
vnode.el = el;
// 设置属性
this.patchProps(el, {}, props);
// 挂载子节点
children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
this.mount(child, el);
}
});
// 插入到容器
container.insertBefore(el, anchor || null);
} else if (typeof type === 'function') {
// 挂载组件
this.mountComponent(vnode, container, anchor);
}
}
private mountComponent(
vnode: VNode,
container: HTMLElement,
anchor?: Node | null
) {
const component = vnode.type as Component;
// 执行setup
let setupResult = {};
if (component.setup) {
setupResult = component.setup(vnode.props);
}
// 执行render
const renderVNode = component.render.call(setupResult);
// 挂载渲染结果
this.mount(renderVNode, container, anchor);
vnode.el = renderVNode.el;
}
private patch(
n1: VNode,
n2: VNode,
container: HTMLElement,
anchor?: Node | null
) {
if (n1.type !== n2.type) {
// 类型不同,直接替换
this.unmount(n1);
this.mount(n2, container, anchor);
return;
}
if (typeof n2.type === 'string') {
// 更新元素
const el = (n2.el = n1.el as HTMLElement);
// 更新属性
this.patchProps(el, n1.props, n2.props);
// 更新子节点
this.patchChildren(n1, n2, el);
} else if (typeof n2.type === 'function') {
// 更新组件
this.patchComponent(n1, n2, container);
}
}
private patchProps(
el: HTMLElement,
oldProps: Record<string, any>,
newProps: Record<string, any>
) {
// 移除旧属性
for (const key in oldProps) {
if (!(key in newProps)) {
if (key.startsWith('on')) {
const event = key.slice(2).toLowerCase();
el.removeEventListener(event, oldProps[key]);
} else {
el.removeAttribute(key);
}
}
}
// 设置新属性
for (const key in newProps) {
const newValue = newProps[key];
const oldValue = oldProps[key];
if (newValue !== oldValue) {
if (key.startsWith('on')) {
// 事件处理
const event = key.slice(2).toLowerCase();
if (oldValue) {
el.removeEventListener(event, oldValue);
}
el.addEventListener(event, newValue);
} else if (key === 'style') {
// 样式处理
if (typeof newValue === 'string') {
el.style.cssText = newValue;
} else {
for (const styleKey in newValue) {
el.style[styleKey] = newValue[styleKey];
}
}
} else if (key === 'class') {
// 类名处理
if (Array.isArray(newValue)) {
el.className = newValue.join(' ');
} else {
el.className = newValue;
}
} else {
// 其他属性
el.setAttribute(key, newValue);
}
}
}
}
private patchChildren(n1: VNode, n2: VNode, container: HTMLElement) {
const oldChildren = n1.children;
const newChildren = n2.children;
// 处理文本节点
if (typeof newChildren[0] === 'string') {
if (typeof oldChildren[0] === 'string') {
// 文本节点更新
if (newChildren[0] !== oldChildren[0]) {
container.textContent = newChildren[0];
}
} else {
// 替换为文本节点
container.textContent = newChildren[0];
}
return;
}
// 处理子节点数组
const oldLen = oldChildren.length;
const newLen = newChildren.length;
const commonLen = Math.min(oldLen, newLen);
// 更新公共部分
for (let i = 0; i < commonLen; i++) {
this.patch(
oldChildren[i] as VNode,
newChildren[i] as VNode,
container
);
}
if (newLen > oldLen) {
// 添加新节点
for (let i = commonLen; i < newLen; i++) {
this.mount(newChildren[i] as VNode, container);
}
} else if (oldLen > newLen) {
// 移除多余节点
for (let i = commonLen; i < oldLen; i++) {
this.unmount(oldChildren[i] as VNode);
}
}
}
private unmount(vnode: VNode) {
if (typeof vnode.type === 'string') {
vnode.el?.parentNode?.removeChild(vnode.el);
} else if (typeof vnode.type === 'function') {
// 组件卸载
if (vnode.el) {
vnode.el.parentNode?.removeChild(vnode.el);
}
}
}
}
Diff算法实现
// diff.ts
interface KeyToIndexMap {
[key: string]: number;
}
export function patchKeyedChildren(
oldChildren: VNode[],
newChildren: VNode[],
container: HTMLElement
) {
let oldStartIdx = 0;
let oldEndIdx = oldChildren.length - 1;
let newStartIdx = 0;
let newEndIdx = newChildren.length - 1;
let oldStartVNode = oldChildren[oldStartIdx];
let oldEndVNode = oldChildren[oldEndIdx];
let newStartVNode = newChildren[newStartIdx];
let newEndVNode = newChildren[newEndIdx];
const keyToIndexMap: KeyToIndexMap = {};
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (!oldStartVNode) {
oldStartVNode = oldChildren[++oldStartIdx];
} else if (!oldEndVNode) {
oldEndVNode = oldChildren[--oldEndIdx];
} else if (isSameVNode(oldStartVNode, newStartVNode)) {
// 头部节点相同
patch(oldStartVNode, newStartVNode, container);
oldStartVNode = oldChildren[++oldStartIdx];
newStartVNode = newChildren[++newStartIdx];
} else if (isSameVNode(oldEndVNode, newEndVNode)) {
// 尾部节点相同
patch(oldEndVNode, newEndVNode, container);
oldEndVNode = oldChildren[--oldEndIdx];
newEndVNode = newChildren[--newEndIdx];
} else if (isSameVNode(oldStartVNode, newEndVNode)) {
// 老头和新尾相同
patch(oldStartVNode, newEndVNode, container);
container.insertBefore(
oldStartVNode.el!,
oldEndVNode.el!.nextSibling
);
oldStartVNode = oldChildren[++oldStartIdx];
newEndVNode = newChildren[--newEndIdx];
} else if (isSameVNode(oldEndVNode, newStartVNode)) {
// 老尾和新头相同
patch(oldEndVNode, newStartVNode, container);
container.insertBefore(oldEndVNode.el!, oldStartVNode.el!);
oldEndVNode = oldChildren[--oldEndIdx];
newStartVNode = newChildren[++newStartIdx];
} else {
// 处理其他情况
if (!keyToIndexMap) {
// 生成旧节点的key映射
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
const key = oldChildren[i].key;
if (key != null) {
keyToIndexMap[key] = i;
}
}
}
// 在旧节点中寻找新头节点
const idxInOld = keyToIndexMap[newStartVNode.key!];
if (idxInOld === undefined) {
// 新节点
mount(newStartVNode, container, oldStartVNode.el!);
} else {
// 移动节点
const vnodeToMove = oldChildren[idxInOld];
patch(vnodeToMove, newStartVNode, container);
container.insertBefore(vnodeToMove.el!, oldStartVNode.el!);
oldChildren[idxInOld] = undefined as any;
}
newStartVNode = newChildren[++newStartIdx];
}
}
// 处理剩余节点
if (oldStartIdx > oldEndIdx) {
// 添加新节点
const anchor = newChildren[newEndIdx + 1]
? newChildren[newEndIdx + 1].el
: null;
for (let i = newStartIdx; i <= newEndIdx; i++) {
mount(newChildren[i], container, anchor);
}
} else if (newStartIdx > newEndIdx) {
// 移除多余节点
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
if (oldChildren[i]) {
unmount(oldChildren[i]);
}
}
}
}
function isSameVNode(n1: VNode, n2: VNode): boolean {
return n1.type === n2.type && n1.key === n2.key;
}
组件系统实现 🏗️
组件定义
// component.ts
export interface ComponentOptions {
name?: string;
props?: Record<string, PropOptions>;
setup?: (
props: Record<string, any>,
context: SetupContext
) => Record<string, any>;
render?: () => VNode;
}
interface PropOptions {
type: any;
required?: boolean;
default?: any;
validator?: (value: any) => boolean;
}
interface SetupContext {
attrs: Record<string, any>;
slots: Record<string, (...args: any[]) => VNode[]>;
emit: (event: string, ...args: any[]) => void;
}
export function defineComponent(options: ComponentOptions) {
return {
name: options.name,
props: options.props,
setup: options.setup,
render: options.render,
// 组件实例创建
create(props: Record<string, any>) {
// 创建组件实例
const instance = {
props: shallowReactive(props),
attrs: {},
slots: {},
emit: (event: string, ...args: any[]) => {
const handler = props[`on${capitalize(event)}`];
if (handler) {
handler(...args);
}
}
};
// 执行setup
if (options.setup) {
const setupContext = {
attrs: instance.attrs,
slots: instance.slots,
emit: instance.emit
};
const setupResult = options.setup(
instance.props,
setupContext
);
if (typeof setupResult === 'function') {
// setup返回渲染函数
instance.render = setupResult;
} else if (typeof setupResult === 'object') {
// setup返回状态对象
instance.setupState = proxyRefs(setupResult);
}
}
// 渲染函数
instance.render = options.render || instance.render;
return instance;
}
};
}
// 工具函数
function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
生命周期实现
// lifecycle.ts
export const enum LifecycleHooks {
BEFORE_CREATE = 'beforeCreate',
CREATED = 'created',
BEFORE_MOUNT = 'beforeMount',
MOUNTED = 'mounted',
BEFORE_UPDATE = 'beforeUpdate',
UPDATED = 'updated',
BEFORE_UNMOUNT = 'beforeUnmount',
UNMOUNTED = 'unmounted'
}
export function injectHook(
type: LifecycleHooks,
hook: Function,
target: any
): Function | undefined {
if (target) {
const hooks = target[type] || (target[type] = []);
const wrappedHook = (...args: any[]) => {
hook.call(target, ...args);
};
hooks.push(wrappedHook);
return wrappedHook;
}
}
export const createHook = (lifecycle: LifecycleHooks) => {
return (hook: Function, target: any = currentInstance) =>
injectHook(lifecycle, hook, target);
};
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT);
export const onMounted = createHook(LifecycleHooks.MOUNTED);
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE);
export const onUpdated = createHook(LifecycleHooks.UPDATED);
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT);
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED);
性能优化 ⚡
静态节点优化
// optimize.ts
interface StaticNodeAnalysis {
isStatic: boolean;
staticRoot: boolean;
}
export function optimizeNode(node: VNode): StaticNodeAnalysis {
if (typeof node.type === 'string') {
// 分析静态节点
const analysis: StaticNodeAnalysis = {
isStatic: true,
staticRoot: false
};
// 检查属性
for (const key in node.props) {
if (
key === 'v-if' ||
key === 'v-for' ||
key === 'v-model' ||
key.startsWith(':') ||
key.startsWith('@')
) {
analysis.isStatic = false;
break;
}
}
// 检查子节点
if (analysis.isStatic && node.children.length > 0) {
let allChildrenStatic = true;
let staticChildCount = 0;
for (const child of node.children) {
if (typeof child === 'object') {
const childAnalysis = optimizeNode(child);
if (!childAnalysis.isStatic) {
allChildrenStatic = false;
break;
}
if (childAnalysis.staticRoot) {
staticChildCount++;
}
}
}
analysis.staticRoot =
allChildrenStatic && staticChildCount > 0;
}
return analysis;
}
return {
isStatic: false,
staticRoot: false
};
}
// 标记静态根节点
export function markStaticRoots(node: VNode, analysis: StaticNodeAnalysis) {
if (analysis.staticRoot) {
node.staticRoot = true;
// 缓存静态子树
node.staticChildren = [...node.children];
}
// 递归处理子节点
for (const child of node.children) {
if (typeof child === 'object') {
const childAnalysis = optimizeNode(child);
markStaticRoots(child, childAnalysis);
}
}
}
更新优化
// update-optimization.ts
export class UpdateOptimizer {
private static readonly BATCH_SIZE = 1000;
private updates: Set<VNode> = new Set();
private updating = false;
queueUpdate(vnode: VNode) {
this.updates.add(vnode);
if (!this.updating) {
this.updating = true;
requestAnimationFrame(() => this.processUpdates());
}
}
private processUpdates() {
const updates = Array.from(this.updates);
this.updates.clear();
this.updating = false;
// 批量处理更新
for (let i = 0; i < updates.length; i += this.BATCH_SIZE) {
const batch = updates.slice(i, i + this.BATCH_SIZE);
this.processBatch(batch);
}
}
private processBatch(vnodes: VNode[]) {
// 按照组件层级排序
vnodes.sort((a, b) => getDepth(a) - getDepth(b));
// 合并同层级更新
const updateMap = new Map<number, VNode[]>();
for (const vnode of vnodes) {
const depth = getDepth(vnode);
if (!updateMap.has(depth)) {
updateMap.set(depth, []);
}
updateMap.get(depth)!.push(vnode);
}
// 按层级处理更新
for (const [depth, nodes] of updateMap) {
this.processDepthUpdates(nodes);
}
}
private processDepthUpdates(vnodes: VNode[]) {
// 处理同一层级的更新
for (const vnode of vnodes) {
if (vnode.staticRoot) {
// 跳过静态根节点
continue;
}
// 更新节点
patch(vnode, vnode, vnode.el!.parentNode);
}
}
}
function getDepth(vnode: VNode): number {
let depth = 0;
let current = vnode;
while (current.parent) {
depth++;
current = current.parent;
}
return depth;
}
最佳实践建议 ⭐
性能优化建议
-
节点优化
- 使用key标识
- 提取静态节点
- 避免深层嵌套
- 合理使用v-show
-
更新优化
- 批量更新
- 异步更新
- 合并操作
- 缓存结果
-
渲染优化
- 懒加载组件
- 虚拟滚动
- 时间切片
- 优先级调度
开发建议
- 组件设计
// 好的实践
const GoodComponent = defineComponent({
name: 'GoodComponent',
props: {
items: {
type: Array,
required: true
}
},
setup(props) {
// 提取复杂逻辑
const state = reactive({
selectedIndex: -1
});
// 计算属性
const filteredItems = computed(() =>
props.items.filter(item => item.visible)
);
return {
state,
filteredItems
};
}
});
// 避免这样做
const BadComponent = defineComponent({
render() {
// 渲染函数中包含复杂逻辑
const items = this.items.filter(item => {
return item.visible && this.complexCheck(item);
});
return h('div', {}, items.map(item =>
h('div', { key: item.id }, item.name)
));
}
});
- 更新处理
// 好的实践
function handleUpdates() {
// 批量更新
nextTick(() => {
state.count++;
state.total = calculateTotal();
});
}
// 避免频繁更新
function badUpdate() {
state.count++;
state.total = calculateTotal();
// 直接触发DOM更新
}
结语 📝
虚拟DOM是现代前端框架的重要基石,通过本文,我们学习了:
- 虚拟DOM的核心概念
- DOM diff算法的实现
- 组件系统的设计
- 性能优化的策略
- 开发中的最佳实践
💡 学习建议:
- 深入理解虚拟DOM原理
- 掌握diff算法的优化
- 注重性能优化实践
- 遵循最佳实践指南
- 持续学习新的优化方案
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻