工程化与框架系列(12)--响应式框架原理
- 手机
- 2025-09-22 07:36:01

响应式框架原理 🔄
响应式框架是现代前端开发的重要基础,它通过数据驱动的方式实现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 { .. ponent, 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 pleted ? 'completed' : '' }, children: [todo.text] })) }; } }; const ConnectedTodoList = connect(TodoList, (state: TodoState) => ({ todos: state.todos.filter(todo => { if (state.filter === 'active') return !todo pleted; if (state.filter === 'completed') return todo pleted; 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 pleted) ); // 避免在计算属性中修改状态 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和渲染优化状态管理的最佳实践💡 学习建议:
深入理解响应式原理掌握性能优化技巧实践响应式编程范式注重代码质量和测试持续学习新的优化方案如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻
工程化与框架系列(12)--响应式框架原理由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“工程化与框架系列(12)--响应式框架原理”