引言
现代前端框架(如Vue、React)的核心之一是响应式数据流。但你是否好奇这些框架背后的状态管理库是如何工作的?本文将从零开始,手写一个轻量级的状态管理库,深入探讨响应式原理与依赖追踪。通过这一过程,你将理解Vue的reactive和watchEffect的底层机制,并掌握实现一个最小化状态管理库的关键技术。

核心概念:响应式与依赖追踪
要构建状态管理库,首先需要理解两个核心概念:
- 响应式数据:当数据发生变化时,自动更新相关视图或副作用。
- 依赖追踪:在读取数据时记录哪些依赖(如组件、函数)使用了该数据,并在数据变化时通知它们。
核心思想
我们将使用发布-订阅模式来实现依赖追踪。每个响应式属性都有一个“依赖列表”(订阅者列表)。当属性被读取时,将当前的“观察者”(例如一个正在执行的副作用函数)添加到该属性的依赖列表中;当属性被赋值时,通知所有订阅者执行更新。
第一步:实现一个简单的响应式系统
1.1 全局变量和辅助函数
首先,我们需要一个全局变量来存储当前正在执行的观察者(例如一个副作用函数)。同时,我们需要一个函数来运行副作用,并在运行期间将自身设置为当前观察者。
let activeEffect = null; // 当前激活的副作用函数
function effect(fn) {
activeEffect = fn;
fn(); // 执行fn,触发依赖收集
activeEffect = null; // 执行完毕,清除
}1.2 响应式函数 reactive
reactive函数接收一个普通对象,并返回一个代理(Proxy),拦截属性的读取和设置操作。
function reactive(target) {
// 使用Map存储每个属性对应的依赖集合
const depsMap = new Map();
const handler = {
get(target, key, receiver) {
// 如果存在当前激活的副作用,则进行依赖收集
if (activeEffect) {
let deps = depsMap.get(key);
if (!deps) {
deps = new Set();
depsMap.set(key, deps);
}
deps.add(activeEffect); // 将当前副作用加入依赖
}
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 通知依赖该属性的所有副作用
const deps = depsMap.get(key);
if (deps) {
deps.forEach(effect => effect());
}
return result;
}
};
return new Proxy(target, handler);
}1.3 使用示例
const state = reactive({ count: 0, name: 'hello' });
effect(() => {
console.log('Count changed:', state.count);
});
effect(() => {
console.log('Name changed:', state.name);
});
state.count = 1; // 输出: Count changed: 1
state.name = 'world'; // 输出: Name changed: world第二步:支持深层嵌套与数组
上述实现仅处理了单层对象。实际应用中,对象可能多层嵌套,或者包含数组。我们需要递归地将所有嵌套对象变为响应式。

2.1 递归代理
修改reactive函数,使其对嵌套对象也创建代理。使用一个proxyMap来缓存已代理的对象,避免重复代理。
const proxyMap = new WeakMap();
function reactive(target) {
if (typeof target !== 'object' || target === null) {
return target;
}
// 如果已经代理过,直接返回
if (proxyMap.has(target)) {
return proxyMap.get(target);
}
const depsMap = new Map();
const handler = {
get(target, key, receiver) {
if (activeEffect) {
let deps = depsMap.get(key);
if (!deps) {
deps = new Set();
depsMap.set(key, deps);
}
deps.add(activeEffect);
}
const value = Reflect.get(target, key, receiver);
// 如果值是对象,递归使其响应式
if (typeof value === 'object' && value !== null) {
return reactive(value);
}
return value;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 如果值变化才触发通知
if (oldValue !== value) {
const deps = depsMap.get(key);
if (deps) {
deps.forEach(effect => effect());
}
}
return result;
}
};
const proxy = new Proxy(target, handler);
proxyMap.set(target, proxy);
return proxy;
}2.2 支持数组
数组的特殊性在于其索引访问、push、pop等方法。Proxy默认可以拦截数组索引,但像push这样的方法会触发多次get/set。我们使用一个技巧:在get时,如果key是数组方法(如'push'),则返回一个包装函数,自动追踪依赖。但为了简单,我们可以直接使用上述实现,数组的索引修改也能触发响应。
第三步:添加计算属性(computed)和监听(watch)
3.1 实现computed
计算属性是一个依赖其他响应式数据的“懒”值,只有在被访问时才重新计算,并且结果会被缓存。
function computed(getter) {
let value;
let dirty = true; // 标记是否需要重新计算
const effectFn = () => {
dirty = true;
// 当依赖变化时,标记dirty,并通知依赖computed的副作用
if (onDepend) onDepend();
};
let onDepend = null;
const obj = {
get value() {
if (dirty) {
activeEffect = effectFn;
value = getter();
activeEffect = null;
dirty = false;
}
// 如果有当前激活的副作用,将当前computed也加入依赖
if (activeEffect) {
if (!onDepend) {
onDepend = () => {
// 通知依赖computed的副作用
if (activeEffect) activeEffect();
};
}
// 将当前副作用加入依赖(通过effectFn间接触发)
effectFn(); // 实际上这里需要更精细的处理,简化版略
}
return value;
}
};
return obj;
}为了简化,我们暂不实现computed的依赖传播。完整工作留给读者思考。
3.2 实现watch
watch函数监听一个响应式数据,并在其变化时执行回调。
function watch(source, callback) {
effect(() => {
// 读取source,触发依赖收集
const newValue = typeof source === 'function' ? source() : source;
// 回调在新值变化时被调用(需要更复杂的设计来获取新旧值)
callback(newValue);
});
}更完善的实现应该支持获取旧值和新值,这里仅作演示。
第四步:封装为状态管理库
将以上功能组织成一个轻量级的库。提供reactive, effect, computed, watch等API。同时增加一个简单的状态容器:
class Store {
constructor(initialState) {
this.state = reactive(initialState);
}
// 类似Vuex的commit或dispatch
commit(mutation, payload) {
// 简化,直接修改状态
this.state[mutation] = payload;
}
}性能优化和注意事项
- 避免不必要的通知:在set时比较新旧值,只有变化时才触发。
- 批量更新:可以使用微任务队列合并多次更新,提高性能。
- 内存泄漏:当组件卸载时,需要清理依赖(使用WeakMap或手动取消)。

结语
通过本文,我们从零实现了一个轻量级的状态管理库,理解了响应式原理和依赖追踪的底层机制。这个简易库虽然不够完善,但已经揭示了Vue、MobX等库的核心思想。希望这能帮助你更好地使用现有的框架和库,甚至根据需求定制自己的状态管理方案。
如果你有任何问题,欢迎在评论区讨论!
觉得内容不错?我要