根据响应式组件通知效果可以知道,响应式机制的主要功能就是,可以把普通的 JavaScript 对象封装成为响应式对象,拦截数据的获取和修改操作,实现依赖数据的自动化更新。
一个最简单的响应式模型,我们可以通过 reactive 函数,把数据包裹成响应式对象,并且通过 effect 函数注册回调函数,然后在数据修改之后,响应式地通知 effect 去执行回调函数即可。
整个流程这么概括地说,你估计不太理解,我们先通过一个简单的小例子直观感受一下响应式的效果。Vue 的响应式是可以独立在其他平台使用的。比如你可以新建 test.js,使用下面的代码在 node 环境中使用 Vue 响应。以 reactive 为例,我们使用 reactive 包裹 JavaScript 对象之后,每一次对响应式对象 counter 的修改,都会执行 effect 内部注册的函数:
1 2 3 4 5 6 7 8 9 10 11 12
| import { effect, reactive } from "vue";
let dummy const counter = reactive({ num1: 1, num2: 2 }) effect(() => { dummy = counter.num1 + counter.num2 console.log(dummy) }) setInterval(()=>{ counter.num1++ },1000)
|
执行 node test.js 之后,你就可以看到 effect 内部的函数会一直调用,每次 count.value 修改之后都会执行。
接下来给出模块化实现,便于拆分理解与复用。
模块化实现(reactivity/)
为便于理解与复用,可将核心拆分为以下文件:
1 2 3 4 5 6
| reactivity/ ├── effect.js // ReactiveEffect / effect / scheduler ├── track.js // track / trigger / targetMap ├── reactive.js // reactive / Proxy ├── handlers.js // mutableHandlers └── index.js // 使用示例
|
reactivity/effect.js
内容与功能说明:
- 核心类
ReactiveEffect:封装副作用函数 fn 与可选 scheduler;支持 run() 执行与 stop() 解绑
- 依赖反向记录:
deps 保存该 effect 被哪些依赖集合收集,stop 时统一清理
effect(fn, { scheduler }):创建并立即执行,返回 runner 以便手动触发或复用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| export let activeEffect = null; export const effectStack = [];
export class ReactiveEffect { constructor(fn, scheduler) { this.fn = fn; this.scheduler = scheduler; this.active = true; this.deps = []; } run() { if (!this.active) return this.fn(); try { effectStack.push(this); activeEffect = this; return this.fn(); } finally { effectStack.pop(); activeEffect = effectStack[effectStack.length - 1] || null; } } stop() { if (this.active) { this.deps.forEach(dep => dep.delete(this)); this.deps.length = 0; this.active = false; } } }
export function effect(fn, options = {}) { const _effect = new ReactiveEffect(fn, options.scheduler); _effect.run(); return _effect.run.bind(_effect); }
|
reactivity/track.js
内容与功能说明:
- 依赖桶结构:
WeakMap<Target, Map<Key, Set<Effect>>>
track(target,key):在读取时把当前活跃 effect 收集到集合中,避免重复收集
trigger(target,key):在写入时取出集合执行;如存在 scheduler,则走调度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { activeEffect } from './effect.js';
export const targetMap = new WeakMap();
export function track(target, key) { 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())); if (!dep.has(activeEffect)) { dep.add(activeEffect); activeEffect.deps && activeEffect.deps.push(dep); } }
export function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (!dep) return;
[...dep].forEach(effect => { if (effect.scheduler) { effect.scheduler(); } else { effect.run(); } }); }
|
reactivity/handlers.js
内容与功能说明:
get:通过 Reflect.get 取值并调用 track 收集依赖;对子对象做懒代理
set:仅当新旧值不等时调用 trigger 触发;保持与 Proxy 语义一致
- 与
reactive 解耦:职责仅为“拦截 + 依赖编织”,不关心缓存细节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { track, trigger } from './track.js'; import { reactive } from './reactive.js';
const isObject = (v) => v !== null && typeof v === 'object';
export const mutableHandlers = { get(target, key, receiver) { const res = Reflect.get(target, key, receiver); track(target, key); return isObject(res) ? reactive(res) : res; }, set(target, key, value, receiver) { const old = target[key]; const ok = Reflect.set(target, key, value, receiver); if (old !== value) trigger(target, key); return ok; } };
|
reactivity/reactive.js
内容与功能说明:
- 仅对象可代理:原始值直接返回
- 代理缓存:同一对象多次
reactive 返回同一个代理,保证引用稳定
- 职责单一:创建并返回代理,拦截逻辑由
handlers 提供
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { mutableHandlers } from './handlers.js';
const isObject = (v) => v !== null && typeof v === 'object'; const reactiveCache = new WeakMap();
export function reactive(target) { if (!isObject(target)) return target; if (reactiveCache.has(target)) return reactiveCache.get(target); const proxy = new Proxy(target, mutableHandlers); reactiveCache.set(target, proxy); return proxy; }
|
reactivity/index.js(使用示例)
内容与功能说明:
- 作为最简用法展示:
reactive + effect 的数据联动
- 可替换为任意 UI 层(控制台/DOM/框架),验证响应式最小闭环
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { reactive } from './reactive.js'; import { effect } from './effect.js';
const state = reactive({ count: 0 });
effect(() => { console.log('count =>', state.count); });
setInterval(() => { state.count++; }, 1000);
|
小结
以上代码展示了响应式系统的最小闭环:
- 读取时
track 建立「属性 → 副作用」的依赖图;
- 写入时
trigger 找到并执行相关副作用;
reactive 负责把对象读写转接到 track/trigger;
在此基础上,再逐步扩展 ref / computed / watch / scheduler / cleanup 等,后面有时间在增加。