логотип PurpleSchool
логотип PurpleSchool

Типизация и использование TypeScript в Vuejs

Автор

Олег Марков

Введение

Современная разработка фронтенда невозможна без качественной типизации, особенно если вы разрабатываете сложные и масштабируемые приложения. Языки с динамической типизацией, такие как JavaScript, зачастую становятся причиной неожиданных ошибок на этапе исполнения. Именно поэтому многие команды переходят на TypeScript — статически типизированное надмножество JavaScript, позволяющее находить ошибки на этапе компиляции и обеспечивать лучшую читаемость кода.

Vue.js — один из самых популярных фронтенд-фреймворков, который активно поддерживает интеграцию с TypeScript, начиная с версии 2.x, а с выходом Vue 3 работа с типами стала еще проще. В этой статье я подробно расскажу, как организовать типизацию в проектах на Vue.js, приведу примеры кода и пояснения к ним. Мы рассмотрим, как подключить TypeScript, какие есть подходы к написанию компонентов, как типизировать props, emit-события, provide/inject, state и многое другое.

Интеграция TypeScript в Vue.js

Почему стоит использовать TypeScript с Vue.js

TypeScript значительно увеличивает надежность кода. Вот несколько основных причин использовать его с Vue:

  • Раннее обнаружение ошибок — проверка типов еще до запуска приложения.
  • Поддержка редакторов кода — автодополнение, рефакторинг, навигация по коду.
  • Ясность структуры данных — проще поддерживать и расширять проект.
  • Улучшение документации — типы выступают как дополнительная документация для вашей команды.

Начало работы: настройка окружения

Для начала потребуется создать проект Vue с поддержкой TypeScript. Самый простой способ — использовать официальный Vue CLI.

vue create my-vue-ts-project

В процессе создания выберите опцию TypeScript. Если вы уже используете Vite, просто выполните:

npm create vite@latest my-vue-ts-app -- --template vue-ts

После этого у вас уже будет преднастроенная структура файлов с поддержкой TypeScript.

Конфигурация tsconfig.json

Файл tsconfig.json создается автоматически, но иногда его нужно подправить под ваши нужды. Например, если вы используете alias-импорты или специфические настройки компилятора.

Важные параметры:

  • strict: true — рекомендуемый режим для строгой проверки типов.
  • jsx: "preserve" — если вы планируете использовать JSX/TSX внутри Vue.
  • types — явно указать глобальные типы или типы плагинов, если требуется.

Вот пример минимальной конфигурации:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "strict": true,
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "allowJs": true,
    "skipLibCheck": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "exclude": ["node_modules"]
}

Добавление "src//*.vue" в "include" обязательно для корректной работы с .vue файлами.

Синтаксис: использование TypeScript в Vue-компонентах

В Vue 3 появилась сильная поддержка TypeScript из коробки. Рассмотрим основные способы объявления компонентов.

SFC (Single File Component) с

Это рекомендуемый подход: каждый компонент живет в своем .vue файле.

<template>
  <div>{{ count }}</div>
</template>

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

export default defineComponent({
  setup() {
    // Тип переменной подсчитывается автоматически как Ref<number>
    const count = ref(0)
    return { count }
  }
})
</script>

Как видите, при использовании Composition API и TypeScript редактор кода всегда знает, какие значения и методы доступны внутри шаблона. Этот подход минимизирует количество ручного объявления типов, делая код чище и надежнее.

Класс-компоненты (Class Components)

Ранее активно применялись с помощью библиотеки vue-class-component. Сейчас этот подход используется реже, так как Composition API предлагает большую гибкость и лучшие возможности для типизации.

Пример класс-компонента:

import { Vue, Options } from 'vue-class-component'

@Options({
  props: {
    msg: String
  }
})
export default class HelloWorld extends Vue {
  msg!: string

  mounted() {
    // Здесь msg уже типизирована как string
    console.log(this.msg)
  }
}

В современных проектах рекомендуется использовать Composition API.

Composition API с setup и defineComponent

Composition API, появившийся в Vue 3, предлагает компактный синтаксис и лучшую интеграцию с TypeScript.

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

export default defineComponent({
  setup() {
    const message = ref<string>('Привет, мир!')
    return { message }
  }
})
</script>

Тип переменной message строго задан, ошибка типа будет сразу видна в редакторе.

Типизация props, emits, slots

Типизация props

TypeScript позволяет явно указывать типы props с помощью дженериков и отдельных интерфейсов.

Использование с defineProps (SFC с )

Vue 3 поддерживает <script setup> — это самый лаконичный способ сочетания Vue и TypeScript.

<script lang="ts" setup>
interface Props {
  title: string
  count?: number
}

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

Для дефолтных значений используйте defineProps + слияние с default-значениями:

<script lang="ts" setup>
interface Props {
  title: string
  count?: number
}

const props = withDefaults(defineProps<Props>(), {
  count: 0
})
</script>

Теперь props.count будет всегда числом, ошибок типов не возникнет.

TypeScript и классический синтаксис

Если используете объектный синтаксис:

import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    title: {
      type: String,
      required: true
    },
    count: Number
  }
})

В этом случае проверка типов будет только на уровне рантайма.

Типизация emits

События можно типизировать с помощью defineEmits.

<script lang="ts" setup>
const emit = defineEmits<{
  (e: 'increment', value: number): void
  (e: 'close'): void
}>()

function inc() {
  emit('increment', 5) // Ошибка компиляции, если передан не number
}
</script>

Типизация slots

<script lang="ts" setup>
interface Slots {
  default: (props: { text: string }) => any
  header?: (props: { title: string }) => any
}

const slots = defineSlots<Slots>()
</script>

Теперь при обращении к слоту в шаблоне или коде получите автодополнение с типами пропсов.

Типизация реактивных переменных и computed

ref и reactive

ref и reactive автоматически выводят тип, но иногда бывает нужно указать его явно:

import { ref, reactive } from 'vue'

// Явно указываем тип переменной
const loading = ref<boolean>(false)

// Типизация сложных объектов
interface User {
  id: number
  name: string
}

const user = reactive<User>({ id: 0, name: '' })

computed

Для вычисляемых значений указывайте тип возвращаемого значения:

import { computed, ref } from 'vue'

const count = ref(5)

const doubled = computed<number>(() => count.value * 2)

В случае передачи функции в computed редактор сам определит тип, но если функция сложная, всегда полезно прописывать его явно.

Типизация provide и inject

При использовании dependency injection важно типизировать данные, чтобы избежать ошибок.

import { provide, inject } from 'vue'

interface AppConfig {
  apiUrl: string
}

provide('appConfig', { apiUrl: 'https://api.ex.com' })

// В другом компоненте:
const config = inject<AppConfig>('appConfig')
if (config) {
  console.log(config.apiUrl)
}

Если inject не найдет значение, вернет undefined, поэтому всегда делайте проверку.

Указание и организация типов

Вынос типов в отдельные файлы

Хорошая практика — выносить интерфейсы и типы в отдельные файлы, например types.ts, особенно если типы используются в нескольких компонентах.

// src/types.ts
export interface User {
  id: number
  name: string
}
// В компоненте
import type { User } from '@/types'

const user = ref<User>({ id: 1, name: 'Алиса' })

Реализация глобальных типов

Для глобальных свойств (например, $http) можно добавить типизацию путем расширения интерфейсов:

// src/shims-vue.d.ts
import { ComponentCustomProperties } from 'vue'

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $http: typeof axios
  }
}

Теперь во всех компонентах this.$http будет корректно определен.

Тестирование типов

TypeScript помогает находить ошибки, но в случае сложных типов полезно писать отдельные unit-тесты на типы с помощью tsd или аналогичных инструментов.

Пример проверки корректности типа через то, что некорректные значения вызовут ошибки компиляции:

// test-types.ts
import { expectType } from 'tsd'

// Проверяем, что value — строка
expectType<string>(props.value)

Работа с сторонними библиотеками

Большинство популярных библиотек для работы с Vue (например, Vue Router, Vuex, Pinia) тоже поддерживают TypeScript. Важно проверять, что у новых подключаемых библиотек есть глобальные определения типов, либо добавлять их вручную через DefinitelyTyped (@types/*).

Пример с типизацией стора Pinia:

import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0 as number
  }),
  actions: {
    increment() {
      this.count++
    }
  }
})

TypeScript обеспечит строгую типизацию state и методов.

Ошибки и подводные камни типизации

  • Некорректная типизация пропсов — если не указать тип интерфейса у props, TypeScript будет выводить any.
  • Ограничения типизации шаблона (template) — типы проверяются в script-блоке, но не всегда в шаблоне. Следует использовать <script setup>, чтобы получать автодополнение и проверки типов прямо в шаблоне.
  • Типы provide/inject — нет гарантии, что injected значение не undefined. Я рекомендую всегда делать проверки или использовать non-null assertion.
  • Работа с this в Options API — в классическом синтаксисе типизация this сложна, используйте Composition API где возможно.

Заключение

TypeScript серьезно упрощает жизнь разработчикам Vue-приложений благодаря статической проверке типов, улучшенной читаемости и лучшей документации кода. С развитием Vue 3 интеграция TypeScript стала максимально гладкой и мощной: достаточно использовать <script lang="ts"> или <script setup lang="ts">, чтобы получить все плюсы статической типизации.

Обратная совместимость, большой выбор инструментов, поддержка сторонних библиотек и удобный DX делают связку Vue.js + TypeScript лучшим выбором для профессиональной разработки современного фронтенда. Используйте типы для props, events, slots, реактивных данных, глобальных свойств, выносите типы в отдельные файлы и тестируйте их — так вы избежите массы неприятных сюрпризов на продакшн-этапе.

Частозадаваемые технические вопросы по теме статьи и ответы на них

Как правильно типизировать refs на DOM-элементы?

Используйте дженерик параметр <HTMLElement | null> для ref, чтобы указать, что ссылка может быть как на элемент, так и null до монтирования: ts import { ref, onMounted } from 'vue' const inputRef = ref<HTMLInputElement | null>(null) onMounted(() => { if (inputRef.value) { inputRef.value.focus() } })

Как типизировать параметры v-model с TypeScript?

Если используете v-model, обязательно используйте явную типизацию: vue <script lang="ts" setup> const modelValue = defineModel<string>() </script> Если на emits, то добавляйте соответствующий тип для событий: ts const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()

Как типизировать глобальные плагины или расширить глобальные свойства?

Расширяйте ComponentCustomProperties в файле shims-vue.d.ts: ts import { ComponentCustomProperties } from 'vue' declare module '@vue/runtime-core' { interface ComponentCustomProperties { $myService: MyServiceType } }

Как использовать типизацию для динамических компонентов?

Импортируйте компонент с defineAsyncComponent или через defineComponent и используйте тип компонента: ts import { defineAsyncComponent, type DefineComponent } from 'vue' const MyComp = defineAsyncComponent(() => import('./MyComp.vue')) as DefineComponent<MyPropsType>

Почему в шаблоне иногда нет автодополнения props/методов в TypeScript?

Это происходит если используете старый синтаксис <script> без setup или неправильную конфигурацию IDE. Используйте <script setup lang="ts"> для максимального автодополнения, и убедитесь, что у вас установлены последние расширения поддержки для Vue и TypeScript.

Стрелочка влевоИспользование v for и slot в VueИспользование шаблонов в Vue js для построения интерфейсовСтрелочка вправо

Все гайды по Vue

Работа с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueОбзор популярных шаблонов и стартовых проектов на VueКак организовать страницы и маршруты в проекте на VueСоздание серверных приложений на Vue с помощью Nuxt jsРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияНастройка и сборка проектов Vue с использованием современных инструментов
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование метода map в Vue для обработки массивовОбработка пользовательского ввода в Vue.jsОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitИспользование директив и их особенности на Vue с помощью defineСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в Vue
Открыть базу знаний