# Soporte para TypeScript

Vue CLI (opens new window) provee soporte integrado para herramientas de TypeScript.

# Declaración Oficial en Paquetes NPM

Un sistema de tipos estático ayuda a prevenir muchos errores potenciales en tiempo de ejecución a medida que crecen las aplicaciones, razón por la cual Vue 3 está escrito en TypeScript. Esto significa que no necesita ninguna herramienta adicional para utilizar TypeScript con Vue, debido a que posee soporte de primera clase para su uso.

# Configuración Recomendada

// tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    // esto habilita la inferencia estricta para las propiedades de datos en `this`
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node"
  }
}
1
2
3
4
5
6
7
8
9
10
11

Note que tiene que incluir strict: true (o al menos noImplicitThis: true que es parte de la bandera strict) para apalancar la revisión de tipos de this en métodos de componente, de lo contrario es siempre tratado como tipo any.

Vea documentación de opciones del compilador de TypeScript (opens new window) para más detalles.

# Configuración de Webpack

Si está utilizando una configuración personalizada de Webpack, se requiere configurar ts-loader para analizar bloques <script lang="ts"> en archivos .vue:










 









// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
        },
        exclude: /node_modules/,
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      }
      ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Herramientas de Desarrollo

# Creación de Proyecto

Vue CLI (opens new window) puede generar nuevos proyectos que utilicen TypeScript. Para empezar:

# 1. Instale Vue CLI, si aún no está instalado
npm install --global @vue/cli

# 2. Cree un nuevo proyecto, luego elija la opción "Seleccionar características manualmente (Manually select features)"
vue create my-project-name

# Si ya tiene un proyecto Vue CLI sin TypeScript, por favor añada un plugin apropiado de Vue CLI:
vue add typescript
1
2
3
4
5
6
7
8

Asegúrese de que la parte script del componente tiene TypeScript como lenguaje:

<script lang="ts">
  ...
</script>
1
2
3

O, si quiere combinar TypeScript con una función render de JSX:

<script lang="tsx">
  ...
</script>
1
2
3

# Soporte para Editores de Código

Para desarrollar aplicaciones Vue con TypeScript, recomendamos fuertemente utilizar Visual Studio Code (opens new window), que provee soporte genial integrado para TypeScript. Si está utilizando componentes de un solo archivo (SFCs), obtenga la asombrosa extención Volar (opens new window), que provee inferencia de TypeScript dentro de SFCs y muchas otras características geniales.

WebStorm (opens new window) provee soporte integrado para ambos TypeScript y Vue. Otros IDEs de JetBrains también los soportan, tando de soporte integrado como mediante este plugin gratis (opens new window).

# Definir Componentes Vue

Para dejar que TypeScript infiera apropiadamente los tipos dentro de opciones de componentes Vue, necesita definir componentes con el método global defineComponent:

import { defineComponent } from 'vue'

const Component = defineComponent({
  // inferencia de tipos habilitada
})
1
2
3
4
5

Si está utilizando componentes de un solo archivo, entonces sería escrito típicamente como lo siguiente:

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  // inferencia de tipos habilitada
})
</script>
1
2
3
4
5
6
7

# Utilizando con la API de Opciones

TypeScript debería ser capaz de inferir la mayoría de los tipos sin definirlos explícitamente. Por ejemplo, si tiene un componente con una propiedad numérica count, tendrá un error si intenta llamar un método específico de cadenas de caracteres en ella:

const Component = defineComponent({
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    const result = this.count.split('') // => La propiedad 'split' no existe en el tipo 'number'
  }
})
1
2
3
4
5
6
7
8
9
10

Si tiene un tipo o interfaz compleja, puede realizar un cast utilizando aserción de tipos (opens new window):

interface Book {
  title: string
  author: string
  year: number
}

const Component = defineComponent({
  data() {
    return {
      book: {
        title: 'Guía de Vue 3',
        author: 'Equipo Vue',
        year: 2020
      } as Book
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Aumentar Tipos para globalProperties

Vue 3 proporciona un objeto globalProperties que puede ser utilizado para agregar una propiedad global que puede ser accesado en cualquiera instancia de componente. Por ejemplo, un plugin podría querer inyectar un objeto global o función compartido.

// Definición por el usuario
import axios from 'axios'

const app = Vue.createApp({})
app.config.globalProperties.$http = axios

// Plugin para validar algunos datos
export default {
  install(app, options) {
    app.config.globalProperties.$validate = (data: object, rule: object) => {
      // comprobar si el objeto cumple con ciertas reglas
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Para informar a TypeScript estas nuevas propiedades, podemos utilizar aumentación de módulos (opens new window).

En el ejemplo arriba, podríamos agregar la siguiente declaración de tipo:

import axios from 'axios'

declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $http: typeof axios
    $validate: (data: object, rule: object) => boolean
  }
}
1
2
3
4
5
6
7
8

Podemos poner esta declaración de tipo en el mismo archivo, o en un archivo *.d.ts para todo el proyecto (por ejemplo, en la carpeta src/typings que está cargada automáticamente por TypeScript). Para autores de librerías/plugins, este archivo debe ser especificado en la propiedad types en package.json.

WARNING

Asegúrase de que el archivo de declaración es un módulo de TypeScript para aprovechar la aumentación de módulos, necesitará asegurar que hay al menos un import o export de nivel superior en su archivo, incluso si es solo export {}.

En TypeScript (opens new window), cualquier archivo que contiene un import o export de nivel superior es considerado un 'módulo'. Si la declaración de tipo es hecha afuera de un módulo, sobrescribirá los tipos originales en vez de aumentarlos.

Para más información sobre el tipo ComponentCustomProperties, vea su definición en @vue/runtime-core (opens new window) y pruebas unitarias de TypeScript (opens new window) para aprender más.

# Anotar Tipos de Dato de Retorno

Debido a la naturaleza circular de los archivos de declaración de Vue, TypeScript puede tener dificultades infiriendo los tipos de valores calculados. Por esta razón, puede tener que anotar el tipo de dato de retorno de propiedades calculadas.

import { defineComponent } from 'vue'

const Component = defineComponent({
  data() {
    return {
      message: 'Hello!'
    }
  },
  computed: {
    // necesita una anotación
    greeting(): string {
      return this.message + '!'
    },

    // en un valor calculado con un establecedor, el captador necesita ser anotado
    greetingUppercased: {
      get(): string {
        return this.greeting.toUpperCase()
      },
      set(newValue: string) {
        this.message = newValue.toUpperCase()
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Anotar Propiedades

Vue realiza una validación en tiempo de ejecución en propiedades con un type definido. Para proporcionar estos tipos a TypeScript, necesitamos hacer cast al constructor con PropType:

import { defineComponent, PropType } from 'vue'

interface Book {
  title: string
  author: string
  year: number
}

const Component = defineComponent({
  props: {
    name: String,
    id: [Number, String],
    success: { type: String },
    callback: {
      type: Function as PropType<() => void>
    },
    book: {
      type: Object as PropType<Book>,
      required: true
    },
    metadata: {
      type: null // metadata es tipado como _any_
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

WARNING

Debido a una limitación de diseño (opens new window) en TypeScript cuando se trata de inferencia de tipo de expresiones de función, tiene que ser cuidadoso con valores validator y default para objetos y matrices:

import { defineComponent, PropType } from 'vue'

interface Book {
  title: string
  year?: number
}

const Component = defineComponent({
  props: {
    bookA: {
      type: Object as PropType<Book>,
      // Asegúrese de utilizar funciones de flecha
      default: () => ({
        title: 'Expresión de Función de Flecha'
      }),
      validator: (book: Book) => !!book.title
    },
    bookB: {
      type: Object as PropType<Book>,
      // O proporciona una parámetro explícito de _this_
      default(this: void) {
        return {
          title: 'Expresión de Función'
        }
      },
      validator(this: void, book: Book) {
        return !!book.title
      }
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# Anotar los Emitidos

Podemos anotar un payload para el evento emitido, también, todos los eventos emitidos non declarados lanzarán un error de tipo cuando sean llamados:

const Component = defineComponent({
  emits: {
    addBook(payload: { bookName: string }) {
      // realizar validación en tiempo de ejecución
      return payload.bookName.length > 0
    }
  },
  methods: {
    onSubmit() {
      this.$emit('addBook', {
        bookName: 123 // ¡error de tipo!
      })

      this.$emit('non-declared-event') // ¡error de tipo!
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Utilizar con la API de Composición

En función setup(), no necesita pasar un tipo al parámetro props, debido a que inferirá tipos de la opción props del componente.

import { defineComponent } from 'vue'

const Component = defineComponent({
  props: {
    message: {
      type: String,
      required: true
    }
  },

  setup(props) {
    const result = props.message.split('') // correcto, 'message' es tipado como una cadena de caracteres
    const filtered = props.message.filter(p => p.value) // se lanzará un error:: Property 'filter' does not exist on type 'string'
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Tipar las refs

Las refs inferirán el tipo del valor inicial:

import { defineComponent, ref } from 'vue'

const Component = defineComponent({
  setup() {
    const year = ref(2020)

    const result = year.value.split('') // => La propiedad 'split' no existe en tipo 'number'
  }
})
1
2
3
4
5
6
7
8
9

Algunas veces necesitaríamos especificar tipos complejos para el valor interno de una ref. Podemos hacerlo mediante pasar un argumento genérico cuando se llame la ref para sobreescribir la inferencia por defecto:

const year = ref<string | number>('2020') // el tipo de _year_: Ref<string | number>

year.value = 2020 // ¡ya!
1
2
3

Note

Si el tipo del genérico es incógnito, es recomendado fundir ref a Ref<T>.

# Tipar las Refs de Plantillas

Algunas veces tendría que anotar una ref de plantilla para un componente secundario para llamar su método público. Por ejemplo, tenemos un componente secundario llamado MyModal con un método que abre el modal:

import { defineComponent, ref } from 'vue'

const MyModal = defineComponent({
  setup() {
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)

    return {
      isContentShown,
      open
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

Queremos llamar este método mediante una ref de plantilla desde el componente padre:

import { defineComponent, ref } from 'vue'

const MyModal = defineComponent({
  setup() {
    const isContentShown = ref(false)
    const open = () => (isContentShown.value = true)

    return {
      isContentShown,
      open
    }
  }
})

const app = defineComponent({
  components: {
    MyModal
  },
  template: `
    <button @click="openModal">Abrir desde el padre</button>
    <my-modal ref="modal" />
  `,
  setup() {
    const modal = ref()
    const openModal = () => {
      modal.value.open()
    }

    return { modal, openModal }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Mientras este funcionará, no hay información de tipo sobre MyModal y sus métodos disponibles. Para arreglarlo, debería utilizar InstanceType cuando cree una ref:

setup() {
  const modal = ref<InstanceType<typeof MyModal>>()
  const openModal = () => {
    modal.value?.open()
  }

  return { modal, openModal }
}
1
2
3
4
5
6
7
8

Por favor tenga en cuenta que debería también utilizar encadenamiento opcional (optional chaining) (opens new window) o cualquier otra manera para verificar que modal.value no esté undefined.

# Tipar reactive

Cuando se tipe una propiedad reactive, podemos utilizar interfaces:

import { defineComponent, reactive } from 'vue'

interface Book {
  title: string
  year?: number
}

export default defineComponent({
  name: 'HelloWorld',
  setup() {
    const book = reactive<Book>({ title: 'Guía de Vue 3' })
    // or
    const book: Book = reactive({ title: 'Guía de Vue 3' })
    // or
    const book = reactive({ title: 'Guía de Vue 3' }) as Book
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Tipar computed

Los valores de computed automáticamente inferirán el tipo del valor retornado.

import { defineComponent, ref, computed } from 'vue'

export default defineComponent({
  name: 'CounterButton',
  setup() {
    let count = ref(0)

    // de solo lectura
    const doubleCount = computed(() => count.value * 2)

    const result = doubleCount.value.split('') // => La propiedad 'split' no existe en el tipo 'number'
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

# Tipar manejadores de Eventos

Cuando se trate de eventos DOM nativos, sería útil tipar el argumento que pasemos al manejador correctamente. Echemos un vistazo a este ejemplo:

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

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  setup() {
    // `evt` será tipado como `any`
    const handleChange = evt => {
      console.log(evt.target.value) // TS va a lanzar un error aquí
    }

    return { handleChange }
  }
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Como puede ver, sin anotar el argumento evt correctamente, TypeScript lanzará un error cuando tratemos de acceder el valor del elemento <input>. La solución es fundir el objeto del evento con un tipo correcto:

const handleChange = (evt: Event) => {
  console.log((evt.target as HTMLInputElement).value)
}
1
2
3