在前两篇文章【重学 Vue:深入本质,脱离八股文,彻底搞定面试】:(四)数据拦截的本质和 《【重学 Vue:深入本质,脱离八股文,彻底搞定面试】:(五)响应式数据的本质》 中,我们深入理解了 JS 中的两种数据拦截方式、ref 和 reactive 实现响应式的策略,明确了 什么操作会触发数据拦截。
但我们仍然留下了一个关键问题没有深入探讨:Vue 是如何知道某个函数依赖了哪些响应式数据?又是如何追踪并在数据变化时通知这些函数重新执行的?
这,正是 Vue 响应式系统的“核心秘密”:依赖收集 + 派发更新。
本篇文章将带你深入这个核心机制,并用多个示例拆解,演示多种典型的依赖收集和派发更新场景,带你彻底摆脱”黑箱式使用“,玩转”响应式陷阱“。
🧩 本文结构:
- Vue 响应式的核心机制
- 如何建立依赖
- 是否收集依赖示例
- 是否派发更新示例
一、Vue 响应式的核心机制
Vue 的响应式系统本质上是一种“函数与数据之间的映射关系机制”,数据变了,对应的函数重新运行。为了实现这一点,就需要两个核心机制:
- 依赖收集:在函数运行期间,收集它所依赖的响应式数据。
- 派发更新:当响应式数据变化时,通知依赖它的函数重新执行。
二、 如何建立依赖
依赖关系的核心是:
函数运行时读取了响应式数据 ⇒ 函数依赖该响应式数据。
✅ 判断是否建立依赖的关键:
- 函数必须是 被监控的函数
- 函数 **运行期间 **必须有 同步代码 读取了 响应式数据
- 读取操作被拦截(即访问响应式对象的属性)才会建立依赖
📌 特别说明:
- 赋值不会建立依赖,只有“读取”才会。
- 异步读取不会建立依赖。
📌 被监控的函数:
effect
(底层 API)watchEffect
watch
- 组件的渲染函数
📌响应式数据:
ref
reactive
computed
props
这些数据具有 可追踪变化 的能力,即当数据发生变换会通知一些函数重新执行。
三、是否收集依赖示例
📍 demo1:非响应式变量
var a;
function foo() {
console.log(a); // ❌ 无依赖,a 不是响应式数据
}
📍 demo2:响应式变量
import { ref } from "vue";
var a = ref(1);
function foo() {
console.log(a); // ❌ 无依赖,虽然是 ref,但未访问 .value,读取未被拦截
// console.log(a.value); // ✅ 有依赖,foo 依赖 a.value
}
📍 demo3:嵌套对象属性读取
import { ref } from "vue";
var a = ref({ b: 1 });
const k = a.value;
const n = k.b;
function foo() {
a;
a.value; // ✅ 有依赖
a.value.b; // ✅ 有依赖
k.b; // ✅ 有依赖
n;
}
📍 demo4:各种访问形式都建立依赖
import { ref } from "vue";
var a = ref({ b: 1 });
const k = a.value;
const n = k.b;
function foo() {
function fn2(){
a;
a.value.b; // ✅ 有依赖
n;
}
fn2();
}
📍 demo5:异步读取不建立依赖
import { ref } from "vue";
var a = ref({ b: 1 });
const k = a.value;
async function foo() {
a;
a.value; // ✅ 有依赖
await 1;
k.b; // ❌ 无依赖
}
🚫 await
后的代码不再参与依赖收集。
📌_ 详见:_Vue 响应式为什么异步读取不建立依赖?
四、是否派发更新示例
demo 共用部分:
import { ref, watchEffect } from "vue";
const state = ref({ a: 1 });
const k = state.value;
const n = k.a;
📍 demo1:完整收集依赖
watchEffect(() => {
console.log("运行");
state; // ❌ 无依赖
state.value; // ✅ 依赖 value
state.value.a; // ✅ 依赖 value,value.a
n; // ❌ 无依赖
});
setTimeout(() => {
state.value = { a: 3 }; // 会重新运行
// state.value; // 不会重新运行
// state.value.a = 1; // 不会重新运行,值没变
// k.a = 2; // 会重新运行
// n++; // 不会重新运行
// state = 100; // 不会重新运行,已经不是 ref 对象了
}, 500);
📍 demo2:只依赖 value,不依赖 a 属性
watchEffect(() => {
console.log("运行");
state; // ❌ 无依赖
state.value; // ✅ 依赖 value
n;
});
setTimeout(() => {
state.value.a = 100; // 不会重新运行
}, 500);
📍 demo3:重新赋原始值
watchEffect(() => {
console.log("运行");
state;
state.value.a; // ✅ 依赖 value,value.a
n;
});
setTimeout(() => {
state.value.a = 1; // 不会重新运行,值没有改变
}, 500);
setTimeout(() => {
state.value.a = 2; // 会重新运行
}, 1000);
setTimeout(() => {
k.a = 3; // 会重新运行
}, 1500);
📍 demo4:重新赋引用值
watchEffect(() => {
console.log("运行");
state.value.a; // ✅ 依赖 value,value.a
});
setTimeout(() => {
state.value = { a: 1 }; // 会重新运行
}, 500);
setTimeout(() => {
state.value.a = 2; // 会重新运行
}, 1000);
setTimeout(() => {
k.a = 3; // 不会重新运行,因为前面修改了 state.value,不再是同一个代理对象
}, 1000);
📍 demo5:k.a 建立依赖,value 重新赋引用值
watchEffect(() => {
console.log("运行");
state.value.a; // ✅ 依赖 value,value.a
k.a; // 返回的 proxy 对象的 a 成员
});
setTimeout(() => {
state.value = { a: 1 }; // 会重新运行
}, 500);
setTimeout(() => {
k.a = 3; // 会重新运行
}, 1000);
📍 demo6:拦截value 的 get, a 的 set
watchEffect(() => {
console.log("运行");
state.value.a = 2; // ✅ 仅依赖 value,拦截value 的 get, a 的 set
});
setTimeout(() => {
state.value.a = 100; // 不会重新运行
// state.value = {}; // 会重新运行
}, 500);
🧠 总结
- 所谓响应式,其实就是函数和数据的一组映射,当数据发生变化,会将该数据对应的所有函数全部执行一遍。当然这里的数据和函数都是有要求的。数据是响应式数据,函数是被监控的函数。
- 收集数据和函数的映射关系在 Vue 中被称之为依赖收集,数据变化通知映射的函数重新执行被称之为派发更新。
- 什么时候会产生依赖收集?
- 只有被监控的函数,在它的同步代码运行期间,读取操作被拦截的响应式数据,才会建立依赖关系;
- 建立了依赖关系之后,响应式数据发生变化,对应的函数才会重新执行.
📌 下一篇:【重学 Vue:深入本质,脱离八股文,彻底搞定面试】:(七)响应式和组件渲染
敬请期待~
如果你觉得这篇文章对你有帮助,欢迎关注 + 点赞 + 收藏,我会持续输出「Vue 技术本质」系列内容。