源码视角,Vue3为什么推荐使用ref而不是reactive
前言
ref
是一个函数,它接受一个内部值并返回一个响应式且可变的引用对象。这个引用对象有一个 .value
属性,该属性指向内部值。
其内部是通过 new RefImpl(value)
创建了一个新的 RefImpl
实例。这个实例包含 getter 和 setter,分别用于追踪依赖和触发更新。使用 ref
可以声明任何数据类型的响应式状态,包括对象和数组。
了解reactive的内部工作原理
reactive
是一个函数,它接受一个对象并返回该对象的响应式代理,也就是 Proxy
。
两者的本质区别
ref
核心是返回响应式且可变的引用对象,reactive
核心是返回的是响应式代理!
谈谈有关ref的RefImpl
实例优点
当你创建一个RefImpl
实例时,它会包含以下几部分:
- 内部值:存储传递给构造函数的初始值。
- 依赖收集:跟踪所有依赖于该实例的效果,比如计算属性或副作用函数。通常通过依赖列表或集合实现。
- 触发更新:当实例的值改变时,会通知所有依赖于它的效果,让它们重新计算或执行。
省流:
RefImpl
类似于发布-订阅模式的设计
谈谈reactive的局限性
1、仅对引用数据类型有效
reactive
主要适用于对象,包括数组和一些集合类型(如 Map
和 Set
)。对于基础数据类型(如 string
、number
和 boolean
),reactive
是无效的。这意味着如果你尝试使用 reactive
来处理这些基础数据类型,将会得到一个非响应式的对象。
2、使用不当会失去响应
- 直接赋值对象:如果直接将一个响应式对象赋值给另一个变量,将会失去响应性。这是因为 reactive 返回的是对象本身,而不仅仅是代理。
import { reactive } from 'vue';
let state = reactive({ count: 0 });
state = { count: 1 }; // 失去响应性
- 直接替换响应式对象:同样,直接替换一个响应式对象也会导致失去响应性。
import { reactive } from 'vue';
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // 失去响应性
- 直接解构对象:在解构响应式对象时,如果直接解构对象属性,将会得到一个非响应式的变量。
const state = reactive({ count: 0 });
let { count } = state;count++; // count 仍然是 0
- 解决这个问题,需要使用
toRefs
函数来将响应式对象转换为ref
对象。
import { toRefs } from 'vue';
const state = reactive({ count: 0 });
let { count } = toRefs(state);
count++; // count 现在是 1
- 将响应式对象的属性赋值给变量:如果将响应式对象的属性赋值给一个变量,这个变量的值将不会是响应式的。
let state = reactive({ count: 0 });
let count = state.countcount++ // count 仍然是 0console.log(state.count)
消息订阅发布模型(RefImpl
)
// Dep类负责收集依赖和通知更新
class Dep {
constructor() {
// 创建一个订阅者集合
this.subscribers = new Set();
}
// depend方法用于收集依赖
depend() {
// 如果存在activeEffect,将其添加到订阅者集合中
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
// notify方法用于通知更新
notify() {
// 遍历订阅者集合,执行每一个订阅者
this.subscribers.forEach(effect => effect());
}
}
let activeEffect = null;
function watchEffect(effect) {
activeEffect = effect;
effect();
activeEffect = null;
}
// 定义一个RefImpl类
class RefImpl {
// 构造函数,接收一个参数value
constructor(value) {
// 将参数value赋值给私有属性_value
this._value = value;
// 实例化一个Dep类,赋值给私有属性dep
this.dep = new Dep();
}
// getter方法,获取私有属性_value的值
get value() {
// 调用dep的depend方法
this.dep.depend();
// 返回私有属性_value的值
return this._value;
}
// setter方法,设置私有属性_value的值
set value(newValue) {
console.log('设置的新属性:', newValue);
// 如果新值不等于私有属性_value的值
if (newValue !== this._value) {
// 将新值赋值给私有属性_value
this._value = newValue;
// 调用dep的notify方法
this.dep.notify();
}
}
}
// 使用示例
let count = new RefImpl(0);
watchEffect(() => {
console.log(`The count is: ${count.value}`); // 订阅变化
});
count.value++; // 修改值,触发通知,重新执行watchEffect中的函数