doc: update vue.md #10 add typescript example
This commit is contained in:
parent
d2b583fc68
commit
cb33da466e
428
docs/vue.md
428
docs/vue.md
@ -677,6 +677,434 @@ const value = inject(ProvideKey)
|
|||||||
|
|
||||||
<!--rehype:className=wrap-text -->
|
<!--rehype:className=wrap-text -->
|
||||||
|
|
||||||
|
Vue 中使用 TypeScript
|
||||||
|
---
|
||||||
|
|
||||||
|
### 为组件的 props 标注类型
|
||||||
|
<!--rehype:wrap-class=row-span-4-->
|
||||||
|
|
||||||
|
当使用 `<script setup>` 时,`defineProps()` 宏函数支持从它的参数中推导类型
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
foo: { type: String, required: true },
|
||||||
|
bar: Number
|
||||||
|
})
|
||||||
|
|
||||||
|
props.foo // string
|
||||||
|
props.bar // number | undefined
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
对同一个文件中的一个接口或对象类型字面量的引用:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Props {/* ... */}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Props 解构默认值
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface Props {
|
||||||
|
msg?: string
|
||||||
|
labels?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
msg: 'hello',
|
||||||
|
labels: () => ['one', 'two']
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
使用目前为实验性的响应性语法糖
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
name: string
|
||||||
|
count?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对 defineProps() 的响应性解构
|
||||||
|
// 默认值会被编译为等价的运行时选项
|
||||||
|
const {
|
||||||
|
name, count = 100
|
||||||
|
} = defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 为组件的 emits 标注类型
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 运行时
|
||||||
|
const emit = defineEmits(['change', 'update'])
|
||||||
|
|
||||||
|
// 基于类型
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'change', id: number): void
|
||||||
|
(e: 'update', value: string): void
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 为 ref() 标注类型
|
||||||
|
|
||||||
|
ref 会根据初始化时的值推导其类型:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
|
const year: Ref<string | number> = ref('2020')
|
||||||
|
|
||||||
|
year.value = 2020 // 成功!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 为 reactive() 标注类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
|
||||||
|
interface Book {
|
||||||
|
title: string
|
||||||
|
year?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const book: Book = reactive({
|
||||||
|
title: 'Vue 3 指引'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 为 computed() 标注类型
|
||||||
|
|
||||||
|
你还可以通过泛型参数显式指定类型:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const double = computed<number>(() => {
|
||||||
|
// 若返回值不是 number 类型则会报错
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 为事件处理函数标注类型
|
||||||
|
<!--rehype:wrap-class=row-span-2-->
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup lang="ts">
|
||||||
|
function handleChange(event) {
|
||||||
|
// `event` 隐式地标注为 `any` 类型
|
||||||
|
console.log(event.target.value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
@change="handleChange" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
显式地为事件处理函数的参数标注类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function handleChange(event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement
|
||||||
|
console.log(target.value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 为 provide / inject 标注类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { provide, inject } from 'vue'
|
||||||
|
import type { InjectionKey } from 'vue'
|
||||||
|
|
||||||
|
const key = Symbol() as InjectionKey<string>
|
||||||
|
// 若提供的是非字符串值会导致错误
|
||||||
|
provide(key, 'foo')
|
||||||
|
// foo 的类型:string | undefined
|
||||||
|
const foo = inject(key)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 为模板引用标注类型
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const el = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
el.value?.focus()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input ref="el" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 为组件模板引用标注类型
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- MyModal.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const isContentShown = ref(false)
|
||||||
|
const open =
|
||||||
|
() => (isContentShown.value = true)
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
使用 TypeScript 内置的 `InstanceType` 工具类型来获取其实例类
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- App.vue -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import MyModal from './MyModal.vue'
|
||||||
|
|
||||||
|
type Modal = InstanceType<typeof MyModal>
|
||||||
|
|
||||||
|
const modal = ref<Modal | null>(null)
|
||||||
|
|
||||||
|
const openModal = () => {
|
||||||
|
modal.value?.open()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选项式 API 为组件的 props 标注类型
|
||||||
|
<!--rehype:wrap-class=row-span-2-->
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
// 启用了类型推导
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
id: [Number, String],
|
||||||
|
msg: { type: String, required: true },
|
||||||
|
metadata: null
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
// 类型:string | undefined
|
||||||
|
this.name
|
||||||
|
// 类型:number|string|undefined
|
||||||
|
this.id
|
||||||
|
// 类型:string
|
||||||
|
this.msg
|
||||||
|
// 类型:any
|
||||||
|
this.metadata
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
使用 PropType 这个工具类型来标记更复杂的 props 类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import type { PropType } from 'vue'
|
||||||
|
|
||||||
|
interface Book {
|
||||||
|
title: string
|
||||||
|
author: string
|
||||||
|
year: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
book: {
|
||||||
|
// 提供相对 `Object` 更确定的类型
|
||||||
|
type: Object as PropType<Book>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// 也可以标记函数
|
||||||
|
callback: Function as PropType<(id: number) => void>
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.book.title // string
|
||||||
|
this.book.year // number
|
||||||
|
|
||||||
|
// TS Error: argument of type 'string' is not
|
||||||
|
// assignable to parameter of type 'number'
|
||||||
|
this.callback?.('123')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选项式 API 为组件的 emits 标注类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
type Payload = { bookName: string }
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
emits: {
|
||||||
|
addBook(payload: Payload) {
|
||||||
|
// 执行运行时校验
|
||||||
|
return payload.bookName.length > 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onSubmit() {
|
||||||
|
this.$emit('addBook', {
|
||||||
|
bookName: 123 // 类型错误
|
||||||
|
})
|
||||||
|
// 类型错误
|
||||||
|
this.$emit('non-declared-event')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选项式 API 为计算属性标记类型
|
||||||
|
<!--rehype:wrap-class=row-span-2-->
|
||||||
|
|
||||||
|
计算属性会自动根据其返回值来推导其类型:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
message: 'Hello!'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
greeting() {
|
||||||
|
return this.message + '!'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.greeting // 类型:string
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
在某些场景中,你可能想要显式地标记出计算属性的类型以确保其实现是正确的:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
message: 'Hello!'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 显式标注返回类型
|
||||||
|
greeting(): string {
|
||||||
|
return this.message + '!'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 标注一个可写的计算属性
|
||||||
|
greetingUppercased: {
|
||||||
|
get(): string {
|
||||||
|
return this.greeting.toUpperCase()
|
||||||
|
},
|
||||||
|
set(newValue: string) {
|
||||||
|
this.message = newValue.toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选项式 API 为事件处理函数标注类型
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
methods: {
|
||||||
|
handleChange(event: Event) {
|
||||||
|
console.log((event.target as HTMLInputElement).value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选项式 API 扩展全局属性
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$http: typeof axios
|
||||||
|
$translate: (key: string) => string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 类型扩展的位置
|
||||||
|
|
||||||
|
我们可以将这些类型扩展放在一个 `.ts` 文件,或是一个影响整个项目的 `*.d.ts` 文件中
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 不工作,将覆盖原始类型。
|
||||||
|
declare module 'vue' {
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$translate: (key: string) => string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 正常工作。
|
||||||
|
export {}
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$translate: (key: string) => string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 选项式 API 扩展自定义选项
|
||||||
|
|
||||||
|
某些插件,比如 vue-router,提供了一些自定义的组件选项,比如 beforeRouteEnter:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
beforeRouteEnter(to, from, next) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
如果没有确切的类型标注,这个钩子函数的参数会隐式地标注为 `any` 类型。我们可以为 `ComponentCustomOptions` 接口扩展自定义的选项来支持:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Route } from 'vue-router'
|
||||||
|
|
||||||
|
declare module 'vue' {
|
||||||
|
interface ComponentCustomOptions {
|
||||||
|
beforeRouteEnter?(
|
||||||
|
to: Route,
|
||||||
|
from: Route,
|
||||||
|
next: () => void
|
||||||
|
): void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
API 参考
|
API 参考
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user