ref 与 响应式变量($ref)
响应式的变量可以像普通变量那样被访问和重新赋值,但这些操作在编译后都会变为带 .value 的 ref。
响应式对象存在解构丢失响应性的问题,而 ref 需要到处使用 .value 则感觉很繁琐.
Vue 响应式语法糖 提供了一个 $ref() 方法是一个编译时的宏命令。
它不是一个真实的、在运行时会调用的方法。而是用作 Vue 编译器的标记,表明最终的 变量需要是一个响应式变量。
ref
ref 得通过ref.value 访问, 有时会我们会忘记使用 .value 而丢失响应性。
代码语言:txt复制const str = ref('测试')
onMounted(() => {
console.log(str.value) //得通过ref.value 访问
})
$ref
通过 使用宏命令 $ 编译后都会变为带 .value 的 ref 。
代码语言:txt复制const str = $ref('测试')
onMounted(() => {
console.log(str)
})
每一个会返回 ref 的响应式 API 都有一个相对应的、以 $ 为前缀的宏函数。包括以下这些 API:
- ref -> $ref
- computed -> $computed
- shallowRef -> $shallowRef
- customRef -> $customRef
- toRef -> $toRef
当启用响应性语法糖时,这些宏函数都是全局可用的、无需手动导入。但如果你想让它更明显,你也可以选择从 vue/macros 中引入它们:
代码语言:txt复制import { $ref } from 'vue/macros'
let count = $ref(0)
用 $() 将现存的 ref 转换为响应式对象
在某些场景中我们可能已经有了会返回 ref 的函数。然而,Vue 编译器并不能够提前知道该函数会返回一个 ref。那么此时可以使用 $() 宏来将现存的 ref 转换为响应式变量。
代码语言:txt复制function myCreateRef() {
return ref(0)
}
let count = $(myCreateRef())
Props 响应式
两个痛点
失去响应性
和 ref 一样,都得通过 .value ,为了保持响应性,通过 props.x 来访问 prop ,得到的变量将不是响应式的、也不会更新。
代码语言:txt复制const props = defineProps({
name: {
type: String,
default: () => '默认值'
},
data:{
type: Object,
required: true
}
})
//访问props
props.name
props.data
定义默认值笨拙
当使用基于类型的 props 的声明时,无法很方便地声明这些 prop 的默认值。为此我们提供了 withDefaults() 这个 API,但使用起来仍然很笨拙。
代码语言:txt复制interface MyProps {
phone: string | number,
name ?: string,
age : number | string
}
const props = withDefaults(defineProps<MyProps>(),{
name:'海军',
phone: '123123123123'
})
props.name
props.phone
解决痛点
当 defineProps 与解构一起使用时,我们可以通过应用编译时转换来解决这些问题,类似于我们之前看到的 $()
代码语言:txt复制interface MyProps {
phone: string | number,
name ?: string,
age : number | string
}
const {
name = '海军',
phone = 1234567,
age = 28,
} = defineProps<MyProps>()
watchEffect(() => {
// 会在 props 变化时打印
console.log(name, phone, age)
//海军 1234567 22
})
函数间传递时的响应性
参数形式传入函数 保持响应式
当 一个函数期望接收一个 ref 对象为参数时,我们可以这样写
$$() 的效果就像是一个转义标识:$$() 中的响应式变量不会追加上 .value。
代码语言:txt复制const contents = $ref('测试')
const changeContent = (ctx: Ref<String>) => {
console.log(ctx.value)
}
watch($$(contents),(val)=> {
changeContent($$(contents))
})
作为函数返回值
如果将响应式变量直接放在返回值表达式中会丢失掉响应性:$$() 可以直接用在要返回的对象上,$$() 调用时任何对响应式变量的引用都会保留为对相应 ref 的引用, 这样保留了响应性。
有点像React Hooks 。
代码语言:txt复制const useWindowData = () => {
const href = ref((window as Window).location.href)
return $$({
href
})
}
const {href} = useWindowData()
开启响应性语法糖
默认是关闭的状态,需要你显式选择启用。此外,以下列出的所有配置都需要 vue@^3.2.25。
Vite
- 需要 @vitejs/plugin-vue@>=2.0.0
- 应用于 SFC 和 js(x)/ts(x) 文件。在执行转换之前,会对文件进行快速的使用检查,因此不使用宏的文件应该不会有性能损失。
- 注意 reactivityTransform 现在是一个插件的顶层选项,而不再是位于 script.refSugar 之中了,因为它不仅仅只对 SFC 起效。
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
Vue-CLi
- 目前仅对 SFC 起效
- 需要 vue-loader@>=17.0.0
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
return {
...options,
reactivityTransform: true
}
})
}
}
Webpack Vue-loader
- 目前仅对 SFC 起效
- 需要 vue-loader@>=17.0.0
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /.vue$/,
loader: 'vue-loader',
options: {
reactivityTransform: true
}
}
]
}
}