TypeScript в Vue
Зачем TypeScript
TypeScript — надстройка над JS с типами. В Vue 3 встроен отлично.
Плюсы:
- Ошибки до запуска
- Автодополнение в IDE
- Рефакторинг безопаснее
- Документация через типы
Настройка
create-vue:
npm create vue@latest my-app
# ✅ Add TypeScript? → Yes
Вручную:
npm install -D typescript vue-tsc @vue/tsconfig
tsconfig.json:
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
},
"include": ["src/**/*.ts", "src/**/*.vue"]
}
script setup + lang="ts"
<script setup lang="ts">
import { ref } from 'vue';
const count = ref<number>(0);
const name = ref<string>('Иван');
const items = ref<string[]>([]);
</script>
lang="ts" — включает TypeScript.
Вывод типов
Не всегда нужно указывать явно:
const count = ref(0); // Ref<number>
const name = ref('Иван'); // Ref<string>
const isActive = ref(false); // Ref<boolean>
Когда явно:
const user = ref<User | null>(null);
const items = ref<Item[]>([]);
Типы для ref
import type { Ref } from 'vue';
const count: Ref<number> = ref(0);
const user: Ref<User | null> = ref(null);
Типы для reactive
import type { User } from '@/types';
const user = reactive<User>({
name: 'Иван',
age: 25,
});
Интерфейсы для данных
src/types/index.ts:
export interface User {
id: number;
name: string;
email: string;
avatar: string | null;
isAdmin: boolean;
}
export interface Product {
id: number;
title: string;
price: number;
inStock: boolean;
tags: string[];
}
export type Status = 'idle' | 'loading' | 'success' | 'error';
Использование:
<script setup lang="ts">
import { ref } from 'vue';
import type { User, Status } from '@/types';
const user = ref<User | null>(null);
const status = ref<Status>('idle');
</script>
Типы для функций
function greet(name: string): string {
return `Привет, ${name}`;
}
function logError(message: string): void {
console.error(message);
}
const sum = (a: number, b: number): number => a + b;
Типы для props
interface Props {
title: string;
count?: number;
items: Item[];
onSave?: () => void;
}
const props = defineProps<Props>();
С дефолтами:
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => [],
});
Типы для emits
type Emits = {
(e: 'save', id: number): void;
(e: 'cancel'): void;
(e: 'update', payload: { id: number; name: string }): void;
};
const emit = defineEmits<Emits>();
emit('save', 5);
emit('cancel');
emit('update', { id: 1, name: 'Иван' });
Сокращённо:
const emit = defineEmits<{
save: [id: number];
cancel: [];
update: [payload: { id: number; name: string }];
}>();
Типы для defineModel
const model = defineModel<string>();
const count = defineModel<number>('count', { required: true });
Типы для provide/inject
import type { InjectionKey, Ref } from 'vue';
export const userKey: InjectionKey<Ref<User | null>> = Symbol('user');
В родителе:
import { provide, ref } from 'vue';
import { userKey } from '@/keys';
import type { User } from '@/types';
const user = ref<User | null>(null);
provide(userKey, user);
В потомке:
import { inject } from 'vue';
import { userKey } from '@/keys';
const user = inject(userKey); // Ref<User | null> | undefined
// С проверкой
const user = inject(userKey)!; // Ref<User | null>
Типы для composables
import { ref, type Ref } from 'vue';
export function useCounter(initial = 0): {
count: Ref<number>;
increment: () => void;
reset: () => void;
} {
const count = ref(initial);
return {
count,
increment: () => count.value++,
reset: () => count.value = initial,
};
}
Дженерик composables
import { ref, type Ref } from 'vue';
export function useLocalStorage<T>(key: string, defaultValue: T): Ref<T> {
const stored = localStorage.getItem(key);
const value = ref(stored ? JSON.parse(stored) : defaultValue) as Ref<T>;
watch(value, (val) => {
localStorage.setItem(key, JSON.stringify(val));
}, { deep: true });
return value;
}
const theme = useLocalStorage<string>('theme', 'light');
const user = useLocalStorage<User | null>('user', null);
Типы для шаблонов
Vue сам проверяет типы в шаблонах при vue-tsc.
npm run type-check
Найдёт:
- Обращение к несуществующим полям
- Неверные типы
- Ошибки в событиях
Проверка в CI
package.json:
{
"scripts": {
"type-check": "vue-tsc --noEmit",
"build": "vue-tsc --noEmit && vite build"
}
}
В GitHub Actions:
- run: npm run type-check
Частые ошибки
1. any:
const data = ref<any>(null); // плохо
Лучше:
const data = ref<User | null>(null);
const data = ref<unknown>(null);
2. Забыл lang="ts" — работает, но без типов.
3. .value без типа — TS покажет ошибку.
4. as без необходимости:
const user = data as User; // обходит проверку
Лучше через type guard:
function isUser(data: unknown): data is User {
return typeof data === 'object' && data !== null && 'email' in data;
}
if (isUser(data)) {
data.email; // TS знает тип
}
Итог
lang="ts"— включает TypeScriptdefineProps<Props>()— типизация propsdefineEmits<Emits>()— emitsInjectionKey<T>— для provide/injectvue-tsc --noEmit— проверка типов- Избегай
any
Дальше — типизация пропсов и событий.