Skip to content

TypeScript com a API de Composição

Esta página presume que já leste a visão geral no Usando a Vue com a TypeScript.

Tipos para as Propriedades do Componente

Usando <script setup>

Quando estivermos a usar o <script setup>, a macro defineProps() suporta a inferência de tipos de propriedades baseado no seu argumento:

vue
<script setup lang="ts">
const props = defineProps({
  foo: { type: String, required: true },
  bar: Number
})

props.foo // string
props.bar // number | undefined
</script>

Isto é chamado de "declaração de tempo de execução", porque o argumento passado para defineProps() será utilizado como opção props de tempo de execução.

No entanto, é normalmente mais direto definir as propriedades com os tipos puros através de um argumento de tipo genérico:

vue
<script setup lang="ts">
const props = defineProps<{
  foo: string
  bar?: number
}>()
</script>

Isto é chamado "declaração baseada em tipo". O compilador tentará fazer o seu melhor para inferir as opções de tempo de execução equivalente baseado no argumento de tipo. Neste caso, o nosso segundo exemplo compila para as exatas mesmas opções de tempo de execução que as do primeiro exemplo.

Nós podemos usar ou a declaração baseada em tipo OU a declaração de tempo de execução, mas não podemos usar ambas ao mesmo tempo.

Nós podemos também mover os tipos das propriedades para uma interface separada:

vue
<script setup lang="ts">
interface Props {
  foo: string
  bar?: number
}

const props = defineProps<Props>()
</script>

Limitações de Sintaxe

Na versão 3.2 e abaixo, o parâmetro de tipo genérico para defineProps() estavam limitados à um literal de tipo ou uma referência à uma inferência local.

Esta limitação tinha sido resolvida na 3.3, A versão mais recente da Vue suporta a referência dum conjunto importado e limitado de tipos complexos na posição do parâmetro de tipo. No entanto, uma vez que o tipo para conversão de tempo de execução ainda é baseado na Árvore de Sintaxe Abstrata, alguns tipos complexos que exigem a analise do tipo verdadeiro, por exemplo, tipos condicionais, não são suportados. Nós podemos usar os tipos condicionais para o tipo duma única propriedade, mas não para objeto de propriedades inteira.

Valores Padrão das Propriedades

Quando estamos a usar a declaração baseada no tipo, perdemos a habilidade de declarar valores padrão para as propriedades. Isto pode ser resolvido pela macro withDefaults do compilador:

ts
export interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']
})

Isto será compilado para as opções default das propriedades de tempo de execução equivalentes. Além disto, a auxiliar withDefaults fornece verificação de tipo para os valores padrão, e garante que os tipo de props retornado tenha opções opcionais removidas para as propriedades que tiverem valores padrão declarados.

Sem <script setup>

Se não estivermos a usar <script setup>, é necessário usar defineComponent() para ativar a inferência de tipo das propriedades. O tipo do objeto das propriedades passadas para setup() é inferida a partir da opção props.

ts
import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    message: String
  },
  setup(props) {
    props.message // <-- type: string
  }
})

Tipos de Propriedades Complexas

Com a declaração baseada no tipo, uma propriedade pode usar um tipo complexo tal como qualquer outro tipo:

vue
<script setup lang="ts">
interface Book {
  title: string
  author: string
  year: number
}

const props = defineProps<{
  book: Book
}>()
</script>

Para a declaração de tempo de execução, podemos usar o tipo utilitário PropType:

ts
import type { PropType } from 'vue'

const props = defineProps({
  book: Object as PropType<Book>
})

Isto funciona exatamente da mesma maneira se estivéssemos a especificar a opção props diretamente:

ts
import { defineComponent } from 'vue'
import type { PropType } from 'vue'

export default defineComponent({
  props: {
    book: Object as PropType<Book>
  }
})

A opção props é mais comummente usada com a API de Opções, então encontrarás exemplos mais detalhados no guia para TypeScript com a API de Opções. As técnicas mostradas nestes exemplos também aplicam-se as declarações de tempo de execução usando defineProps().

Tipos para as Emissões do Componente

No <script setup>, o tipo da função emit também pode ser atribuído usando ou a declaração de tempo de execução OU a declaração de tipo:

vue
<script setup lang="ts">
// tempo de execução (runtime)
const emit = defineEmits(['change', 'update'])

// baseada em tipo (type-based)
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()
</script>

O argumento de tipo deve ser um literal de tipo com Assinaturas de Chamada. O literal de tipo será usado como tipo da função emit retornada. Conforme podemos ver, a declaração de tipo dá-nos o controlo mais refinado sobre as restrições do tipo dos eventos emitidos.

Quando não se está utilizando <script setup>, a defineComponent() é capaz de inferir os eventos permitidos para a função emit exposta sobre o contexto da configuração:

ts
import { defineComponent } from 'vue'

export default defineComponent({
  emits: ['change'],
  setup(props, { emit }) {
    emit('change') // <-- verificação de tipo / conclusão automática
  }
})

Tipos para a ref()

As referências inferem o tipo a partir do valor inicial:

ts
import { ref } from 'vue'

// tipo inferido: Ref<number>
const year = ref(2020)

// => Erro de TypeScript: tipo 'string' não é atribuível ao tipo 'number'.
year.value = '2020'

Algumas vezes podemos precisar de especificar os tipos complexos para um valor interno da referência. Nós podemos fazer isto usando o tipo Ref:

ts
import { ref } from 'vue'
import type { Ref } from 'vue'

const year: Ref<string | number> = ref('2020')

year.value = 2020 // ok!

Ou, passando um argumento genérico quando estivermos chamando a ref() para sobrepor a inferência padrão:

ts
// tipo resultante: Ref<string | number>
const year = ref<string | number>('2020')

year.value = 2020 // ok!

Se especificarmos um argumento de tipo genérico mas omitirmos o valor inicial, o tipo resultante será um tipo de união que inclui undefined:

ts
// tipo inferido: Ref<number | undefined>
const n = ref<number>()

Tipos para a reactive()

A reactive() também infere implicitamente o tipo a partir do seu argumento:

ts
import { reactive } from 'vue'

// tipo inferido: { title: string }
const book = reactive({ title: 'Vue 3 Guide' })

Para atribuir tipos explicitamente para uma propriedade de reactive, podemos usar as inferências:

ts
import { reactive } from 'vue'

interface Book {
  title: string
  year?: number
}

const book: Book = reactive({ title: 'Vue 3 Guide' })

DICA

Não é recomendado usar o argumento genérico da reactive() porque o tipo retornado, o qual manipula o desembrulhar da referência encaixada, é diferente do tipo do argumento genérico.

Tipos para a computed()

A computed() infere o seu tipo baseado no valor de retorno do recuperador:

ts
import { ref, computed } from 'vue'

const count = ref(0)

// tipo inferido: ComputedRef<number>
const double = computed(() => count.value * 2)

// => Erro de TypeScript: A propriedade 'split' não existe no tipo 'number'
const result = double.value.split('')

Nós podemos também especificar um tipo explícito através de um argumento genérico:

ts
const double = computed<number>(() => {
  // erro de tipo se isto não retornar um número
})

Tipos para os Manipuladores de Evento

Quando estivermos a lidar com eventos de DOM nativo, pode ser útil definir o tipo para o argumento que passamos para o manipulador corretamente. Daremos uma vista de olhos neste exemplo:

vue
<script setup lang="ts">
function handleChange(event) {
  // `event` tem implicitamente o tipo `any`
  console.log(event.target.value)
}
</script>

<template>
  <input type="text" @change="handleChange" />
</template>

Sem a anotação de tipo, o argumento event terá implicitamente um tipo de any. Isto também resultará num erro de TypeScript se "strict": true ou "noImplicitAny": true forem utilizados no tsconfig.json. É portanto recomendado anotar explicitamente o argumento dos manipuladores de evento. Além disto, podemos precisar de usar as asserções de tipo quando acessamos as propriedades de event:

ts
function handleChange(event: Event) {
  console.log((event.target as HTMLInputElement).value)
}

Tipos para provide() / inject()

O fornecimento e a injeção são normalmente realizadas em componentes separados. Para corretamente definir os tipos dos valores injetados, a Vue fornece uma interface InjectionKey, que é um tipo genérico que estende o Symbol. Este pode ser usado para sincronizar o tipo do valor injetado entre o fornecedor e o consumidor:

ts
import { provide, inject } from 'vue'
import type { InjectionKey } from 'vue'

const key = Symbol() as InjectionKey<string>

// fornecer valor que não é uma sequência de caracteres resultará em erro
provide(key, 'foo')

const foo = inject(key) // tipo de foo: string | undefined

É recomendado colocar a chave de injeção num ficheiro separado para que possa ser importada em vários componentes.

Quando estivermos usando as chaves de injeção de sequência de caracteres, o tipo do valor injetado será unknown, e precisará ser explicitamente declarados através dum argumento de tipo genérico:

ts
const foo = inject<string>('foo') // type: string | undefined

Repara que o valor injetado ainda pode ser undefined, porque não existe garantia de que um fornecedor fornecerá este valor no tempo de execução.

Os tipos undefined podem ser removidos fornecendo um valor padrão:

ts
const foo = inject<string>('foo', 'bar') // type: string

Se estivermos certos de que o valor é sempre fornecido, podemos também forçar o lançamento do valor:

ts
const foo = inject('foo') as string

Tipos para as Referências do Modelo de Marcação

As referências de modelo de marcação devem ser criadas com um argumento de tipo genérico explícito e um valor inicial de null:

vue
<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>

Nota que para a segurança de tipo restrito, é necessário usar o encadeamento opcional ou guardas de tipo quando estivermos acessando el.value. Isto é porque o valor da referência inicial é null até o componente ser montado, e também pode ser definido para null se o elemento referenciado for desmontado pelo v-if.

Tipos para Referências do Modelo de Marcação do Componente

Algumas vezes podemos precisar anotar uma referência de modelo de marcação para um componente filho para chamar o seu método público. Por exemplo, temos um componente filho MyModal com um método que abre o modal:

vue
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const isContentShown = ref(false)
const open = () => (isContentShown.value = true)

defineExpose({
  open
})
</script>

Para receber o tipo da instância de MyModal, precisamos primeiro recuperar o seu tipo através de typeof, depois usar o utilitário InstanceType embutido da TypeScript para extrair o tipo da sua instância:

vue
<!-- 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>

Nota que se quiseres utilizar esta técnica nos ficheiros de TypeScript dos Componentes de Ficheiro Único de Vue, precisamos ativar o Modo de Aquisição da Volar.

Nos casos onde o tipo exato do componente não estiver disponível ou não for importante, ComponentPublicInstance pode ser usado. Isto apenas incluirá as propriedades que são partilhadas por todos os componentes, tais como $el:

ts
import { ref } from 'vue'
import type { ComponentPublicInstance } from 'vue'

const child = ref<ComponentPublicInstance | null>(null)
TypeScript com a API de Composição has loaded