Просунуті патерни TypeScript, які зроблять вас кращим розробником JavaScript

pic

TypeScript справжнім чином змінив розробку на JavaScript, привнісши строгі типи та передові шаблони в екосистему. У цьому посібнику ми розглянемо просунуті патерни TypeScript, які не лише роблять ваш код більш надійним, але й підвищують ваші загальні навички розробки на JavaScript.

Зміст

  1. Програмування на рівні типів
  2. Просунуті шаблони з використанням Generic
  3. Дискриміновані об'єднання
  4. Глибоке занурення в утилітарні типи
  5. Фабричні патерни
  6. Патерни керування станом
  7. Патерни будівника
  8. Приклади з реального світу

1. Програмування на рівні типів

Умовні типи

// Просунутий шаблон умовного типу  
type IsArray = T extends Array ? true : false;  
type IsString = T extends string ? true : false;  

// Практичний приклад: обробник відповіді API  
type ApiResponse = {  
 data: T;  
 status: number;  
 message: string;  
};  

type ExtractData = T extends ApiResponse ? U : never;  

// Приклад використання  
interface UserData {  
 id: number;  
 name: string;  
}  

type UserApiResponse = ApiResponse;  
type ExtractedUserData = ExtractData; // Повертає тип UserData

Типи шаблонів літералів

 // Визначення допустимих HTTP методів  
 type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';  
 type ApiEndpoint = '/users' | '/posts' | '/comments';  

 // Створення типів маршрутів API  
 type ApiRoute = `${HttpMethod} ${ApiEndpoint}`;  

 // Перевірка маршрутів під час компіляції  
 const validRoute: ApiRoute = 'GET /users'; // ✅ Дозволено  
 const invalidRoute: ApiRoute = 'PATCH /users'; // ❌ Помилка типу

2. Просунуті шаблони з використанням Generic

Фабрика з обмеженнями для Generic

interface HasId {  
 id: number;  
}  

interface HasTimestamps {  
 createdAt: Date;  
 updatedAt: Date;  
}  

// Генерична фабрика з обмеженнями  
class EntityFactory {  
 create(data: Omit): T {  
 return {  
 ...data,  
 createdAt: new Date(),  
 updatedAt: new Date()  
 } as T;  
 }  

 update(entity: T, data: Partial>): T {  
 return {  
 ...entity,  
 ...data,  
 updatedAt: new Date()  
 };  
 }  
}  

// Приклад використання  
interface User extends HasId, HasTimestamps {  
 id: number;  
 name: string;  
 email: string;  
}  

const userFactory = new EntityFactory();  
const user = userFactory.create({ id: 1, name: 'John', email: '[email protected]' });

Безпечний для типів "Emitter" подій

type EventMap = {  
 'user:login': { userId: string; timestamp: number };  
 'user:logout': { userId: string; timestamp: number };  
 'error': { message: string; code: number };  
}  

class TypedEventEmitter> {  
 private listeners: Partial> = {};  

 on(event: K, callback: (data: T[K]) => void) {  
 if (!this.listeners[event]) {  
 this.listeners[event] = [];  
 }  
 this.listeners[event]?.push(callback);  
 }  

 emit(event: K, data: T[K]) {  
 this.listeners[event]?.forEach(callback => callback(data));  
 }  
}  

// Приклад використання  
const emitter = new TypedEventEmitter();  

emitter.on('user:login', ({ userId, timestamp }) => {  
 console.log(`Користувач ${userId} увійшов о ${timestamp}`);  
});  

// Безпечний для типів emit  
emitter.emit('user:login', {   
 userId: '123',   
 timestamp: Date.now()   
 });

## Дискриміновані об'єднання

## Патерн керування станом

// Визначення можливих станів з дискримінованим об'єднанням
type AsyncState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: E };

// Генеричний обробник асинхронних даних
class AsyncData {
private state: AsyncState = { status: 'idle' };

setState(newState: AsyncState) {
this.state = newState;
this.render();
}

render() {
switch (this.state.status) {
case 'idle':
console.log('Очікування початку...');
break;
case 'loading':
console.log('Завантаження...');
break;
case 'success':
console.log('Дані:', this.state.data);
break;
case 'error':
console.log('Помилка:', this.state.error);
break;
}
}
}

// Приклад використання
interface UserProfile {
id: string;
name: string;
}

const userProfile = new AsyncData();
```

4. Глибоке занурення в утилітарні типи

Просунуті відображені типи

// Зробити всі властивості необов'язковими і nullable  
type Nullable = { [P in keyof T]: T[P] | null };  

// Зробити певні властивості обов'язковими  
type RequiredProps = T & { [P in K]-?: T[P] };  

// Глибокий partial тип  
type DeepPartial = {  
 [P in keyof T]?: T[P] extends object ? DeepPartial : T[P];  
};  

// Приклад використання  
interface Config {  
 api: {  
 baseUrl: string;  
 timeout: number;  
 headers: {  
 authorization: string;  
 contentType: string;  
 };  
 };  
 cache: {  
 enabled: boolean;  
 ttl: number;  
 };  
}  

type PartialConfig = DeepPartial;  

const config: PartialConfig = {  
 api: {  
 baseUrl: 'https://api.example.com',  
 headers: {  
 authorization: 'Bearer token'  
 }  
 }  
};

5. Фабричні патерни

Абстрактний фабричний патерн

// Абстрактні інтерфейси продуктів  
interface Button {  
 render(): void;  
 onClick(): void;  
}  

interface Input {  
 render(): void;  
 getValue(): string;  
}  

// Абстрактний інтерфейс фабрики  
interface UIFactory {  
 createButton(): Button;  
 createInput(): Input;  
}  

// Конкретні реалізації  
class MaterialButton implements Button {  
 render() { console.log('Відображення кнопки Material'); }  
 onClick() { console.log('Кнопка Material натиснута'); }  
}  

class MaterialInput implements Input {  
 render() { console.log('Відображення поля вводу Material'); }  
 getValue() { return 'Значення поля вводу Material'; }  
}  

class MaterialUIFactory implements UIFactory {  
 createButton(): Button {  
 return new MaterialButton();  
 }  
 createInput(): Input {  
 return new MaterialInput();  
 }  
}  

// Використання з ін'єкцією залежностей  
class Form {  
 constructor(private factory: UIFactory) {}  

 render() {  
 const button = this.factory.createButton();  
 const input = this.factory.createInput();  
 button.render();  
 input.render();  
 }  
}

6. Патерни керування станом

Безпечний для типів патерн Redux

// Типи дій з дискримінованими об'єднаннями  
type Action =  
 | { type: 'ADD_TODO'; payload: { text: string } }  
 | { type: 'TOGGLE_TODO'; payload: { id: number } }  
 | { type: 'DELETE_TODO'; payload: { id: number } };  

interface Todo {  
 id: number;  
 text: string;  
 completed: boolean;  
}  

interface State {  
 todos: Todo[];  
 loading: boolean;  
}  

// Безпечний для типів редуктор  
function reducer(state: State, action: Action): State {  
 switch (action.type) {  
 case 'ADD_TODO':  
 return {  
 ...state,  
 todos: [...state.todos, {  
 id: Date.now(),  
 text: action.payload.text,  
 completed: false  
 }]  
 };  
 case 'TOGGLE_TODO':  
 return {  
 ...state,  
 todos: state.todos.map(todo =>  
 todo.id === action.payload.id  
 ? { ...todo, completed: !todo.completed }  
 : todo  
 )  
 };  
 case 'DELETE_TODO':  
 return {  
 ...state,  
 todos: state.todos.filter(todo => todo.id !== action.payload.id)  
 };  
 }  
}

## Патерни будівників

## Патерн флюїдного будівника

class QueryBuilder {
private query: Partial = {};
private conditions: Array<(item: T) => boolean> = [];

where(key: K, value: T[K]): this {
this.query[key] = value;
return this;
}

whereIn(key: K, values: T[K][]): this {
this.conditions.push((item: T) =>
values.includes(item[key])
);
return this;
}

build(): (item: T) => boolean {
const query = this.query;
const conditions = this.conditions;

return (item: T) => {
const matchesQuery = Object.entries(query).every(
([key, value]) => item[key as keyof T] === value
);

const matchesConditions = conditions.every(
condition => condition(item)
);

return matchesQuery && matchesConditions;
};
}
}

// Приклад використання
interface User {
id: number;
name: string;
age: number;
role: 'admin' | 'user';
}

const query = new QueryBuilder()
.where('role', 'admin')
.whereIn('age', [25, 30, 35])
.build();

const users: User[] = [
{ id: 1, name: 'John', age: 30, role: 'admin' },
{ id: 2, name: 'Jane', age: 25, role: 'user' }
];

const results = users.filter(query);
```

8. Приклади з реального світу

Безпечний для типів API-клієнт

// Визначення кінцевих точок API  
interface ApiEndpoints {  
 '/users': {  
 GET: {  
 response: User[];  
 query: { role?: string };  
 };  
 POST: {  
 body: Omit;  
 response: User;  
 };  
 };  
 '/users/:id': {  
 GET: {  
 params: { id: string };  
 response: User;  
 };  
 PUT: {  
 params: { id: string };  
 body: Partial;  
 response: User;  
 };  
 };  
}  

// Безпечний для типів API-клієнт  
class ApiClient {  
 async get<  
 Path extends keyof ApiEndpoints,  
 Method extends keyof ApiEndpoints[Path],  
 Endpoint extends ApiEndpoints[Path][Method]  
 >(  
 path: Path,  
 config?: {  
 params?: Endpoint extends { params: any } ? Endpoint['params'] : never;  
 query?: Endpoint extends { query: any } ? Endpoint['query'] : never;  
 }  
 ): Promise {  
 // Реалізація  
 return {} as any;  
 }  

 async post<  
 Path extends keyof ApiEndpoints,  
 Method extends keyof ApiEndpoints[Path],  
 Endpoint extends ApiEndpoints[Path][Method]  
 >(  
 path: Path,  
 body: Endpoint extends { body: any } ? Endpoint['body'] : never  
 ): Promise {  
 // Реалізація  
 return {} as any;  
 }  
}  

// Приклад використання  
const api = new ApiClient();  

// Безпечні для типів API-виклики  
const users = await api.get('/users', { query: { role: 'admin' } });  
const user = await api.post('/users', { name: 'John', age: 30, role: 'user' });

Висновок

Ці просунуті патерни TypeScript можуть значно покращити якість вашого коду та досвід розробника. Пам'ятайте про такі рекомендації:

  1. Починайте з простих патернів і поступово вводьте складність
  2. Використовуйте типову систему TypeScript для запобігання помилок на етапі компіляції
  3. Використовуйте обмеження для узагальнених типів для створення багаторазових компонентів
  4. Документуйте складні типові патерни для кращого розуміння вашою командою

Показані патерни — це лише початок, типова система TypeScript неймовірно потужна і може бути використана для створення ще більш складних патернів, що відповідають вашим конкретним потребам.

Бажаєте дізнатися більше такого контенту?

Слідкуйте за мною або напишіть мені на Linkedin.

Перекладено з: Advanced TypeScript Patterns That Will Make You a Better JavaScript Developer

Leave a Reply

Your email address will not be published. Required fields are marked *