🔥由浅入深,揭秘vue3中ref、setup、template三者的藕断丝连!
前言
大家好,今天打算写一个vue3
中的ref
函数,当然不单单是封装一个ref
函数这么简单,还会在此基础上引申出vue
在template
模板中读取及设置ref
值时怎么做到省略.value
的写法,要深入理解其中原理又得引申出setup
函数给template
暴露属性时都做了什么,从而理解三者(setup、template、ref) 之间的藕断丝连。我们这里不讲源码,旨在用最通俗的方式让大家理解其中的实现思路。
ref函数揭秘
vue官方文档对ref
是这样介绍的:如果将一个对象赋值给 ref
,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。
对其有了基本了解后,我们创建一个myvue.js
文件,尝试自己封装一个ref
函数:
import { reactive } from 'vue'
export const ref = (value) => {
const wrapper = {
value
}
return reactive(wrapper)
}
封装好后,我们尝试在vue
页面中引入使用:
<template>
<div>
<span>当前值:{{ count.value }}</span>
<button @click="increment">加一</button>
</div>
</template>
<script setup>
// 自己封装的ref
import { ref } from './hooks/myvue'
const count = ref(0)
const increment = () => {
count.value++
}
</script>
当我们点击加一按钮时,页面显示的count.value
值也同样能够正常刷新。But,还没完!细心的同学可能已经发现,在template
模板中使用count
变量时,需要.value
才行,我们想要的效果是像vue提供的ref函数一样,不用.value
也能正常读取值。
template中省略.value揭秘
其实不难,我们只需在前文自己封装的ref
函数里增加一行代码即可!
import { reactive } from 'vue'
export const ref = (value) => {
const wrapper = {
value
}
/**
* 在wrapper上定义一个不可修改且不可迭代的私有属性,
* vue内部会通过这个__v_isRef私有属性判断是否是ref响应式数据
*/
// 新增代码
Object.defineProperty(wrapper, '__v_isRef', {value: true})
return reactive(wrapper)
}
这样我们就能在template
中省略.value
写法了!!!
到这,虽然功能实现了,但我们不能知其然而不知其所以然,下面说说其实现原理。
setup返回值暴露原理揭秘
我们这次不使用<script setup>
语法糖,采用原始的setup函数暴露属性给template
模板使用:
import { ref } from './hooks/myvue'
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
// 将 ref 暴露给模板
return {
count,
increment
}
}
}
</script>
从以上可看出,setup
的返回值是一个对象,而这个对象中的属性之所以能够在template
中直接使用,是归功于webpack/vite
之类的编译打包工具,在.vue
文件编译成js
文件中的render函数
执行时,依然是通过对象.
属性(比如:obj.count
)的形式读取setup返回的对象属性
,只不过这个setup
函数返回的对象经过了Proxy
包装,变成了一个响应式对象,在读取/设置属性时,在get/set
方法中拦截判断是否为ref
响应式对象,如果是则会自动补全.value
并返回/设置相应值。
下面我们通过编写一个proxyRefs
包装函数,更直观的了解其中原理。
const proxyRefs = (obj) => {
return new Proxy(obj, {
get(target, key, receiver) {
// 读取target[key]的值
const value = Reflect.get(target, key, receiver)
// __v_isRef为我们标记为ref的属性
return value.__v_isRef ? value.value : value
},
set(target, key, newValue, receiver) {
const value = target[key]
if(value.__v_isRef) {
// 是ref则设置.value值
value.value = newValue
// 返回设置成功
return true
}
// 设置target[key]的值,并返回是否设置成功
return Reflect.set(target, key, newValue, receiver)
}
})
}
以上就是我们实现的setup
返回对象包装方法,之后我们使用ref
时就不再需要.value
了!主要是通过Proxy
的get
方法拦截读取操作,如果读取的值是ref
则返回其.value
值,如果不是则直接返回原始值;set
拦截也是同理,目的是在设置ref
值时也能省略.value
写法。
下面我们通过一个例子测试一下:
<script>
import { ref, proxyRefs } from './hooks/myvue'
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
const state = {
count,
increment
}
const state2 = proxyRefs(state)
console.log(state2.count) // 0
state2.count++
console.log(state.count.value) // 1
// 将 ref 暴露给模板
return state
}
}
</script>
以上我们把setup
返回对象通过刚刚我们自己写的proxyRefs
方法做一层包装,并赋值给state2
,并且我们通过state2.count
成功读取和设置了state
中的count
值。
总结
首先,封装ref
函数,并创建一个具有value
属性wrapper
对象,并把传入的参数赋值给value
属性,最后再把wrapper
对象通过reactive
响应式API,包装成一个深层的响应式对象。
其次,为了使ref
能够省略.value
的写法,我们给wrapper
包装对象设置了一个私有属性__v_isRef
,由于vue
内部对setup
函数返回的对象做了Proxy
拦截包装,所以在template
模板中读取ref
的值时,可以省略.value
读取及设置ref
的值。
最后,我们手写了一个在读取或设置ref
的值时,可以省略.value
的包装函数proxyRefs
,深入了解setup
函数给模板暴露属性时,是如何包装返回的对象给模板使用。
转载自:https://juejin.cn/post/7341840038964117523