vue3 中定义的 ref 类型变量,在 setup 中使用这些变量是需要带上.value 才可以访问的,但是在 template 中却可以直接使用。why?你可能会说 vue 自动进行 ref 解包了,那具体是如何实现的的?

proxyRefs

vue3 中有个方法 proxyRefs,这属于底层方法,在官方文档中并没有阐述,但是 vue 里是可以导出这个方法!

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup lang="ts">
import { onMounted, ref, proxyRefs } from "vue";

const user = {
name: "James",
age: ref(18),
};

const _user = proxyRefs(user);

onMounted(() => {
console.log(_user.name);
console.log(_user.age);
console.log(user.age);
});
</script>

上面代码定义了一个普通对象 user,其中 age 属性的值是 ref 类型。当访问 age 值当时候,需要通过 user.age.value,而使用了 proxyRefs,则可以直接通过 user.age 来访问。

这也就是 template 中为什么不用加.value 的原因,vue3 源码中使用 proxyRefs 方法将 setup 返回的对象进行处理。

实现 proxyRefs

单列测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
it("proxyRefs", () => {
const user = {
name: "jack",
age: ref(10),
}

const proxyUser = proxyRefs(user);

expect(user.age.value).toBe10);
expect(proxyUser.age).toBe(10);

proxyUser.age = 20;
expect(proxyUser age).toBe(20);
expect (user.age.value).toBe20);

proxyUser.age = ref(30);
expect(proxyUser.age).toBe(30);
expect(user.age.value).toBe(30);
});

定义一个 age 属性值为 ref 类型的普通对象 user。 proxyRefs 方法需要满足:

1.proxyUsen 直接访问 age 是可以直接获取到 10。

2.当修改 proxyUser 的 age 值切这个值不是 ref 类型时,proxyUser 和原数据 user 都会被修改。

3.age 值被修改为 ref 类型时,proxyUser 和 user 也会都更新

实现

既然是访问和修改对象内部的属性值,就可以使用 Proxy 来处理 get 和 set。先来实现 get。

1
2
3
4
5
export function proxyRefs(objectWithRefs) {
return new Proxy(objectWithRefs, {
get(target, key) {},
});
}

需要实现的是 proxyUser.age 能直接获取到数据,那原数据 target[key]是 ref 类型,只需要将 ref.value 转成 value。使用 unref 即可实现。

1
2
3
4
5
6
7
export function proxyRefs(objectWithRefs) {
return new Proxy(objectWithRefs, {
get(target, key) {
return unref(Reflect.get(target, key));
},
});
}

实现 set

1
2
3
4
5
6
7
8
export function proxyRefs(objectWithRefs) {
return new Proxy(objectWithRefs, {
get(target, key) {
return unref(Reflect.get(target, key));
},
set(target, key, value) {},
});
}

从单侧中可以看出,我们是测试了两种情况,一种是修改 proxyUsen 的 age 为 ref 类型,一种是修改成不是 ref 类型的,但是结果都是同步更新 proxyUser 和 user。那实现上也需要考虑这两种情况,需要判断原数据值是不是 ref 类型,新赋的值是不是 ref 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function proxyRefs(objectWithRefs) {
return new Proxy(objectWithRefs, {
get(target, key) {
return unref(Reflect.get(target, key));
},
set(target, key, value) {
const oldValue = target[key];
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
},
});
}