Vue响应式原理

💡Vue响应式原理详解|疑惑小记录

💡Proxy对象

Proxy 对象是 ECMAScript 6 引入的一种用于创建代理对象的特殊对象。它允许你在操作一个对象之前定义自定义的行为,这些行为会在对象的属性被读取、写入或删除时被触发。Proxy 对象提供了一种强大的机制,可以拦截并定义各种操作,例如属性查找、赋值、枚举等,从而实现元编程的功能。

Proxy 对象的基本语法如下:

1
const proxy = new Proxy(target, handler);
  • target:要包装的目标对象。
  • handler:一个包含了代理对象的拦截器(handler)方法的对象。

代理对象的拦截器方法包括了一系列钩子函数,比如 getsethasdeleteProperty 等,通过这些钩子函数,你可以定义在对目标对象进行操作时的行为。例如,你可以定义一个 get 钩子函数来拦截属性的读取操作,或者定义一个 set 钩子函数来拦截属性的赋值操作。

 

以下是一个简单的示例,展示了如何使用 Proxy 对象来拦截对象的读取和赋值操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const target = {
name: 'John',
age: 30
};

const handler = {
get(target, prop) {
console.log(`Getting property "${prop}"`);
return target[prop];
},
set(target, prop, value) {
console.log(`Setting property "${prop}" to "${value}"`);
target[prop] = value;
}
};

const proxy = new Proxy(target, handler);

console.log(proxy.name); // 输出: Getting property "name"; John
proxy.age = 40; // 输出: Setting property "age" to "40"

在这个例子中,我们创建了一个代理对象 proxy,它拦截了 target 对象的读取和赋值操作。当访问 proxy 对象的属性时,get 钩子函数会被触发,并输出一条日志;当对 proxy 对象的属性进行赋值时,set 钩子函数会被触发,并输出一条日志。


💡reactive()ref()

reactive()ref() 是 Vue 3 中用于创建响应式数据的两种不同方式。它们的主要区别在于它们处理数据的方式和用法:

  1. reactive()

    • reactive() 函数用于创建一个具有响应式特性的对象。
    • 当你使用 reactive() 包裹一个对象时,该对象的所有属性都会变成响应式的,即当对象的属性发生变化时,相关的视图会被重新渲染。
    • reactive() 返回的是一个 Proxy 对象,它会拦截对对象的所有操作,从而使得对对象的任何改变都能被 Vue 追踪到,并触发相应的更新。
    1
    2
    3
    4
    5
    6
    7
    import { reactive } from 'vue';

    const state = reactive({
    count: 0
    });

    // state.count 会自动变为响应式的
  2. ref()

    • ref() 函数用于创建一个具有响应式特性的简单数据值(比如数字、字符串等)。
    • 当你使用 ref() 包裹一个简单的数据值时,它会返回一个包含该值的响应式对象。但是,对于对象类型的数据,ref() 不会自动转换为响应式对象。
    • ref() 返回的是一个包含 .value 属性的普通对象,当你需要获取数据时,需要通过 .value 属性来访问
    1
    2
    3
    4
    5
    import { ref } from 'vue';

    const count = ref(0);

    // count.value 会自动变为响应式的

总结一下,主要区别在于:

  • reactive() 用于创建响应式对象,它处理复杂的对象类型数据,并且返回一个 Proxy 对象
  • ref() 用于创建响应式简单数据值,它处理基本的数据类型,返回一个带有 .value 属性的普通对象

 

值得注意的是,reactive() 返回的是一个原始对象的Proxy,它和原始对象是不相等的:

1
2
3
4
5
const raw = {}
const proxy = reactive(raw)

// 代理对象和原始对象不是全等的
console.log(proxy === raw) // false

只有代理对象是响应式的,更改原始对象不会触发更新。因此,使用 Vue 的响应式系统的最佳实践是 仅使用你声明对象的代理版本

reactive() 函数用于创建一个具有响应式特性的对象。当你使用 reactive() 包裹一个对象时,该对象的所有属性都会变成响应式的但是该对象本身不会变成响应式的。因此,reactive() 函数返回的是一个 Proxy 对象,而不是一个响应式对象本身。

为保证访问代理的一致性,对同一个原始对象调用 reactive() 会总是返回同样的代理对象,而对一个已存在的代理对象调用 reactive() 会返回其本身:

1
2
3
4
5
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true

// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true
1
2
3
4
5
6
const proxy = reactive({})

const raw = {}
proxy.nested = raw

console.log(proxy.nested === raw) // false

但是reactive() API 有一些局限性:

  • 有限的值类型
  • 不能替换整个对象
  • 对解构操作不友好

所以建议使用 ref() 作为声明响应式状态的主要 API

💡shallowRef()

  • 浅层次响应
  • 只到x.value,再深层的改变(比如x.value.name)就不会响应了

💡triggerRef()