Дізнайтеся, як реалізувати безпеку типів з Fetch API в TypeScript для створення більш надійних і зручних в обслуговуванні веб-застосунків. Вивчіть найкращі практики та практичні приклади.
TypeScript Web API: Досягнення безпеки типів Fetch для надійних застосунків
У сучасній веб-розробці отримання даних з API є фундаментальним завданням. Хоча нативний Fetch API в JavaScript забезпечує зручний спосіб надсилання мережевих запитів, йому не вистачає внутрішньої безпеки типів. Це може призвести до помилок під час виконання та ускладнити підтримку складних застосунків. TypeScript, з його можливостями статичної типізації, пропонує потужне рішення для вирішення цієї проблеми. Цей всебічний посібник досліджує, як реалізувати безпеку типів з Fetch API в TypeScript, створюючи більш надійні та зручні в обслуговуванні веб-застосунки.
Чому безпека типів важлива з Fetch API
Перш ніж заглиблюватися в деталі реалізації, давайте зрозуміємо, чому безпека типів має вирішальне значення при роботі з Fetch API:
- Зменшення помилок під час виконання: Статична типізація TypeScript допомагає виявляти помилки під час розробки, запобігаючи несподіваним проблемам під час виконання, спричиненим неправильними типами даних.
- Покращена зручність обслуговування коду: Аннотації типів полегшують розуміння та обслуговування коду, особливо у великих проєктах з кількома розробниками.
- Покращений досвід розробників: IDE забезпечують краще автозаповнення, виділення помилок та можливості рефакторингу, коли доступна інформація про типи.
- Перевірка даних: Безпека типів дозволяє перевіряти структуру та типи даних, отриманих з API, забезпечуючи цілісність даних.
Основне використання Fetch API з TypeScript
Розпочнемо з простого прикладу використання Fetch API в TypeScript без безпеки типів:
async function fetchData(url: string) {
const response = await fetch(url);
const data = await response.json();
return data;
}
fetchData('https://api.example.com/users')
.then(data => {
console.log(data.name); // Potential runtime error if 'name' doesn't exist
});
У цьому прикладі функція `fetchData` отримує дані з вказаного URL та розбирає відповідь як JSON. Однак тип змінної `data` неявно `any`, що означає, що TypeScript не забезпечуватиме жодної перевірки типів. Якщо відповідь API не містить властивості `name`, код видасть помилку під час виконання.
Реалізація безпеки типів з інтерфейсами
Найпоширеніший спосіб додати безпеку типів до викликів Fetch API в TypeScript – це визначення інтерфейсів, які представляють структуру очікуваних даних.
Визначення інтерфейсів
Припустимо, ми отримуємо список користувачів з API, який повертає дані у наступному форматі:
[
{
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com"
},
{
"id": 2,
"name": "Jane Smith",
"email": "jane.smith@example.com"
}
]
Ми можемо визначити інтерфейс для представлення цієї структури даних:
interface User {
id: number;
name: string;
email: string;
}
Використання інтерфейсів з Fetch API
Тепер ми можемо оновити функцію `fetchData` для використання інтерфейсу `User`:
async function fetchData(url: string): Promise<User[]> {
const response = await fetch(url);
const data = await response.json();
return data as User[];
}
fetchData('https://api.example.com/users')
.then(users => {
users.forEach(user => {
console.log(user.name); // Type-safe access to 'name' property
});
});
У цьому оновленому прикладі ми додали анотацію типу до функції `fetchData`, вказавши, що вона повертає `Promise`, яка розширюється до масиву об’єктів `User` (`Promise<User[]>`). Ми також використовуємо твердження типу (`as User[]`), щоб повідомити TypeScript, що дані, повернуті `response.json()`, є масивом об’єктів `User`. Це допомагає TypeScript зрозуміти структуру даних і забезпечити перевірку типів.
Важлива примітка: Хоча ключове слово `as` виконує твердження типу, воно не виконує перевірку під час виконання. Воно повідомляє компілятору, чого очікувати, але не гарантує, що дані фактично відповідають заявленому типу. Саме тут бібліотеки, як-от `io-ts` або `zod`, стають у нагоді для перевірки під час виконання, як ми обговоримо пізніше.
Використання генериків для повторно використовуваних функцій отримання
Щоб створити більш повторно використовувані функції отримання, ми можемо використовувати генерики. Генерики дозволяють нам визначити функцію, яка може працювати з різними типами даних, не потребуючи написання окремих функцій для кожного типу.
Визначення загальної функції отримання
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: T = await response.json();
return data;
}
У цьому прикладі ми визначили загальну функцію `fetchData`, яка приймає параметр типу `T`. Функція повертає `Promise`, яка розширюється до значення типу `T`. Ми також додали обробку помилок, щоб перевірити, чи вдалася відповідь.
Використання загальної функції отримання
Тепер ми можемо використовувати загальну функцію `fetchData` з різними інтерфейсами:
interface Post {
id: number;
title: string;
body: string;
userId: number;
}
fetchData<Post>('https://jsonplaceholder.typicode.com/posts/1')
.then(post => {
console.log(post.title); // Type-safe access to 'title' property
})
.catch(error => {
console.error("Error fetching post:", error);
});
fetchData<User[]>('https://api.example.com/users')
.then(users => {
users.forEach(user => {
console.log(user.email);
});
})
.catch(error => {
console.error("Error fetching users:", error);
});
У цьому прикладі ми використовуємо загальну функцію `fetchData` для отримання як окремого `Post`, так і масиву об’єктів `User`. TypeScript автоматично виведе правильний тип на основі параметра типу, який ми надаємо.
Обробка помилок та кодів стану
Критично важливо обробляти помилки та коди стану під час роботи з Fetch API. Ми можемо додати обробку помилок до нашої функції `fetchData`, щоб перевірити наявність помилок HTTP і видати помилку, якщо це необхідно.
async function fetchData<T>(url: string): Promise<T> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: T = await response.json();
return data;
}
У цьому оновленому прикладі ми перевіряємо властивість `response.ok`, яка вказує, чи знаходиться код стану відповіді в діапазоні 200-299. Якщо відповідь не ОК, ми видаємо помилку з кодом стану.
Перевірка даних під час виконання з `io-ts` або `zod`
Як згадувалося раніше, твердження типів TypeScript (`as`) не виконують перевірку під час виконання. Щоб переконатися, що дані, отримані з API, фактично відповідають очікуваному типу, ми можемо використовувати такі бібліотеки, як `io-ts` або `zod`.
Використання `io-ts`
`io-ts` – це бібліотека для визначення типів під час виконання та перевірки даних відповідно до цих типів.
import * as t from 'io-ts'
import { PathReporter } from 'io-ts/PathReporter'
const UserType = t.type({
id: t.number,
name: t.string,
email: t.string
})
type User = t.TypeOf<typeof UserType>
async function fetchDataAndValidate(url: string): Promise<User[]> {
const response = await fetch(url)
const data = await response.json()
const decodedData = t.array(UserType).decode(data)
if (decodedData._tag === 'Left') {
const errors = PathReporter.report(decodedData)
throw new Error(`Validation errors: ${errors.join('\n')}`)
}
return decodedData.right
}
fetchDataAndValidate('https://api.example.com/users')
.then(users => {
users.forEach(user => {
console.log(user.name);
});
})
.catch(error => {
console.error('Error fetching and validating users:', error);
});
У цьому прикладі ми визначаємо `UserType` за допомогою `io-ts`, що відповідає нашому інтерфейсу `User`. Потім ми використовуємо метод `decode` для перевірки даних, отриманих з API. Якщо перевірка не вдається, ми видаємо помилку з помилками перевірки.
Використання `zod`
`zod` – ще одна популярна бібліотека для оголошення та перевірки схем.
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>;
async function fetchDataAndValidate(url: string): Promise<User[]> {
const response = await fetch(url);
const data = await response.json();
const parsedData = z.array(UserSchema).safeParse(data);
if (!parsedData.success) {
throw new Error(`Validation errors: ${parsedData.error.message}`);
}
return parsedData.data;
}
fetchDataAndValidate('https://api.example.com/users')
.then(users => {
users.forEach(user => {
console.log(user.name);
});
})
.catch(error => {
console.error('Error fetching and validating users:', error);
});
У цьому прикладі ми визначаємо `UserSchema` за допомогою `zod`, що відповідає нашому інтерфейсу `User`. Потім ми використовуємо метод `safeParse` для перевірки даних, отриманих з API. Якщо перевірка не вдається, ми видаємо помилку з помилками перевірки.
Як `io-ts`, так і `zod` забезпечують потужний спосіб переконатися, що дані, отримані з API, відповідають очікуваному типу під час виконання.
Інтеграція з популярними фреймворками (React, Angular, Vue.js)
Безпечні типи викликів Fetch API можна легко інтегрувати з популярними JavaScript фреймворками, як-от React, Angular і Vue.js.
Приклад React
import React, { useState, useEffect } from 'react';
interface User {
id: number;
name: string;
email: string;
}
function UserList() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
async function fetchUsers() {
try {
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: User[] = await response.json();
setUsers(data);
} catch (error: any) {
setError(error.message);
} finally {
setLoading(false);
}
}
fetchUsers();
}, []);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error}</p>;
}
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default UserList;
У цьому прикладі React ми використовуємо хук `useState` для керування станом масиву `users`. Ми також використовуємо хук `useEffect` для отримання користувачів з API під час монтування компонента. Ми додали анотації типів до стану `users` та змінної `data`, щоб забезпечити безпеку типів.
Приклад Angular
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-list',
template: `
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
`,
styleUrls: []
})
export class UserListComponent implements OnInit {
users: User[] = [];
constructor(private http: HttpClient) { }
ngOnInit() {
this.http.get<User[]>('https://api.example.com/users')
.subscribe(users => {
this.users = users;
});
}
}
У цьому прикладі Angular ми використовуємо сервіс `HttpClient` для надсилання виклику API. Ми вказуємо тип відповіді як `User[]` за допомогою генериків, що забезпечує безпеку типів.
Приклад Vue.js
<template>
<ul>
<li v-for="user in users" :key="user.id">{{ user.name }}</li>
</ul>
</template>
<script>
import { defineComponent, ref, onMounted } from 'vue'
interface User {
id: number
name: string
email: string
}
export default defineComponent({
setup() {
const users = ref<User[]>([])
onMounted(async () => {
try {
const response = await fetch('https://api.example.com/users')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data: User[] = await response.json()
users.value = data
} catch (error) {
console.error('Error fetching users:', error)
}
})
return {
users
}
}
})
</script>
У цьому прикладі Vue.js ми використовуємо функцію `ref` для створення реактивного масиву `users`. Ми використовуємо хук життєвого циклу `onMounted`, щоб отримати користувачів з API під час монтування компонента. Ми додали анотації типів до `users` ref та змінної `data`, щоб забезпечити безпеку типів.
Найкращі практики для безпечних типів викликів Fetch API
Ось деякі найкращі практики, яких слід дотримуватися під час реалізації безпечних типів викликів Fetch API у TypeScript:
- Визначення інтерфейсів: Завжди визначайте інтерфейси, які представляють структуру очікуваних даних.
- Використання генериків: Використовуйте генерики для створення повторно використовуваних функцій отримання, які можуть працювати з різними типами даних.
- Обробка помилок: Реалізуйте обробку помилок, щоб перевірити наявність помилок HTTP і видати помилки, якщо це необхідно.
- Перевірка даних: Використовуйте такі бібліотеки, як `io-ts` або `zod`, щоб перевірити дані, отримані з API під час виконання.
- Типізуйте свій стан: Під час інтеграції з такими фреймворками, як React, Angular і Vue.js, типізуйте свої змінні стану та відповіді API.
- Централізуйте конфігурацію API: Створіть центральне місце для базового URL-адреси API та будь-яких спільних заголовків або параметрів. Це полегшує підтримку та оновлення конфігурації вашого API. Розгляньте можливість використання змінних середовища для різних середовищ (розробка, проміжна, виробнича).
- Використовуйте бібліотеку клієнта API (необов’язково): Розгляньте можливість використання бібліотеки клієнта API, як-от Axios, або згенерованого клієнта зі специфікації OpenAPI/Swagger. Ці бібліотеки часто надають вбудовані функції безпеки типів і можуть спростити ваші взаємодії з API.
Висновок
Реалізація безпеки типів з Fetch API в TypeScript важлива для створення надійних і зручних в обслуговуванні веб-застосунків. Визначаючи інтерфейси, використовуючи генерики, обробляючи помилки та перевіряючи дані під час виконання, ви можете значно зменшити помилки під час виконання та покращити загальний досвід розробників. Цей посібник містить всебічний огляд того, як досягти безпеки типів з Fetch API, а також практичні приклади та найкращі практики. Дотримуючись цих вказівок, ви можете створити більш надійні та масштабовані веб-застосунки, які легше зрозуміти та підтримувати.
Подальше дослідження
- Генерація коду OpenAPI/Swagger: Вивчіть інструменти, які автоматично генерують клієнти API TypeScript зі специфікацій OpenAPI/Swagger. Це може значно спростити інтеграцію API та забезпечити безпеку типів. Приклади включають: `openapi-typescript` та `swagger-codegen`.
- GraphQL з TypeScript: Розгляньте можливість використання GraphQL з TypeScript. Схема GraphQL зі строгим типом забезпечує чудову безпеку типів і усуває надмірне отримання даних.
- Тестування безпеки типів: Напишіть модульні тести, щоб перевірити, чи ваші виклики API повертають дані очікуваного типу. Це допомагає переконатися, що ваші механізми безпеки типів працюють правильно.