ref 的内部工作原理
ref
是一个函数,它接受一个内部值并返回一个响应式且可变的引用对象。这个引用对象有一个 .value
属性,该属性指向内部值。
在上述代码中,ref
函数通过 new RefImpl(value)
创建了一个新的 RefImpl
实例。这个实例包含 getter 和 setter,分别用于追踪依赖和触发更新。使用 ref
可以声明任何数据类型的响应式状态,包括对象和数组。
注意,ref
核心是返回响应式且可变的引用对象,而reactive
核心是返回的是响应式代理,这是两者本质上的核心区别,也就导致了ref
优于reactive
,我们接着看下reactive
源码实现。
reactive 的内部工作原理
reactive
是一个函数,它接受一个对象并返回该对象的响应式代理,也就是 Proxy
。
reactive
的源码相对就简单多了,reactive
通过 new Proxy(target, baseHandlers)
创建了一个代理。这个代理会拦截对目标对象的操作,从而实现响应式。
到这里我们可以看出 ref
和 reactive
在声明数据的响应式状态上,底层原理是不一样的。ref
采用 RefImpl
对象实例,reactive
采用Proxy
代理对象。
ref 更深入的理解
当你使用 new RefImpl(value)
创建一个 RefImpl
实例时,这个实例大致上会包含以下几部分:
- 内部值:实例存储了传递给构造函数的初始值。
- 依赖收集:实例需要跟踪所有依赖于它的效果(effect),例如计算属性或者副作用函数。这通常通过一个依赖列表或者集合来实现。
- 触发更新:当实例的值发生变化时,它需要通知所有依赖于它的效果,以便它们可以重新计算或执行。
RefImpl 类似于发布-订阅模式的设计,以下是一个简化的 RefImpl
类的伪代码实现,展示这个实现过程:
Dep 类负责管理一个依赖列表,并提供依赖收集和通知更新的功能。RefImpl 类包含一个内部值 _value 和一个 Dep 实例。当 value 被访问时,通过 get 方法进行依赖收集;当 value 被赋予新值时,通过 set 方法触发更新。
ref
和 reactive
尽管两者在内部实现上有所不同,但它们都能满足我们对于声明响应式变量的要求,但是 reactive
却存在一定的局限性。
reactive 的局限性
在 Vue3 中,reactive
API 通过 Proxy
实现了一种响应式数据的方法,尽管这种方法在性能上比 Vue2 有所提升,但 Proxy
的局限性也导致了 reactive
的局限性,这些局限性可能会影响开发者的使用体验。
仅对引用数据类型有效
reactive
主要适用于对象,包括数组和一些集合类型(如 Map
和 Set
)。对于基础数据类型(如 string
、number
和 boolean
),reactive
是无效的。这意味着如果你尝试使用 reactive
来处理这些基础数据类型,将会得到一个非响应式的对象。
使用不当会失去响应
- 直接赋值对象:如果直接将一个响应式对象赋值给另一个变量,将会失去响应性。这是因为 reactive 返回的是对象本身,而不仅仅是代理。
- 直接替换响应式对象:同样,直接替换一个响应式对象也会导致失去响应性。
- 直接解构对象:在解构响应式对象时,如果直接解构对象属性,将会得到一个非响应式的变量。
解决这个问题,需要使用 toRefs 函数来将响应式对象转换为 ref
对象。
- 将响应式对象的属性赋值给变量:如果将响应式对象的属性赋值给一个变量,这个变量的值将不会是响应式的。
使用 reactive
声明响应式变量的确存在一些不便之处,尤其是对于喜欢使用解构赋值的开发者而言。这些局限性可能会导致意外的行为,因此在使用 reactive
时需要格外注意。相比之下,ref
API 提供了一种更灵活和统一的方式来处理响应式数据。
为什么推荐使用 ref ?
ref()
它为响应式编程提供了一种统一的解决方案,适用于所有类型的数据,包括基本数据类型和复杂对象。以下是推荐使用 ref 的几个关键原因:
统一性
ref
的核心优势之一是它的统一性。它提供了一种简单、一致的方式来处理所有类型的数据,无论是数字、字符串、对象还是数组。这种统一性极大地简化了开发者的代码,减少了在不同数据类型之间切换时的复杂性。
深层响应性
ref
支持深层响应性,这意味着它可以追踪和更新嵌套对象和数组中的变化。这种特性使得 ref
非常适合处理复杂的数据结构,如对象和数组。
当然,为了减少大型不可变数据的响应式开销,也可以通过使用shallowRef
来放弃深层响应性。
灵活性
ref
提供了高度的灵活性,尤其在处理普通赋值方面。这种灵活性使得 ref 在开发中的使用更加方便,特别是在进行复杂的数据操作时。
总结
ref
在 Vue3 中提供了一种更统一、灵活的响应式解决方案,还能避免了 reactive
的某些局限性。