Vue 3 响应式原理
Vue 3 引入了全新的响应式系统,它使得数据变化能够自动触发相关的更新,从而让我们能够轻松地构建动态应用。本文将深入探讨 Vue 3 的响应式原理,解释响应式系统的核心机制,并逐步实现一个简化版的响应式系统。
什么是响应式
响应式编程是一种编程范式,其中数据的变化会自动触发相关的计算和 UI 更新。在 Vue 3 中,当我们创建一个响应式对象时,对象的属性发生变化会自动触发依赖这些属性的函数(称为副作用函数)的重新执行。
track 和 trigger
在 Vue 3 的响应式系统中,track
和 trigger
是两个核心函数。track
用于追踪依赖关系,而 trigger
则在依赖的数据发生变化时触发相应的副作用函数。
track(target, key)
:记录当前活跃的副作用函数,并将其与目标对象的属性关联。trigger(target, key)
:在目标对象的属性发生变化时,触发所有依赖该属性的副作用函数。
实现 track 和 trigger 并测试效果
首先,我们实现这两个函数并手动调用 track
和 trigger
来测试效果:
const targetMap = new WeakMap();
let activeEffect = null;
function track(target, key) {
if (activeEffect) {
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()));
}
dep.add(activeEffect);
}
}
function trigger(target, key) {
const depsMap = targetMap.get(target);
if (!depsMap) return;
const deps = depsMap.get(key);
if (deps) {
deps.forEach(effect => effect());
}
}
// 测试用例
let product = { price: 5, quantity: 2 };
let total;
function effect() {
total = product.price * product.quantity;
console.log(`Total is: ${total}`);
}
activeEffect = effect;
track(product, 'price');
track(product, 'quantity');
activeEffect = null;
product.price = 10;
trigger(product, 'price'); // 打印 "Total is: 20"
引入 Proxy 并实现数据劫持
为了自动追踪依赖,我们使用 Proxy
来拦截对象的 get
和 set
操作。Proxy
是一种元编程的技术,可以用来定义对象基本操作的自定义行为(如属性访问、赋值等)。
function reactive(target) {
const handler = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
track(target, key);
return result;
},
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;
}
};
return new Proxy(target, handler);
}
// 测试用例
const product = reactive({ price: 5, quantity: 2 });
effect = () => {
total = product.price * product.quantity;
console.log(`Total is: ${total}`);
};
activeEffect = effect;
effect();
activeEffect = null;
product.price = 10; // 打印 "Total is: 20"
引入 activeEffect 自动追踪副作用
为了能够支持多个副作用函数,我们需要一个全局变量 activeEffect
来保存当前的副作用函数,并自动将其添加到正确的依赖集合中。
function effect(fn) {
activeEffect = fn;
fn();
activeEffect = null;
}
// 测试用例
effect(() => {
total = product.price * product.quantity;
console.log(`Total is: ${total}`);
});
product.quantity = 3; // 打印 "Total is: 30"
引入 ref 处理基本数据类型
reactive
函数适用于对象,但无法处理基本数据类型(如 number
和 string
)。为了解决这个问题,我们引入 ref
。
function ref(raw) {
const r = {
get value() {
track(r, 'value');
return raw;
},
set value(newVal) {
if (raw !== newVal) {
raw = newVal;
trigger(r, 'value');
}
}
};
return r;
}
// 测试用例
const count = ref(0);
effect(() => {
console.log(`Count is: ${count.value}`);
});
count.value = 1; // 打印 "Count is: 1"
实现计算属性
计算属性在 Vue 3 中是基于依赖自动更新的属性。我们可以使用 ref
和 effect
来实现计算属性。
function computed(getter) {
const result = ref();
effect(() => (result.value = getter()));
return result;
}
// 测试用例
const product = reactive({ price: 5, quantity: 2 });
const total = computed(() => product.price * product.quantity);
effect(() => {
console.log(`Total is: ${total.value}`);
});
product.price = 10; // 打印 "Total is: 20"
实现 watch 函数
watch
函数用于监听特定响应式数据的变化,并在数据变化时执行指定的回调函数。
function watch(source, callback) {
let getter;
if (typeof source === 'function') {
getter = source;
} else {
getter = () => source.value;
}
let oldValue, newValue;
const effectFn = () => {
newValue = getter();
callback(newValue, oldValue);
oldValue = newValue;
};
effect(effectFn);
}
// 测试用例
watch(
() => product.price,
(newPrice, oldPrice) => {
console.log(`Price changed from ${oldPrice} to ${newPrice}`);
}
);
product.price = 10; // 打印 "Price changed from 5 to 10"
总结
通过本文,我们深入探讨了 Vue 3 响应式系统的核心原理,并逐步实现了一个简化版的响应式系统,涵盖了依赖追踪 (track)、触发更新 (trigger)、使用 Proxy 实现数据劫持、处理基本数据类型的 ref、创建计算属性以及监听数据变化的 watch 函数。这些步骤帮助我们全面理解了 Vue 3 如何实现响应式编程,从而构建高效、动态的应用。
评论区