vue3 ref() 响应式源码浅析
官方解释
ref
接收一个内部值并返回一个响应式且可变的ref对象。ref
对象具有指向内部值的单个property.value
如果将对象分配为 ref 值,则通过 reactive 函数使该对象具有高度的响应式。
语法:const xxx = ref(initValue)
ref()
- 返回一个包含响应式数据的引用对象
- JS中操作数据需要加
xxx.value
,模板中读取数据不需要加(内部解析模板时会自动添加.value
),直接<div>{{ xxx }}</div>
- 接收的数据可以是
基本类型
也可以是对象类型(复杂类型)
- 基本类型的数据:响应式依然是靠
es5
的Object.definProperty()
的get()
与set()
实现的 - 对象类型的数据:内部“求助”了
v3
中的reactive()
函数(复杂类型一律用reactive()
) - 为非对象的基本类型创建响应式,其内部使用
getter/setter
的原因是proxy
只能代理对象,无法直接对基本类型进行代理
下面来测试一下:
1 | const p = 1 |
打印显示:
可以看到经过ref()
加工后的数据变成了一个RefImpl(引用实例对象),该对象作为一个响应式的引用维护着它内部的值,这就是ref(referenced)
名称的来源。它只包含一个名为value
的property
(属性),使用时需要加.value
源码
ref()
1 | export function ref(value?: unknown) { |
当使用ref()
创建一个响应式数据时,ref()
会调用createRef()
函数来创建一个RefImpl
对象
createRef()
1 | function createRef(rawValue: unknown, shallow: boolean) { |
createRef()
接收一个 rawValue
, rawValue
如果已经是一个 RefImpl
对象了,也就是其属性 __v_isRef
为 true
,就不再创建新的 RefImpl
对象,而是直接返回 rawValue
。否则,会创建一个 RefImpl
对象。
1 | // isRef 用来判断 rawValue 是不是 RefImpl 对象 |
凡是一个
ref
对象都应当有__v_isRef
属性,且为true
,它用于createRef
函数判断其是否为RefImpl
RefImpl 类
1 | class RefImpl<T> { |
RefImpl
有两个私有属性
_value
是存放当前值的
_rawValue
是存放原始值的
公共的只读变量 __v_isRef
是用来标识该对象是一个 ref
响应式对象的标记
最后 dep
属性不知道干啥的,还没研究
1 | constructor(value: T, public readonly _shallow: boolean) { |
构造函数参数:
value
是 ref()
接收的值
_shallow
在 createRef()
中默认传的 false
,表示 ref()
是递归监听,深度响应式(即无论对象嵌套多深,一旦变更都会触发响应式,并更新UI)
既然是 _shallow = false
,那实例化的时候肯定走 toRaw()
函数和 toReactive()
函数了
toRaw()
函数也是 v3
的一个 api
,它的作用是获取 ref
的原始值
例子:
1 | let state = ref({ name: 'alice', age: 18 }); // RefImpl |
此时原始值存放在 this._rawValue
属性中
如果给 toRaw()
传入普通对象,那它就原样返回这个普通对象,不做任何加工处理
1 | this._value = _shallow ? value : toReactive(value) |
这一行是 ref()
的关键,主要体现在 toReactive()
函数中
1 | export const toReactive = <T extends unknown>(value: T): T => |
toReactive()
函数会判断 ref()
接收的值是不是一个 对象
,如果是对象则通过 reactive()
API 创建一个代理(proxy)对象并返回,就是开头所说的 “ 如果将对象分配为 ref 值,则通过 reactive 函数使该对象具有高度的响应式”,否则直接返回原参数
最后不管是对象还是基本类型数据保存在 this._value
属性中
1 | // RefImpl 类里的 get set |
在对某个 对象属性
进行存取行为的时候会触发 getter/setter
xxx.value
的时候触发 getter
xxx.value = 222
的时候触发 setter
整个ref()运行流程
1 | export function ref(value?: unknown) { |
总结
既接收
基本类型
也接收对象类型
(遇到对象类型
内部自动调reactive()
API)在基本类型的数据的情况下,响应式依然是靠
es5
的Object.definProperty()
的get()
与set()
实现的返回的是一个RefImpl(引用实例对象)
RefImpl
对象中的value
属性存放的是原始值ref()
接收基本类型的情况下,修改响应式数据,原数据不会发生改变,UI会刷新;修改原数据,响应式数据不会变,UI也不会刷新,得出结论:两者无任何关联,属复制关系,因为在JS
中的基本类型,是按值传递的1
2
3
4
5
6const a = 1;
function foo(x) {
x = 2;
}
foo(a); // 在 a 传入的那一刻,a 与 x 就已经毫无关系,属副本
console.log(a); // 仍为 1, 未受 x = 2 赋值所影响ref()
接收对象类型的情况下,修改响应式数据,原数据会发生改变,UI也会刷新;修改原数据的情况下,虽然响应式数据发生了变化,但是UI却没有更新,详情:JS基础之传参(值传递、对象传递)基本数据类型交给
ref()
去做,对象(复杂类型)交给reactive()
去做
v3
的ref()
API 源码的分析总结完毕🎉!
结语
小时候不理解老人晒太阳,一坐就是半天,长大才明白:目之所及,皆是回忆;心之所想,皆是过往;眼之所看,皆是遗憾