像 TypeScript 这样的类型系统可以在编译时通过静态分析检测出很多常见错误。这减少了生产环境中的运行时错误,也让我们在重构大型项目的时候更有信心。通过 IDE 中基于类型的自动补全,TypeScript 还改善了开发体验和效率。
一、项目配置
在使用 npm create vue@lates t创建项目时有是否添加typescript选项。
如果没有在创建项目时添加也可以用下面的命令在已有的项目中添加:
- npm install -D typescript 最新的稳定版本
- tsc --init生成
tsconfig.json
文件
在tsconfig.json配置文件中添加配置避免使用import时报错问题:
{
"compilerOptions": {
/*
这里省略了其他配置
*/
"noImplicitAny": false,
"allowJs": true,
"baseUrl": "src",
"paths": {
"@/*": ["./*"],
"@": ["./"]
}
},
// include也需要配置以下:
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules"]
}
二、为组件的 props 标注类型
为子组件的属性指定类型:
<script setup lang="ts">
const props = defineProps({
foo: {type: String, required: true},
bar: Number
})
</script>
<template>
{{ foo }} - {{ bar }}
</template>
在父组件中使用:
<script lang="ts" setup>
import TypeScript from "@/components/TypeScript.vue";
import {ref} from "vue";
// 这里不是数值类型将会报错
const number = ref(0);
</script>
<template>
<type-script foo="A" :bar="number"></type-script>
</template>
这被称之为“运行时声明”,因为传递给 defineProps()
的参数会作为运行时的 props
选项使用。然而,通过泛型参数来定义 props 的类型通常更直接:
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
这被称之为“基于类型的声明”。编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。在这种场景下,我们第二个例子中编译出的运行时选项和第一个是完全一致的。
基于类型的声明或者运行时声明可以择一使用,但是不能同时使用。我们也可以将 props 的类型移入一个单独的接口中:
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>
这同样适用于 Props
从另一个源文件中导入的情况。该功能要求 TypeScript 作为 Vue 的一个 peer dependency。
<script setup lang="ts">
import type { Props } from './foo'
const props = defineProps<Props>()
</script>
1、Props 解构默认值
当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。这可以通过 withDefaults
编译器宏解决:
<script setup lang="ts">
export interface Props {
msg?: string
labels?: string[]
}
// 为 Props提供默认值
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
</script>
<template>
{{ msg }} - {{ labels }}
</template>
这将被编译为等效的运行时 props default
选项。此外,withDefaults
帮助程序为默认值提供类型检查,并确保返回的 props 类型删除了已声明默认值的属性的可选标志。
2、非 <script setup>
场景下
如果没有使用 <script setup>
,那么为了开启 props 的类型推导,必须使用 defineComponent()
。传入 setup()
的 props 对象类型是从 props
选项中推导而来。
import { defineComponent } from 'vue'
export default defineComponent({
props: {
message: String
},
setup(props) {
props.message // <-- 类型:string
}
})
3、复杂的 prop 类型
通过基于类型的声明,一个 prop 可以像使用其他任何类型一样使用一个复杂类型:
<script setup lang="ts">
interface Book {
title: string
author: string
year: number
}
const props = defineProps<{
book: Book
}>()
</script>
对于运行时声明,我们可以使用 PropType
工具类型:
<script setup lang="ts">
import type { PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
const props = defineProps({
book: Object as PropType<Book>
})
</script>
<template>
{{ book }}
</template>
在父组件中使用:
<script lang="ts" setup>
import TypeScript from "@/components/TypeScript.vue";
import {reactive} from "vue";
// 会进行类型检查
const book = reactive({
title: "A",
author: "B",
year: 2024
});
</script>
<template>
<type-script :book="book"></type-script>
</template>
三、为组件的 emits 标注类型
在 <script setup>
中,emit
函数的类型标注也可以通过运行时声明或是类型声明进行:
<script setup lang="ts">
/*
// 运行时
const emit = defineEmits(['change', 'update'])
// 基于选项
const emit = defineEmits({
change: (id: number) => {
// 返回 `true` 或 `false`
// 表明验证通过或失败
},
update: (value: string) => {
// 返回 `true` 或 `false`
// 表明验证通过或失败
}
})
// 基于类型
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
*/
// 3.3+: 可选的、更简洁的语法
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
</script>
<template>
<button @click="$emit('change',1)">按钮1</button>
<button @click="$emit('update','a')">按钮2</button>
</template>
在父组件中使用:
<script lang="ts" setup>
import TypeScript from "@/components/TypeScript.vue";
// 点击按钮1触发,获取传递的参数
function change(args) {
console.log(args);
}
// 点击按钮2触发,获取传递的参数
function update(args) {
console.log(args);
}
</script>
<template>
<type-script @change="change" @update="update"></type-script>
</template>
类型参数可以是以下的一种:
- 一个可调用的函数类型,但是写作一个包含调用签名的类型字面量。它将被用作返回的
emit
函数的类型。 - 一个类型字面量,其中键是事件名称,值是数组或元组类型,表示事件的附加接受参数。上面的示例使用了具名元组,因此每个参数都可以有一个显式的名称。
我们可以看到,基于类型的声明使我们可以对所触发事件的类型进行更细粒度的控制。若没有使用 <script setup>
,defineComponent()
也可以根据 emits
选项推导暴露在 setup 上下文中的 emit
函数的类型:
import { defineComponent } from 'vue'
export default defineComponent({
emits: ['change'],
setup(props, { emit }) {
emit('change') // <-- 类型检查 / 自动补全
}
})