工程化与框架系列(12)--响应式框架原理
响应式框架原理 🔄
响应式框架是现代前端开发的重要基础,它通过数据驱动的方式实现UI的自动更新。本文将深入探讨响应式框架的核心原理和实现方案。
响应式编程概述 🌟
💡 小知识:响应式编程是一种面向数据流和变化传播的编程范式。在前端开发中,它让我们能够以声明式的方式处理异步数据流和UI更新。
为什么需要响应式框架
在现代前端开发中,响应式框架带来以下优势:
-
开发效率提升
- 声明式编程
- 自动UI更新
- 状态管理简化
- 代码可维护性
-
性能优化
- 精确更新
- 批量处理
- 异步渲染
- 按需更新
-
状态管理
- 集中状态管理
- 数据流可追踪
- 状态变化可预测
- 调试工具支持
-
开发体验
- 代码简洁
- 逻辑清晰
- 复用性强
- 测试友好
响应式系统实现 ⚡
依赖收集
// dependency-tracking.ts
type Dep = Set<ReactiveEffect>;
type KeyToDepMap = Map<any, Dep>;
const targetMap = new WeakMap<any, KeyToDepMap>();
let activeEffect: ReactiveEffect | undefined;
export class ReactiveEffect {
private _fn: () => any;
public deps: Dep[] = [];
public active = true;
constructor(fn: () => any) {
this._fn = fn;
}
run() {
if (!this.active) {
return this._fn();
}
try {
activeEffect = this;
return this._fn();
} finally {
activeEffect = undefined;
}
}
stop() {
if (this.active) {
cleanupEffect(this);
this.active = false;
}
}
}
function cleanupEffect(effect: ReactiveEffect) {
effect.deps.forEach((dep: Set<ReactiveEffect>) => {
dep.delete(effect);
});
effect.deps.length = 0;
}
export function track(target: object, key: unknown) {
if (!activeEffect) return;
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
depsMap.set(key, (dep = new Set()));
}
trackEffects(dep);
}
export function trackEffects(dep: Dep) {
if (!activeEffect) return;
dep.add(activeEffect);
activeEffect.deps.push(dep);
}
export function trigger(target: object, key: unknown) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const dep = depsMap.get(key);
if (dep) {
triggerEffects(dep);
}
}
export function triggerEffects(dep: Dep) {
const effects = new Set(dep);
effects.forEach(effect => {
effect.run();
});
}
响应式对象实现
// reactive.ts
import { track, trigger } from './dependency-tracking';
const reactiveMap = new WeakMap<object, any>();
export function reactive<T extends object>(target: T): T {
// 如果已经是响应式对象,直接返回
if (reactiveMap.has(target)) {
return reactiveMap.get(target);
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key);
// 如果是对象,继续进行响应式转换
if (res && typeof res === 'object') {
return reactive(res);
}
return res;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 只有当值真正改变时才触发更新
if (oldValue !== value) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = key in target;
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
trigger(target, key);
}
return result;
}
});
reactiveMap.set(target, proxy);
return proxy;
}
// 创建只读对象
export function readonly<T extends object>(target: T): T {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
if (res && typeof res === 'object') {
return readonly(res);
}
return res;
},
set() {
console.warn('Cannot set value on readonly object');
return true;
},
deleteProperty() {
console.warn('Cannot delete property on readonly object');
return true;
}
});
}
// 创建计算属性
export function computed<T>(getter: () => T) {
let value: T;
let dirty = true;
const effect = new ReactiveEffect(getter);
return {
get value() {
if (dirty) {
value = effect.run();
dirty = false;
}
return value;
}
};
}
响应式渲染系统
// renderer.ts
interface VNode {
type: string | Component;
props: Record<string, any>;
children: (VNode | string)[];
}
interface Component {
render: () => VNode;
setup?: () => Record<string, any>;
}
export class Renderer {
private container: HTMLElement;
constructor(container: HTMLElement) {
this.container = container;
}
render(vnode: VNode) {
// 清空容器
this.container.innerHTML = '';
// 创建并挂载元素
const el = this.mount(vnode);
this.container.appendChild(el);
}
private mount(vnode: VNode): HTMLElement {
if (typeof vnode.type === 'string') {
// 处理原生HTML元素
return this.mountElement(vnode);
} else {
// 处理组件
return this.mountComponent(vnode);
}
}
private mountElement(vnode: VNode): HTMLElement {
const el = document.createElement(vnode.type as string);
// 设置属性
Object.entries(vnode.props || {}).forEach(([key, value]) => {
if (key.startsWith('on')) {
// 事件处理
const eventName = key.slice(2).toLowerCase();
el.addEventListener(eventName, value as EventListener);
} else {
// 普通属性
el.setAttribute(key, value);
}
});
// 处理子节点
vnode.children.forEach(child => {
if (typeof child === 'string') {
el.appendChild(document.createTextNode(child));
} else {
el.appendChild(this.mount(child));
}
});
return el;
}
private mountComponent(vnode: VNode): HTMLElement {
const component = vnode.type as Component;
// 执行setup函数
let setupResult = {};
if (component.setup) {
setupResult = component.setup();
}
// 创建渲染效果
const effect = new ReactiveEffect(() => {
const renderVNode = component.render.call(setupResult);
return this.mount(renderVNode);
});
// 执行渲染
return effect.run();
}
}
状态管理实现 🗃️
简单状态管理器
// store.ts
import { reactive } from './reactive';
export class Store<S extends object> {
private state: S;
private subscribers: Set<() => void> = new Set();
constructor(initialState: S) {
this.state = reactive(initialState);
}
getState(): S {
return this.state;
}
setState(partial: Partial<S>): void {
Object.assign(this.state, partial);
this.notify();
}
subscribe(callback: () => void): () => void {
this.subscribers.add(callback);
return () => {
this.subscribers.delete(callback);
};
}
private notify(): void {
this.subscribers.forEach(callback => callback());
}
}
// 使用示例
interface TodoState {
todos: { id: number; text: string; completed: boolean }[];
filter: 'all' | 'active' | 'completed';
}
const store = new Store<TodoState>({
todos: [],
filter: 'all'
});
// 订阅状态变化
store.subscribe(() => {
console.log('State updated:', store.getState());
});
// 更新状态
store.setState({
todos: [
{ id: 1, text: 'Learn TypeScript', completed: false }
]
});
组件绑定
// component-binding.ts
import { Store } from './store';
export function connect<S extends object, P extends object>(
component: Component,
mapStateToProps: (state: S) => P
) {
return {
...component,
setup() {
const store = Store.getInstance();
const state = reactive({}) as P;
// 初始化props
Object.assign(state, mapStateToProps(store.getState()));
// 订阅store变化
store.subscribe(() => {
Object.assign(state, mapStateToProps(store.getState()));
});
return state;
}
};
}
// 使用示例
const TodoList = {
render() {
return {
type: 'div',
props: {},
children: this.todos.map(todo => ({
type: 'div',
props: {
class: todo.completed ? 'completed' : ''
},
children: [todo.text]
}))
};
}
};
const ConnectedTodoList = connect(TodoList, (state: TodoState) => ({
todos: state.todos.filter(todo => {
if (state.filter === 'active') return !todo.completed;
if (state.filter === 'completed') return todo.completed;
return true;
})
}));
性能优化实现 ⚡
批量更新
// batch-update.ts
let isFlushing = false;
const queue = new Set<ReactiveEffect>();
export function queueJob(effect: ReactiveEffect) {
queue.add(effect);
if (!isFlushing) {
isFlushing = true;
Promise.resolve().then(flushJobs);
}
}
function flushJobs() {
try {
queue.forEach(effect => effect.run());
} finally {
isFlushing = false;
queue.clear();
}
}
// 修改trigger函数以支持批量更新
export function triggerEffects(dep: Dep) {
const effects = new Set(dep);
effects.forEach(effect => {
if (effect !== activeEffect) {
queueJob(effect);
}
});
}
虚拟DOM优化
// vdom-optimization.ts
interface VNode {
type: string | Component;
props: Record<string, any>;
children: (VNode | string)[];
key?: string | number;
}
export class VDomRenderer {
private oldVNode: VNode | null = null;
patch(newVNode: VNode, container: HTMLElement) {
if (this.oldVNode) {
// 更新
this.patchVNode(this.oldVNode, newVNode, container);
} else {
// 初始挂载
this.mount(newVNode, container);
}
this.oldVNode = newVNode;
}
private patchVNode(
oldVNode: VNode,
newVNode: VNode,
container: HTMLElement
) {
if (oldVNode.type !== newVNode.type) {
// 类型不同,直接替换
const el = this.mount(newVNode, container);
container.replaceChild(el, this.getEl(oldVNode));
return;
}
if (typeof newVNode.type === 'string') {
// 更新元素属性
this.patchProps(oldVNode, newVNode);
// 更新子节点
this.patchChildren(oldVNode, newVNode);
} else {
// 更新组件
this.patchComponent(oldVNode, newVNode);
}
}
private patchProps(oldVNode: VNode, newVNode: VNode) {
const el = this.getEl(oldVNode);
const oldProps = oldVNode.props || {};
const newProps = newVNode.props || {};
// 更新或添加新属性
Object.entries(newProps).forEach(([key, value]) => {
if (oldProps[key] !== value) {
this.setProp(el, key, value);
}
});
// 删除不再存在的属性
Object.keys(oldProps).forEach(key => {
if (!(key in newProps)) {
this.removeProp(el, key);
}
});
}
private patchChildren(oldVNode: VNode, newVNode: VNode) {
const el = this.getEl(oldVNode);
const oldChildren = oldVNode.children;
const newChildren = newVNode.children;
// 使用key优化列表更新
const oldKeyToIdx = new Map();
oldChildren.forEach((child, idx) => {
if (typeof child !== 'string' && child.key != null) {
oldKeyToIdx.set(child.key, idx);
}
});
let lastIndex = 0;
newChildren.forEach((newChild, i) => {
if (typeof newChild === 'string') {
// 文本节点直接更新
if (typeof oldChildren[i] === 'string') {
if (oldChildren[i] !== newChild) {
el.childNodes[i].textContent = newChild;
}
} else {
el.insertBefore(
document.createTextNode(newChild),
el.childNodes[i] || null
);
}
} else {
const key = newChild.key;
const oldIdx = key != null ? oldKeyToIdx.get(key) : null;
if (oldIdx == null) {
// 新节点
el.insertBefore(
this.mount(newChild, el),
el.childNodes[i] || null
);
} else {
// 移动节点
const oldChild = oldChildren[oldIdx];
this.patchVNode(oldChild as VNode, newChild, el);
if (oldIdx < lastIndex) {
el.insertBefore(
this.getEl(oldChild as VNode),
el.childNodes[i] || null
);
} else {
lastIndex = oldIdx;
}
}
}
});
// 删除多余的旧节点
while (el.childNodes.length > newChildren.length) {
el.removeChild(el.lastChild!);
}
}
private getEl(vnode: VNode): HTMLElement {
return (vnode as any).el;
}
private setProp(el: HTMLElement, key: string, value: any) {
if (key.startsWith('on')) {
const eventName = key.slice(2).toLowerCase();
el.addEventListener(eventName, value);
} else {
el.setAttribute(key, value);
}
}
private removeProp(el: HTMLElement, key: string) {
if (key.startsWith('on')) {
const eventName = key.slice(2).toLowerCase();
el.removeEventListener(eventName, el[key]);
} else {
el.removeAttribute(key);
}
}
}
最佳实践建议 ⭐
响应式设计原则
-
数据设计
- 合理的数据结构
- 最小化响应式数据
- 避免深层嵌套
- 使用不可变数据
-
性能优化
- 合理使用计算属性
- 避免不必要的响应
- 使用虚拟列表
- 异步组件加载
-
代码组织
- 组件职责单一
- 状态管理分层
- 复用逻辑抽象
- 测试覆盖完善
开发建议
- 响应式编程规范
// 好的实践
const state = reactive({
count: 0,
todos: []
});
// 避免这样做
const state = {
count: ref(0),
todos: reactive([])
};
// 使用计算属性
const completedTodos = computed(() =>
state.todos.filter(todo => todo.completed)
);
// 避免在计算属性中修改状态
const badComputed = computed(() => {
state.count++; // 不要这样做
return state.count;
});
- 组件设计
// 组件接口定义
interface Props {
items: string[];
onSelect: (item: string) => void;
}
// 组件实现
const ListComponent = {
props: ['items', 'onSelect'],
setup(props: Props) {
// 本地状态
const state = reactive({
selectedIndex: -1
});
// 方法
const handleSelect = (index: number) => {
state.selectedIndex = index;
props.onSelect(props.items[index]);
};
return {
state,
handleSelect
};
}
};
结语 📝
响应式框架为现代前端开发提供了强大的开发范式。通过本文,我们学习了:
- 响应式编程的核心概念
- 依赖收集和追踪的实现
- 响应式对象的处理方案
- 虚拟DOM和渲染优化
- 状态管理的最佳实践
💡 学习建议:
- 深入理解响应式原理
- 掌握性能优化技巧
- 实践响应式编程范式
- 注重代码质量和测试
- 持续学习新的优化方案
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻