Стандарты
TypeScript
Организация типов
app/types/
├── index.ts # Re-exports
├── User.ts # Пользователь
├── Order.ts # Заказы
├── Service.ts # Услуги
├── Company.ts # Компании
├── Api.ts # API типы (PaginatedResponse, etc.)
└── Common.ts # Общие типы
Паттерны типизации
Entity типы
// app/types/User.ts
export interface User {
id: string
email: string
firstName: string
lastName: string
phone: string | null
avatar: string | null
role: UserRole
permissions: string[]
createdAt: string
updatedAt: string
}
export type UserRole = 'admin' | 'operator' | 'user'
// DTO для создания (без id и timestamps)
export type CreateUserDto = Omit<User, 'id' | 'createdAt' | 'updatedAt'>
// DTO для обновления (всё опционально)
export type UpdateUserDto = Partial<CreateUserDto>
Enum vs Union
// ✅ Union types (предпочтительно)
export type OrderStatus = 'pending' | 'processing' | 'completed' | 'cancelled'
// ✅ Enum (если нужны значения)
export enum OrderStatusEnum {
Pending = 'pending',
Processing = 'processing',
Completed = 'completed',
Cancelled = 'cancelled',
}
// С лейблами
export const ORDER_STATUS_LABELS: Record<OrderStatus, string> = {
pending: 'Ожидает',
processing: 'В работе',
completed: 'Завершён',
cancelled: 'Отменён',
}
API Response типы
// app/types/Api.ts
export interface PaginatedResponse<T> {
data: T[]
meta: PaginationMeta
}
export interface PaginationMeta {
total: number
page: number
perPage: number
lastPage: number
}
export interface ApiError {
message: string
code?: string
details?: Record<string, string[]>
}
Компонент props
// В компоненте
interface Props {
user: User
readonly?: boolean
onEdit?: (user: User) => void
}
const props = withDefaults(defineProps<Props>(), {
readonly: false,
})
Правила
1. Используй interface для объектов
// ✅ interface для объектов
interface User {
id: string
name: string
}
// ✅ type для unions, primitives, utilities
type UserRole = 'admin' | 'user'
type UserId = string
type UserWithoutId = Omit<User, 'id'>
2. Избегай any
// ❌ Плохо
function process(data: any) { ... }
// ✅ Хорошо
function process<T>(data: T) { ... }
// ✅ Или используй unknown
function process(data: unknown) {
if (typeof data === 'string') { ... }
}
3. Типизируй API ответы
// ❌ Плохо
const response = await api.get('/users')
// ✅ Хорошо
const response = await api.get<PaginatedResponse<User>>('/users')
4. Используй утилитарные типы
// Partial — все поля опциональны
type UpdateUserDto = Partial<User>
// Pick — только указанные поля
type UserCredentials = Pick<User, 'email' | 'password'>
// Omit — исключить поля
type CreateUserDto = Omit<User, 'id' | 'createdAt'>
// Required — все поля обязательны
type RequiredUser = Required<User>
// Record — словарь
type UserRoleLabels = Record<UserRole, string>
5. Типизируй template refs
import type { FormInstance } from 'ant-design-vue'
const formRef = ref<FormInstance>()
const inputRef = ref<HTMLInputElement>()
6. Не дублируй типы, которые выводятся автоматически
TypeScript умеет выводить типы из контекста. Не указывай типы там, где они очевидны.
// ❌ Избыточно — тип выводится из начального значения
const loading = ref<boolean>(false)
const count = ref<number>(0)
const name = ref<string>('')
const users = ref<User[]>([])
// ✅ Хорошо — TypeScript выведет тип сам
const loading = ref(false) // Ref<boolean>
const count = ref(0) // Ref<number>
const name = ref('') // Ref<string>
const users = ref<User[]>([]) // Нужен — массив пустой, тип не выводится
// ❌ Избыточно — тип возврата очевиден
function getFullName(user: User): string {
return `${user.firstName} ${user.lastName}`
}
// ✅ Хорошо — тип возврата выводится
function getFullName(user: User) {
return `${user.firstName} ${user.lastName}`
}
// ❌ Избыточно — тип return composable
interface UseCounterReturn {
count: Ref<number>
increment: () => void
}
function useCounter(): UseCounterReturn {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
// ✅ Хорошо — тип выводится автоматически
function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
Когда типы НУЖНЫ:
- Пустые массивы:
ref<User[]>([]) - Nullable значения:
ref<User | null>(null) - API ответы:
api.get<User>('/user') - Props компонентов:
defineProps<Props>() - Сложные generic функции
Чек-лист
- Типы в отдельных файлах (types/)
- API ответы типизированы
- Props компонентов типизированы
- Нет any (используй unknown или generic)
- Используются утилитарные типы