1.开场白
5月份,vue团队发布了 vue3.3.
这次小版本的发布主要解决了–
Vue 与 TypeScript 一起使用时的许多长期存在的痛点.
下面我们一起来学习一下vue3.3新特征
2.准备新新特征的环境
根据官方团队的描述,我们需要准备一下工作。
vue升级到 3.3 时,建议同时更新以下依赖项:
Volar / vue-tsc@^1.6.4
vite@^4.3.5
@vitejs/plugin-vue@^4.2.0
vue-loader@^17.1.0 (如果使用 webpack 或 vue-cli)
3.解析导入的类型,并支持有限的复杂类型
//hello.ts文件
// 给HelloWord组件定义类型
export interface HelloPerson {
name: string;
age: number;
likeArr: string[]
}
//HelloWorld.vue文件
<template>
<div class="card">
<p>姓名 {{ name }}</p>
<p>年龄 {{ age }}</p>
<p>爱好 {{ likeArr }}</p>
</div>
</template>
<script setup lang="ts">
//导入我们定义的类型
import { HelloPerson } from './hello'
// 使用定义的类型
defineProps<HelloPerson>()
</script>
//在页面中使用
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<div>
<HelloWorld name="张三" :age="19" :likeArr="['打豆豆','挖呀挖']" />
</div>
</template>
需要注意的点
所以使用组件的时候接口中的参数必须要有,否者会有红色波浪线。
但在实际的场景中,我们有可能不需每一个参数。
为了解决这个上述问题。
我们需要将 interface 中的字段改为可选。使用 ? 还处理。
变为下面的样子就可以了。
// 给HelloWord组件定义类型
// ? 表示该字段可以有也可以没有
export interface HelloPerson {
name?: string;
age?: number;
likeArr?: string[]
}
3.1类型的扩展
有些时候我们还需要进行类型的扩展。
HelloPerson接口中没有我们需要的类型。
我们可以自己进行扩展,现在我们扩展一个其他类型
otherProp 为字符串,同样的这个类型是可有可无的
<script setup lang="ts">
//导入我们定义的类型
import { HelloPerson } from './hello'
// 使用定义的类型 同时还扩展了otherProp类型为字符串。
// 同样的这个类型是可有可无的
defineProps<HelloPerson & { otherProp?: string }>()
</script>
需要注意的点
需要注意的是,复杂类型支持是基于 AST 的,因此不是 100% 全面的。
不支持一些需要实际类型分析的复杂类型,例如条件类型。
我们可以将条件类型用于单个 props 的类型,
但不能对整个 props 对象使用。
3.2通用组件-组件可以接收泛型参数
有些时候我们不知道,传递过来的是什么类型。
这个时候我们就可以使用泛型了。
// Hi组件
<template>
<div class="card">
<p>爱好 {{ likeArr }}</p>
<p>性别 {{ sex }}</p>
</div>
</template>
<script setup lang="ts" generic="T">
defineProps<{
likeArr: T[]
sex: T
}>()
</script>
// 调用Hi组件
<script setup lang="ts">
import Hi from './components/Hi.vue'
let likeArr = ['摸鱼','睡觉']
</script>
<template>
<div>
// 在使用的时候会自动推导类型的
<Hi :likeArr="likeArr" sex="男"/>
</div>
</template>
3.3多个泛型
多个泛型使用逗号隔开,于此同时,也是可以继承的
// Hi组件
<template>
<div class="card">
<p>爱好 {{ likeArr }}</p>
<p>性别 {{ sex }}</p>
<p>年龄 {{ age }}</p>
</div>
</template>
<script setup lang="ts" generic="T,U extends number">
// 让U这个泛型继承数字
defineProps<{
likeArr: T[]
sex: T,
age: U
}>()
</script>
// 调用Hi组件
<script setup lang="ts">
import Hi from './components/Hi.vue'
let likeArr = ['摸鱼','睡觉']
</script>
<template>
<div>
<Hi :likeArr="likeArr" sex="男" :age="10" />
</div>
</template>
<style scoped>
4.defineEmits的优化
// Say.vue
<template>
<div class="card">
<button @click="handlerSay">说</button>
<button @click="handlerWrite">写</button>
</div>
</template>
<script setup lang="ts" generic="T,U extends number">
const emit = defineEmits<{
//这里的id是自定义的,你也可以叫其他
foo: [id: number];
//这里的name, age是自定义的,你也可以叫其他
bar:[name:string, age:number]
}>()
function handlerSay() {
emit('foo',1)
}
function handlerWrite() {
emit('bar', '张三', 19)
}
</script>
// 调用Say.vue
<script setup lang="ts">
import Say from './components/Say.vue'
function bar(a,b) {
console.log(a,b)
}
</script>
<template>
<div>
<Say @bar="bar" />
</div>
</template>
4.1defineEmits以前的写法
<template>
<div class="card">
<button @click="handlerSay">说</button>
<button @click="handlerWrite">写</button>
</div>
</template>
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string, age: number): void
}>()
function handlerSay() {
emit('foo',1)
}
function handlerWrite() {
emit('bar', '张三', 19)
}
5.使用 defineSlots 设置 slots 类型
新的 defineSlots 宏可以声明 slots 及其类型:
// Say.vue
<script setup lang="ts">
defineSlots<{
default?: (props: { msg: string }) => any
item?: (props: { id: number }) => any
}>()
</script>
<template>
<slot msg="xxx" />
<slot name="item" id="xxx"/>
</template>
// 调用Hi.vue
<script setup lang="ts">
import Say from './components/Say.vue'
function bar(a,b) {
console.log(a,b)
}
</script>
<template>
<div>
<Say>
<template #default={msg}></template>
<template #item={id}></template>
</Say>
</div>
</template>
defineSlots()只接受一个类型参数,没有运行时参数。类型参数应该是一个类型字面量
key 是 slot 名称
value 是 slot 函数
函数的第一个参数是 slot 期望接收的 props,它的类型将用于模板中的 slot props。
defineSlots的返回值与 useSlots 返回的 slots 对象相同。
目前存在的限制:
volar / vue-tsc 尚未实现 slots 类型检查。
slot 函数的返回类型目前是忽略的,是任何类型,但我们可能会在将来利用它进行 slot 内容检查。
除了在 <script setup> 中使用 defineSlots 定义 slots 类型,还能在 defineComponent 中的 slots 属性中定义
typescript复制代码import { SlotsType } from 'vue'
defineComponent({
slots: Object as SlotsType<{
default: { foo: string; bar: number }
item: { data: number }
}>,
setup(props, { slots }) {
expectType<undefined | ((scope: { foo: string; bar: number }) => any)>(
slots.default
)
expectType<undefined | ((scope: { data: number }) => any)>(slots.item)
}
})
6.实验性功能
6.1 reactive 解构
该功能可以解构的 props 并保持响应性,并提供了一种更符合人体工程学的方式来声明 props 的默认值:
<script setup>
import { watchEffect } from 'vue'
const { msg = 'hello' } = defineProps(['msg'])
watchEffect(() => {
// 在 watch 和 computed 中使用 msg
// 能够正常收集依赖,就好像使用 props.msg
console.log(`msg is: ${msg}`)
})
</script>
<template>{{ msg }}</template>
此功能是实验性的,需要明确的选择加入。以 Vite 为例:
// vite.config.js
export default {
plugins: [
vue({
propsDestructure: true
})
]
}
6.2defineModel
以前组件想要支持 v-model,需要两个步骤:
声明 props
在打算更新 props 时,emit update:propName 事件
子组件支持 v-model 的写法:
// 子组件
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
console.log(props.modelValue)
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
<template>
<input :value="modelValue" @input="onInput" />
</template>
或者使用computed
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象
// 子组件
const props = defineProps<{
modelValue:string
}>()
const emit = defineEmits(['update:modelValue'])
const value = computed({
get: () => props.modelValue,
set: (val) => {
emit('update:modeValue',val)
}
})
<template>
<input v-model="value" />
</template>
// 父组件
<script setup>
import { ref } from 'vue'
import Comp from './Comp.vue'
const msg = ref('')
</script>
<template>
<Comp v-model="msg">
</template>
Vue 3.3 用新的 defineModel 宏简化了用法。宏将自动注册 props 和事件 ,并返回一个 ref:
<script setup>
const modelValue = defineModel()
console.log(modelValue.value)
</script>
<template>
<input v-model="modelValue" />
</template>
根据接受 defineModel 返回值的变量名,这里是 modelValue,会自动定义 props 名为 modelValue,emit 事件为 update:modelValue
也支持显示传入 props 名称
const count = defineModel<number>('count', { default: 0 })
此功能是实验性的,需要明确的选择加入。
// vite.config.js
export default {
plugins: [
vue({
script: {
defineModel: true
}
})
]
}
7.其他值得注意的功能
7.1 defineOptions
//defineOptions 允许直接在 <script setup> 中声明组件选项,而无需单独的 <script> 块:
<script setup>
defineOptions({name: 'xxx', inheritAttrs: false })
</script>
//终于可以快乐地设置组件 name 了
7.2 toRef、toValue 提供 getter 支持
增强 toRef,支持将 values/getters/ref 标准化为 ref:
// 相当于 ref(1)
toRef(1)
// 创建只读 ref,使用 .value 时执行 getter
toRef(() => props.foo)
// 返回 ref
toRef(existingRef)
//调用 toRef 类似于 computed,但如果 getter 没有昂贵的计算,toRef 会更高效
//toValue 则相反,将 values/getters/ref 标准化为 value:
toValue(1) // --> 1
toValue(ref(1)) // --> 1
toValue(() => 1) // --> 1
8. JSX 导入源支持
目前,Vue 的类型自动注册全局 JSX 类型。这可能会与需要 JSX 类型推断的其他库一起使用时发生冲突,特别是 React
从3.3开始,Vue 支持通过 TypeScript 的 jsxImportSource 选项指定 JSX 命名空间。这允许用户根据其需要,选择全局或每个文件的选择加入。
为了向后兼容,3.3 仍然全局注册 JSX 命名空间。我们计划在 3.4 中删除默认的全局注册。如果您正在使用 TSX 与 Vue,请在升级到 3.3后在 tsconfig.json 中添加显式的 jsxImportSource,以避免在 3.4 中出现问题。