在本篇文章中,将讨论在Vue
中原始值的响应式方案。原始值指的是Boolean
、Number
、String
、BigInt
、Symbol
、undefined
和null
等类型的值。在JavaScript
中,原始值是按值传递的,而非按引用传递。这意味着如果一个函数接受原始值作为参数,那么形参与实参之间没有引用关系,它们两个是完全独立的值,对形参的修改不会影响实参。另外,JavaScript
中的Proxy
无法提供对原始值的代理,因此想要将原始值变为响应式数据,就必须对其做一层包裹,也就是我们接下来要介绍的ref
。
引入 ref 的概念
由于Proxy
的代理目标必须是非原始值,所以我们没有任何手段拦截原始值的操作。例如:
1 | let str = 'hello' |
对于这个问题,我们唯一能够想到的办法就是使用一个非原始值去“包裹”原始值。例如使用一个对象去包裹原始值:
1 | const wrapper = { |
但这样做会导致两个问题:
- 用户为了创建一个响应式的原始值,不得不顺带创建一个包裹对象;
- 包裹对象由用户来定义,意味着不规范。
为了解决上面这两个问题,我们可以封装一个函数,将包裹对象的创建工作封装到这个函数中:
1 | function ref(val) { |
如上代码所示,我们把创建包裹对象的工作封装到ref
函数内部,然后使用reactive
函数将包裹对象变成响应式数据并返回,这样就解决了上面这两个问题。运行如下测试代码:
1 | // 创建原始值的响应式数据 |
上面这段代码看似没什么问题了,其实并不是这样的。我们接下来要面临的第一个问题就是如何区分 refVal 到底是原始值的包裹对象,还是一个非原始值的响应式数据呢?
1 | const refVal1 = ref(1) |
这段代码中的refVal1
和refVal2
在实现上并没有什么区别。但是我们有必要区分一个数据到底是不是 ref,因为涉及到了后面的自动脱 ref 能力。
想要区分一个数据是不是ref
很简单。怎么做呢?如下面代码所示:
1 | function ref(val) { |
我们使用Object.defineProperty
为包裹对象wrapper
定义了一个不可枚举且不可写的属性__v_isRef
,它的值为true
。代表这个对象是一个ref
,而非一个普通对象。这样我们就可以通过检查__v_isRef
属性来判断一个数据是不是ref
了。