当前位置: 首页 > article >正文

工程化与框架系列(12)--响应式框架原理

响应式框架原理 🔄

响应式框架是现代前端开发的重要基础,它通过数据驱动的方式实现UI的自动更新。本文将深入探讨响应式框架的核心原理和实现方案。

响应式编程概述 🌟

💡 小知识:响应式编程是一种面向数据流和变化传播的编程范式。在前端开发中,它让我们能够以声明式的方式处理异步数据流和UI更新。

为什么需要响应式框架

在现代前端开发中,响应式框架带来以下优势:

  1. 开发效率提升

    • 声明式编程
    • 自动UI更新
    • 状态管理简化
    • 代码可维护性
  2. 性能优化

    • 精确更新
    • 批量处理
    • 异步渲染
    • 按需更新
  3. 状态管理

    • 集中状态管理
    • 数据流可追踪
    • 状态变化可预测
    • 调试工具支持
  4. 开发体验

    • 代码简洁
    • 逻辑清晰
    • 复用性强
    • 测试友好

响应式系统实现 ⚡

依赖收集

// 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);
        }
    }
}

最佳实践建议 ⭐

响应式设计原则

  1. 数据设计

    • 合理的数据结构
    • 最小化响应式数据
    • 避免深层嵌套
    • 使用不可变数据
  2. 性能优化

    • 合理使用计算属性
    • 避免不必要的响应
    • 使用虚拟列表
    • 异步组件加载
  3. 代码组织

    • 组件职责单一
    • 状态管理分层
    • 复用逻辑抽象
    • 测试覆盖完善

开发建议

  1. 响应式编程规范
// 好的实践
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;
});
  1. 组件设计
// 组件接口定义
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
        };
    }
};

结语 📝

响应式框架为现代前端开发提供了强大的开发范式。通过本文,我们学习了:

  1. 响应式编程的核心概念
  2. 依赖收集和追踪的实现
  3. 响应式对象的处理方案
  4. 虚拟DOM和渲染优化
  5. 状态管理的最佳实践

💡 学习建议:

  1. 深入理解响应式原理
  2. 掌握性能优化技巧
  3. 实践响应式编程范式
  4. 注重代码质量和测试
  5. 持续学习新的优化方案

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻


http://www.kler.cn/a/567676.html

相关文章:

  • 16.2 LangChain 表达式语言设计哲学:重新定义大模型应用开发范式
  • 4.3MISC流量分析练习-wireshark-https
  • 哈工大信息管理与信息系统本科,有C++和Python基础,如何选择就业方向?
  • taoCMS v3.0.2 任意文件读取漏洞(CVE-2022-23316)
  • 如何保证 Redis 缓存和数据库的一致性?
  • Vue3:Vue Router的学习(四)
  • 民安智库:物业满意度调查的数据分析经验分享
  • 011 rocketmq过滤消息
  • JavaWeb——HTML
  • 工程化与框架系列(13)--虚拟DOM实现
  • XML 编辑器:全面指南与最佳实践
  • 基于vue3和spring boot实现大文件上传
  • 20250225-代码笔记03-class CVRPModel AND other class
  • 备战蓝桥杯Day11 DFS
  • Leetcode1 两数之和 python两种方法实现
  • 汽车低频发射天线介绍
  • Ae 效果详解:CC Cross Blur
  • [M数据结构] lc2353. 设计食物评分系统(数据结构+set 平衡树+懒删除堆)
  • nginx+keepalived实现高可用负载均衡
  • 【K8S】Kubernetes 中的基本组成部分介绍,一文了解 K8S 中的所有概念