本文将详细介绍 vue.js 3.3 版本的更新内容,最新版本主要目标是优化开发者的使用体验,包括引入一些新的简化语法和宏,以及在 TypeScript
方面的进一步提升。
当升级到3.3时,建议同时更新以下依赖项:
defineProps
支持使用 import
从外部导入的类型声明,也支持全局变量引入。
// hi.ts
export interface HI {
name: string;
}
// global.d.ts
declare global {
interface HI {
name: string;
}
}
<script setup lang="ts">
import type { HI } from './hi'
// 支持使用导入的类型 + 交集类型(导入类型基础上增加一个字段)
defineProps<HI & { age: number }>()
</script>
之前 defineEmits
的类型参数只支持调用签名语法。
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string, ...rest: any[]): void
}>()
// 或者不定义类型
const emit = defineEmits(['update:modelValue'])
在 vue3.3
中可以简化为以下写法,更加简洁(当然原来的写法照样可以继续使用)。
const emit = defineEmits<{
foo: [id: number]
bar: [name: string, ...rest: any[]]
}>()
使用 的组件现在可以通过
generic
属性接受泛型类型参数,也可以使用多个参数,extend
约束、默认类型和引用导入的类型。
// generic.vue
<script setup lang="ts" generic="T extends number, U extends HI">
import type { HI } from './hi'
defineProps<{ age: T[], names: U[] }>()
</script>
// 在组件中定义并使用
<generic :age="[20, 20]" :names="[{ name: '张三' }, { name: '李四' }]" />
generic
语法同样也支持在 .tsx
结尾的文件中使用:
// generic.tsx
import { defineComponent } from 'vue'
import type { HI } from './hi'
export default defineComponent(<T extends number, U extends HI>(props: { age: T[], names: U[] }) => {
return () => <div>{props.names}</div>
})
// 在组件中定义并使用
<generic :age="[20, 20]" :names="[{ name: '张三' }, { name: '李四' }]" />
由于在 vue3.3
中此功能是实验性的,当使用该新特性时需要进行以下配置(需要重启)。
// vite.config.ts
export default defineConfig({
plugins: [
vue({
script: {
defineModel: true
}
})
]
})
简化前自定义 v-model
双向绑定语法,需要声明 props
,并定义 update:propName
事件
<template>
<input :value="modelValue" @input="onInput" />
</template>
<script setup lang="ts">
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
以下是简化后的写法,宏将自动注册 props 和事件 ,并返回一个 ref:
<template>
<input v-model="modelValue">
</template>
<script setup lang="ts">
const modelValue = defineModel()
// 也可以直接修改,等价于emit('update:modelValue', '新的值')
// modelValue.value = '新的值'
</script>
更多用法
// 默认的model (通过 `v-model`)
const modelValue = defineModel() // Ref
modelValue.value = 10
// 增加类型
const modelValue = defineModel<string>() // Ref
modelValue.value = "hello"
// 带有设置的默认model, 要求非undefined
const modelValue = defineModel<string>({ required: true }) // Ref
// 特定名称的model (通过 `v-model:count` )
const count = defineModel<number>('count')
count.value++
// 具有默认值的特定名称的model
const count = defineModel<number>('count', { default: 0 }) // Ref
// 本地作用域可变的 model, 顾名思义
// 可以不需要父组件传递v-model
const count = defineModel<number>('count', { local: true, default: 0 })
在之前的版本中如果你需要在中定义一些原先
Option Api
的属性比如 inheritAttrs/name
是需要创建一个 script
单独导出这两个属性的。
<script setup lang="ts">
// 一些代码
</script>
<script>
export default {
name: "hahahha",
inheritAttrs: false
}
</script>
现在在 vue3.3
版本中可以用 defineOptions
定义任意选项,但 props
, emits
, expose
, slots
除外。
<script setup lang="ts">
import { getCurrentInstance } from 'vue'
defineOptions({
name: 'hahahha'
inheritAttrs: false
})
const instance = getCurrentInstance()
console.log(instance?.type.name) // hahahha
// 一些代码
</script>
defineSlots
和 slots
属性类似,需要提供一个函数语法。
<template>
<slot msg="hi"></slot>
<slot name="foo" :age="10"></slot>
</template>
<script setup lang="ts">
defineSlots<{
default: (props: { msg: string }) => any
foo: (props: { age: number }) => any
}>()
</script>
函数的入参具体如下:
slot
期望接收的 props
,它的类型将用于模板中的 slot propsdefineSlots
的返回值与 useSlots
返回的 slots
对象相同。在 .tsx
结尾的文件中使用:
import { SlotsType } from 'vue'
import { defineComponent } from 'vue'
export default defineComponent({
slots: Object as SlotsType<{
default: { msg: string }
foo: { age: number }
}>,
setup(props, { slots }) {
return () => (
<>
{slots.default && slots.default({ msg: 'hi' }) }
{slots.foo && slots.foo({ age: 10 })}
</>
)
}
})
上述两种定义的方式在组件中都可以这样使用。
<Sloter>
<template #default="{ msg }">{{ msg }}</template>
<template #foo="{ age }">{{ age }}</template>
</Sloter>
在 vue3.3
中此功能是实验性的,当使用该新特性时需要进行以下配置(需要重启):
// vite.config.ts
export default defineConfig({
plugins: [
vue({
script: {
propsDestructure: true
}
})
]
})
允许非结构化的 prop
保留响应性:
// Child.vue
<script setup lang="ts">
import type { HI } from './hi'
const { name = 'world' } = defineProps<HI>()
watchEffect(() => {
console.log('watch', name) // 每次修改值都会触发打印
})
</script>
// Parent.vue
<template>
<Child :name="name"></Child>
<button @click="changeName">change name</button>
</template>
<script setup lang="ts">
import Child from './Child.vue'
import { ref } from 'vue'
const name = ref('hi')
function changeName () {
name.value+='hi'
}
</script>
在 vue3.3 版本中 toRef
已得到增强,以支持将 values/getters/refs
规范化为 refs
。
import { ref, toRef } from 'vue'
const firstRef = toRef(1) // 等价于ref(1)
const second = ref(2)
const secondRef = toRef(second) // existingRef 按原样返回现有的引用
const getterRef = toRef(() => second) // 创建一个readonly ref,在.value访问时调用getter
使用 getter
调用 toRef
类似于 computed
,但当 getter
只是执行属性访问而没有昂贵的计算时,效率会更高。
在 vue3
日常使用中会封装各种组合函数 composition api
,通常会遇到响应式丢失的问题,举个例子:
// Child.vue
<script setup lang="ts">
const props = defineProps<{ user: { info: { age: number } } }>()
useXXX(props.user.info.age) // 非响应式
function useXXX (age) {
watchEffect(() => {
console.log('watchEffect', age) // 只会触发一次
})
}
</script>
引用 Child
并修改传入的参数:
<template>
<Child :user="user"></Child>
<button @click="changeAge">change age</button>
</template>
<script setup lang="ts">
import Child './Child.vue'
import { ref } from 'vue'
const user = ref({
info: {
age: 1
}
})
function changeAge () {
user.value.info.age += 1
}}
</script>
通过上面的例子可以看到在取值的时候遇到了响应式丢失的问题,那么如何解决上面的问题呢,这时可以使用 toRef
,只需要进行如下修改:
useXXX(toRef(props.user.info, 'age'))
这样写的话还是会有一点问题,如下这样修改值是不会触发响应式:
function changeAge () {
user.value.info = {
age: 10
}
}
如何解决上述问题呢:
// 方法一
useXXX(() => props.user.info.age)
function useXXX(age) {
watchEffect(() => {
console.log('watchEffect', age()) // 但是要通过方法调用
})
}
// 方法二
useXXX(computed(() => props.user.info.age))
function useXXX(age) {
watchEffect(() => {
console.log('watchEffect', age.value) // 但是要使用computed
})
}
// 方法三
useXXX(toRef(() => props.user.info.age))
function useXXX(age) {
watchEffect(() => {
console.log('watchEffect', age.value)
})
}
新的 toValue
实用程序方法提供了相反的功能,将 values/getters/refs
标准化为值。
const unrefValue = unref(() => 3) // () => 3
const value1 = toValue(ref(1)) // 1
const value2 = toValue(2) // 2
const value3 = toValue(() => 3) // 3
个人观点:这些更新的新特性有些还是比较实用的,但是其中又包含太多的黑魔法。。。