TypeScript fundamentos y configuración

Intermedio
Vuejs
Vuejs
Actualizado: 27/03/2026

Por qué TypeScript con Vue

TypeScript aporta seguridad de tipos al desarrollo con Vue, lo que se traduce en beneficios concretos para cualquier proyecto:

  • Detección temprana de errores: El compilador detecta errores antes de ejecutar el código, como pasar un string donde se espera un number o acceder a una propiedad inexistente.
  • Autocompletado inteligente: Los editores como VS Code ofrecen sugerencias precisas basadas en los tipos definidos, acelerando el desarrollo.
  • Refactorización segura: Renombrar una propiedad o cambiar la firma de una función actualiza automáticamente todas las referencias.
  • Documentación implícita: Los tipos actúan como documentación viva del código, facilitando la comprensión de APIs y estructuras de datos.

Vue está escrito internamente en TypeScript y proporciona tipos para toda su API pública, lo que garantiza una integración nativa y sin fricciones.

Creación del proyecto con TypeScript

La forma recomendada de crear un proyecto Vue con TypeScript es mediante create-vue:

npm create vue@latest

Durante el asistente interactivo, se selecciona la opción Add TypeScript? con Yes. Esto genera un proyecto preconfigurado con:

  • Archivos .vue con <script setup lang="ts">
  • Configuración de TypeScript optimizada para Vue
  • Extensión .ts para archivos de lógica pura

La estructura generada incluye:

mi-proyecto/
├── src/
│   ├── App.vue
│   ├── main.ts
│   ├── components/
│   └── views/
├── tsconfig.json
├── tsconfig.app.json
├── tsconfig.node.json
├── env.d.ts
└── vite.config.ts

Configuración de tsconfig

Un proyecto Vue con TypeScript utiliza una estructura de configuración con múltiples archivos para separar responsabilidades.

tsconfig.json (raíz)

El archivo raíz actúa como referencia a las configuraciones específicas:

{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ]
}

tsconfig.app.json

Configura el código de la aplicación Vue:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "jsx": "preserve",
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": [
    "env.d.ts",
    "src/**/*",
    "src/**/*.vue"
  ]
}

Opciones clave:

  • "strict": true activa todas las comprobaciones estrictas de TypeScript, lo cual es altamente recomendado.
  • "moduleResolution": "bundler" habilita la resolución de módulos optimizada para Vite.
  • "jsx": "preserve" permite usar JSX/TSX si se necesita, delegando la transformación a Vite.
  • La sección paths configura el alias @ para importaciones absolutas desde src/.

tsconfig.node.json

Configura los archivos de herramientas de build como vite.config.ts:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true
  },
  "include": ["vite.config.ts"]
}

env.d.ts

Este archivo declara tipos globales necesarios para que TypeScript reconozca los módulos .vue y las variables de entorno de Vite:

/// <reference types="vite/client" />

Esta declaración permite importar archivos .vue en archivos .ts sin errores de tipo.

vue-tsc para verificación de tipos

vue-tsc es una herramienta que extiende el compilador de TypeScript (tsc) para entender archivos .vue. Es esencial porque tsc estándar no puede procesar Single File Components.

Se ejecuta para verificar tipos en todo el proyecto:

npx vue-tsc --noEmit

El flag --noEmit indica que solo se verifican tipos sin generar archivos de salida. Este comando se incluye normalmente en el script de build:

{
  "scripts": {
    "build": "vue-tsc --noEmit && vite build",
    "type-check": "vue-tsc --noEmit"
  }
}

vue-tsc verifica tanto el código TypeScript dentro de <script setup lang="ts"> como las expresiones del template, detectando errores como props incorrectas o variables no definidas en la plantilla.

Tipos básicos en Vue con TypeScript

En <script setup lang="ts">, Vue infiere automáticamente los tipos en muchos casos. Sin embargo, es importante conocer cómo anotar tipos explícitamente.

Variables reactivas con tipos inferidos

Vue infiere el tipo de ref a partir del valor inicial:

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

const count = ref(0)          // Ref<number>
const name = ref('Vue')       // Ref<string>
const active = ref(true)      // Ref<boolean>
const items = ref([1, 2, 3])  // Ref<number[]>
</script>

Anotación explícita de tipos

Cuando el tipo no se puede inferir del valor inicial o cuando se necesita un tipo más específico, se usa el genérico de ref:

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

const count = ref<number>(0)
const name = ref<string | null>(null)
const items = ref<string[]>([])
</script>

reactive con tipos

reactive infiere el tipo del objeto pasado. Para tipos complejos, se define una interfaz:

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

interface UserState {
  name: string
  age: number
  email: string | null
}

const state = reactive<UserState>({
  name: 'Ana',
  age: 28,
  email: null
})
</script>

computed con tipos

Las propiedades computadas infieren su tipo del valor retornado:

<script setup lang="ts">
import { ref, computed } from 'vue'

const price = ref(100)
const tax = ref(0.21)

// TypeScript infiere computed<number>
const total = computed(() => price.value * (1 + tax.value))

// Anotación explícita cuando es necesario
const formatted = computed<string>(() => `${total.value.toFixed(2)} €`)
</script>

Interfaces y type aliases

TypeScript permite definir tipos personalizados que se reutilizan en toda la aplicación. Esto es fundamental para mantener la consistencia de las estructuras de datos.

Definir interfaces

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

interface Product {
  id: number
  name: string
  price: number
  category: string
  inStock: boolean
}

interface CartItem {
  product: Product
  quantity: number
}

const products = ref<Product[]>([])
const cart = ref<CartItem[]>([])
</script>

Type aliases para uniones y utilidades

<script setup lang="ts">
type Status = 'pending' | 'active' | 'completed' | 'cancelled'
type ID = number | string

interface Task {
  id: ID
  title: string
  status: Status
  assignee?: string
}
</script>

Organizar tipos en archivos separados

Para proyectos grandes, los tipos se definen en archivos .ts independientes:

// src/types/product.ts
export interface Product {
  id: number
  name: string
  price: number
  description: string
  inStock: boolean
}

export interface ProductFilter {
  category?: string
  minPrice?: number
  maxPrice?: number
  onlyInStock?: boolean
}

Se importan en los componentes:

<script setup lang="ts">
import { ref } from 'vue'
import type { Product, ProductFilter } from '@/types/product'

const products = ref<Product[]>([])
const filter = ref<ProductFilter>({})
</script>

La palabra clave type en la importación (import type) indica que solo se importa información de tipos, que se elimina en tiempo de compilación sin generar código JavaScript adicional.

Ejemplo práctico completo

A continuación, un componente completo que combina los conceptos anteriores:

<script setup lang="ts">
import { ref, computed } from 'vue'

interface Todo {
  id: number
  text: string
  completed: boolean
  createdAt: Date
}

type FilterType = 'all' | 'active' | 'completed'

const todos = ref<Todo[]>([])
const newTodoText = ref('')
const filter = ref<FilterType>('all')
let nextId = 1

const filteredTodos = computed<Todo[]>(() => {
  switch (filter.value) {
    case 'active':
      return todos.value.filter(t => !t.completed)
    case 'completed':
      return todos.value.filter(t => t.completed)
    default:
      return todos.value
  }
})

const stats = computed(() => ({
  total: todos.value.length,
  active: todos.value.filter(t => !t.completed).length,
  completed: todos.value.filter(t => t.completed).length
}))

function addTodo(): void {
  if (newTodoText.value.trim() === '') return

  todos.value.push({
    id: nextId++,
    text: newTodoText.value.trim(),
    completed: false,
    createdAt: new Date()
  })
  newTodoText.value = ''
}

function toggleTodo(id: number): void {
  const todo = todos.value.find(t => t.id === id)
  if (todo) {
    todo.completed = !todo.completed
  }
}

function removeTodo(id: number): void {
  todos.value = todos.value.filter(t => t.id !== id)
}
</script>

<template>
  <div class="todo-app">
    <h1>Lista de tareas</h1>

    <form @submit.prevent="addTodo">
      <input v-model="newTodoText" placeholder="Nueva tarea..." />
      <button type="submit">Añadir</button>
    </form>

    <div class="filters">
      <button
        v-for="f in (['all', 'active', 'completed'] as FilterType[])"
        :key="f"
        :class="{ active: filter === f }"
        @click="filter = f"
      >
        {{ f }}
      </button>
    </div>

    <ul>
      <li v-for="todo in filteredTodos" :key="todo.id">
        <input
          type="checkbox"
          :checked="todo.completed"
          @change="toggleTodo(todo.id)"
        />
        <span :class="{ done: todo.completed }">{{ todo.text }}</span>
        <button @click="removeTodo(todo.id)">Eliminar</button>
      </li>
    </ul>

    <p>Total: {{ stats.total }} | Activas: {{ stats.active }} | Completadas: {{ stats.completed }}</p>
  </div>
</template>

Este ejemplo muestra la inferencia automática de tipos, anotaciones explícitas con genéricos, interfaces, type aliases y funciones tipadas, todo dentro de un componente Vue funcional con <script setup lang="ts">.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Vuejs es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Vuejs

Explora más contenido relacionado con Vuejs y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje