TypeScript стал незаменимым инструментом в современной веб-разработке. В этой статье мы подробно разберём, почему TypeScript так важен и как использовать его эффективно.
Что такое TypeScript и почему его стоит использовать?
Основные преимущества
1. Типобезопасность
2. Качество кода
3. Опыт разработчика
Почему это важно
Основы TypeScript
1. Базовые типы и интерфейсы
// Примитивные типы
let username: string = "Erdenay";
let age: number = 25;
let isActive: boolean = true;
// Массивы
let tags: string[] = ["web", "design", "seo"];
let scores: Array<number> = [95, 87, 92];
// Кортежи
let user: [string, number, boolean] = ["Erdenay", 25, true];
// Перечисления (enum)
enum UserRole {
Admin = "ADMIN",
User = "USER",
Guest = "GUEST"
}
// Интерфейсы
interface Product {
id: string;
name: string;
price: number;
inStock: boolean;
category?: string; // Необязательное
readonly createdAt: Date; // Только для чтения
}
// Псевдонимы типов
type ID = string | number;
type Status = "pending" | "approved" | "rejected";
// Объединённые типы (union)
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(2));
}
}2. Продвинутые типы
// Дженерики
function getFirstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstNumber = getFirstElement([1, 2, 3]); // number
const firstString = getFirstElement(["a", "b"]); // string
// Утилитарные типы (Utility Types)
interface User {
id: string;
name: string;
email: string;
age: number;
}
// Partial — делает все свойства необязательными
type PartialUser = Partial<User>;
// Pick — выбирает определённые свойства
type UserPreview = Pick<User, "id" | "name">;
// Omit — исключает определённые свойства
type UserWithoutEmail = Omit<User, "email">;
// Record — создаёт тип объекта с заданными ключами
type UserRoles = Record<string, UserRole>;
// Сопоставленные типы (Mapped Types)
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
// Условные типы (Conditional Types)
type IsString<T> = T extends string ? "yes" : "no";
type Test1 = IsString<string>; // "yes"
type Test2 = IsString<number>; // "no"3. Типы функций
// Аннотации типов функций
function add(a: number, b: number): number {
return a + b;
}
// Стрелочная функция с типами
const multiply = (a: number, b: number): number => a * b;
// Необязательные параметры и параметры по умолчанию
function greet(name: string, greeting: string = "Hello"): string {
return `${greeting}, ${name}!`;
}
// Остаточные параметры (rest)
function sum(...numbers: number[]): number {
return numbers.reduce((acc, num) => acc + num, 0);
}
// Перегрузка функций
function parseValue(value: string): string[];
function parseValue(value: number): number[];
function parseValue(value: string | number): string[] | number[] {
if (typeof value === "string") {
return value.split(",");
}
return [value];
}Лучшие практики TypeScript с React
1. Типы компонентов
import { FC, ReactNode } from 'react';
// Интерфейс пропсов
interface ButtonProps {
children: ReactNode;
onClick: () => void;
variant?: "primary" | "secondary";
disabled?: boolean;
}
// Функциональный компонент с типами
const Button: FC<ButtonProps> = ({
children,
onClick,
variant = "primary",
disabled = false
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`btn btn-${variant}`}
>
{children}
</button>
);
};
// Дженерик-компонент
interface ListProps<T> {
items: T[];
renderItem: (item: T) => ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}2. Хуки с TypeScript
import { useState, useEffect, useCallback, useMemo } from 'react';
// useState с явным типом
const [user, setUser] = useState<User | null>(null);
const [count, setCount] = useState<number>(0);
// useEffect
useEffect(() => {
const fetchUser = async () => {
const response = await fetch('/api/user');
const data: User = await response.json();
setUser(data);
};
fetchUser();
}, []);
// useCallback
const handleClick = useCallback((id: string) => {
console.log(`Clicked: ${id}`);
}, []);
// useMemo
const expensiveValue = useMemo(() => {
return computeExpensiveValue(items);
}, [items]);
// Кастомный хук с типами
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}3. Обработчики событий
import { ChangeEvent, FormEvent, MouseEvent } from 'react';
function Form() {
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Обработка отправки формы
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
console.log("Button clicked");
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>Submit</button>
</form>
);
}TypeScript с Next.js
1. Компоненты страниц
// app/products/[id]/page.tsx
import type { Metadata } from 'next';
interface PageProps {
params: {
id: string;
};
searchParams: {
[key: string]: string | string[] | undefined;
};
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const product = await fetchProduct(params.id);
return {
title: product.name,
description: product.description,
};
}
export default async function ProductPage({ params, searchParams }: PageProps) {
const product = await fetchProduct(params.id);
return <div>{product.name}</div>;
}2. API-маршруты
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface Product {
id: string;
name: string;
price: number;
}
export async function GET(request: NextRequest) {
const products: Product[] = await fetchProducts();
return NextResponse.json(products);
}
export async function POST(request: NextRequest) {
const body: Partial<Product> = await request.json();
const newProduct = await createProduct(body);
return NextResponse.json(newProduct, { status: 201 });
}Корпоративные паттерны
1. Предметно-ориентированное проектирование (Domain-Driven Design)
// types/domain/product.ts
export interface ProductEntity {
id: ProductId;
name: ProductName;
price: Money;
stock: Stock;
}
export type ProductId = string & { readonly __brand: "ProductId" };
export type ProductName = string & { readonly __brand: "ProductName" };
export class Money {
private constructor(
public readonly amount: number,
public readonly currency: string
) {}
static create(amount: number, currency: string): Money {
if (amount < 0) throw new Error("Amount cannot be negative");
return new Money(amount, currency);
}
}2. Паттерн «Репозиторий»
interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(entity: Omit<T, 'id'>): Promise<T>;
update(id: string, entity: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
class ProductRepository implements Repository<Product> {
async findById(id: string): Promise<Product | null> {
// Реализация
}
// ... другие методы
}Производительность и оптимизация
1. Настройка строгого режима (Strict Mode)
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"esModuleInterop": true,
"moduleResolution": "bundler",
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"jsx": "preserve",
"incremental": true
}
}2. Оптимизация сборки
Время компиляции:
Размер бандла:
Командная работа
1. Преимущества для код-ревью
2. Документация
/**
* Вычисляет общую стоимость товаров в корзине
* @param items - Массив товаров корзины
* @param discountCode - Необязательный промокод
* @returns Итоговая стоимость после применения скидок
* @throws {Error} Если промокод недействителен
* @example
* ```ts
* const total = calculateTotal(cartItems, "SUMMER25");
* ```
*/
function calculateTotal(
items: CartItem[],
discountCode?: string
): number {
// Реализация
}Стандарты TypeScript в Enextware
Мы используем TypeScript во всех наших проектах:
Результаты:
Контакт
Для профессиональных веб-приложений, созданных с помощью TypeScript:
Телефон: +90 536 628 0007
Эл. почта: info@enextware.com
