Para dizer que a tecnologia de front-end mais quente deste ano, Vue3 e TS estão definitivamente na lista. Entende-se que muitas empresas já estão utilizando Vue3 + TS + Vite para desenvolver novos projetos. Então não podemos ficar para trás. Hoje vou compartilhar com você como usar o tipo TS em combinação com Composition-Api em componentes Vue3. Se tem amigos que não conhecem ou não conhecem, vamos aprender juntos!
Tipos de anotação para adereços
usar configuração
ao usar
<script setup lang="ts">
const props = defineProps({
foo: {
type: String, required: true },
bar: Number
})
props.foo // string
props.bar // number | undefined
</script>
Isso é chamado de declaração de tempo de execução porque os argumentos passados para defineProps() são usados como opções props em tempo de execução.
A segunda forma é definir o tipo de props através de parâmetros genéricos, que é mais direto:
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
Isso é conhecido como declaração baseada em tipo, e o compilador tenta deduzir opções de tempo de execução equivalentes com base nos parâmetros de tipo tanto quanto possível.
Também poderíamos mover o tipo de props para uma interface separada:
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>
A abordagem baseada em tipo é mais concisa, mas perde a capacidade de definir valores padrão para props. Podemos resolver isso com o açúcar sintático responsivo atualmente experimental:
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
// Padrões de açúcar sintático responsivo serão compilados em opções de tempo de execução equivalentes
const { foo, bar = 100 } = defineProps()
Este comportamento atualmente precisa ser explicitamente aceito na configuração:
// vite.config.js
export default {
plugins: [
vue({
reactivityTransform: true
})
]
}
// vue.config.js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => {
return {
...options,
reactivityTransform: true
}
})
}
}
configurar
se não for usado
import {
defineComponent } from 'vue'
export default defineComponent({
props: {
message: String
},
setup(props) {
props.message // <-- 类型:string
}
})
Tipo de anotação para emissões
configurar
existir
<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])
// 基于类型
const emit = defineEmits<{
(e: 'change', id: number): void
(e: 'update', value: string): void
}>()
</script>
Podemos ver que as declarações baseadas em tipo nos fornecem um controle mais refinado sobre os tipos de eventos que são acionados.
não configurado
se não for usado
import {
defineComponent } from 'vue'
export default defineComponent({
emits: ['change'],
setup(props, {
emit }) {
emit('change') // <-- 类型检查 / 自动补全
}
})
Anote o tipo para ref()
tipo de derivação padrão
Uma ref deduz automaticamente seu tipo do valor com o qual foi inicializada:
import {
ref } from 'vue'
// 推导出的类型:Ref<number>
const year = ref(2020)
// => TS Error: Type 'string' is not assignable to type 'number'.
year.value = '2020'
especificando um tipo por meio de uma interface
Às vezes podemos querer especificar um tipo mais complexo para o valor em ref, usando a interface Ref:
import {
ref } from 'vue'
import type {
Ref } from 'vue'
const year: Ref<string | number> = ref('2020')
year.value = 2020 // 成功!
Especificando tipos por meio de genéricos
Como alternativa, passe um argumento genérico para ref() para substituir o comportamento de dedução padrão:
// 得到的类型:Ref<string | number>
const year = ref<string | number>('2020')
year.value = 2020 // 成功!
Se você especificar um parâmetro genérico sem um valor inicial, acabará com um tipo de união contendo indefinido:
// 推导得到的类型:Ref<number | undefined>
const n = ref<number>()
Tipo de anotação para reativo ()
tipo de derivação padrão
reactivo() também deduz implicitamente os tipos de seus argumentos:
import {
reactive } from 'vue'
// 推导得到的类型:{ title: string }
const book = reactive({
title: 'Vue 3 指引' })
especificando um tipo por meio de uma interface
Para especificar explicitamente o tipo de uma variável reativa, podemos usar interfaces:
import {
reactive } from 'vue'
interface Book {
title: string
year?: number
}
const book: Book = reactive({
title: 'Vue 3 指引' })
Anote o tipo para computer()
tipo de derivação padrão
computered() deduz automaticamente o tipo do valor de retorno de sua função computada:
import {
ref, computed } from 'vue'
const count = ref(0)
// 推导得到的类型:ComputedRef<number>
const double = computed(() => count.value * 2)
// => TS Error: Property 'split' does not exist on type 'number'
const result = double.value.split('')
Especificando tipos por meio de genéricos
Você também pode especificar explicitamente o tipo por meio de um parâmetro genérico:
const double = computed<number>(() => {
// 若返回值不是 number 类型则会报错
})
Anotar tipos para manipuladores de eventos
Ao lidar com eventos DOM nativos, os parâmetros do manipulador de eventos devem ser marcados corretamente com tipos. Vejamos este exemplo:
<script setup lang="ts">
function handleChange(event) {
// `event` 隐式地标注为 `any` 类型
console.log(event.target.value)
}
</script>
<template>
<input type="text" @change="handleChange" />
</template>
Quando não houver anotação de tipo, o parâmetro de evento será marcado implicitamente como qualquer tipo. Isso também gerará um erro TS se "strict": true ou "noImplicitAny": true estiver configurado em tsconfig.json. Portanto, é recomendável digitar explicitamente os parâmetros das funções do manipulador de eventos. Além disso, pode ser necessário converter propriedades explicitamente no evento:
function handleChange(event: Event) {
console.log((event.target as HTMLInputElement).value)
}
Tipo de anotação para fornecer / injetar
provide e inject geralmente rodam em componentes diferentes. Para marcar corretamente o tipo do valor injetado, o Vue fornece uma interface InjectionKey, que é um tipo genérico herdado do Symbol, que pode ser usado para sincronizar o tipo de valor injetado entre o provedor e o consumidor:
import {
provide, inject } from 'vue'
import type {
InjectionKey } from 'vue'
const key = Symbol() as InjectionKey<string>
provide(key, 'foo') // 若提供的是非字符串值会导致错误
const foo = inject(key) // foo 的类型:string | undefined
Recomenda-se colocar o tipo de chave injetada em um arquivo separado para que possa ser importado por vários componentes.
Ao usar uma string para injetar a chave, o tipo do valor injetado é desconhecido, o que precisa ser declarado explicitamente através do parâmetro genérico:
const foo = inject<string>('key') // 类型:string | undefined
Observe que o valor injetado ainda pode ser indefinido, pois não há garantia de que o provedor fornecerá esse valor em tempo de execução. Quando um valor padrão é fornecido, o tipo indefinido pode ser removido:
const foo = inject<string>('foo', 'bar') // 类型:string
Você também pode converter o valor se tiver certeza de que o valor sempre será fornecido:
const foo = inject('foo') as string
#Tipo de anotação para referência de modelo dom
O modelo ref precisa ser criado com um parâmetro genérico explicitamente especificado e um valor inicial nulo:
<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>
Observe que, para segurança de tipo estrita, é necessário usar encadeamento opcional ou guardas de tipo ao acessar el.value. Isso ocorre porque até que o componente seja montado, o valor dessa referência é inicialmente nulo e v-if também será definido como nulo quando o elemento referenciado for desmontado.
#Consulte o tipo de anotação para o modelo de componente
Às vezes, precisamos adicionar uma referência de modelo a um subcomponente para chamar os métodos que ele expõe. Por exemplo, temos um subcomponente MyModal que possui um método para abrir um modal:
<!-- MyModal.vue -->
<script setup lang="ts">
import {
ref } from 'vue'
const isContentShown = ref(false)
const open = () => (isContentShown.value = true)
defineExpose({
open
})
</script>
Para obter o tipo de MyModal, primeiro precisamos obter seu tipo por meio de typeof e, em seguida, usar o tipo de ferramenta InstanceType integrado do TypeScript para obter seu tipo de instância:
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const modal = ref<InstanceType<typeof MyModal> | null>(null)
const openModal = () => {
modal.value?.open()
}
</script>