πŸ“œ Как Π½Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½Ρ‹ΠΉ API-ΠΊΠ»ΠΈΠ΅Π½Ρ‚ Π½Π° Typescript

Π’ этой ΡΡ‚Π°Ρ‚ΡŒΠ΅ я ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎ расскаТу ΠΎ Ρ€Π΅Π°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ API-ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° Π½Π° языкС TypeScript для Ρ€Π°Π±ΠΎΡ‚Ρ‹ ΠΊΠ°ΠΊ со сторонними API, Ρ‚Π°ΠΊ ΠΈ со своими собствСнными. ΠšΠ»ΠΈΠ΅Π½Ρ‚ ΠΌΠΎΠΆΠ΅Ρ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΌΠΈ ΠΈ Π·Π°Ρ‰ΠΈΡ‰Π΅Π½Π½Ρ‹ΠΌΠΈ эндпойнтами ΠΈ Π½Π΅ привязан ΠΊ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠΌΡƒ Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΡƒ, Ρ‡Ρ‚ΠΎ Π΄Π΅Π»Π°Π΅Ρ‚ Π΅Π³ΠΎ ΠΏΡ€ΠΈΠ³ΠΎΠ΄Π½Ρ‹ΠΌ для использования Π² React, Vue, Svelte ΠΈ Π΄Ρ€ΡƒΠ³ΠΈΡ… Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠ°Ρ….

Боздавая ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ слоТнСС ToDo-листа, Ρ‡Π°Ρ‰Π΅ всСго Π½Π°ΠΌ трСбуСтся Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡ‚Π²ΠΎΠ²Π°Ρ‚ΡŒ с ΠΊΠ°ΠΊΠΈΠΌΠΈ-Ρ‚ΠΎ Π΄Π°Π½Π½Ρ‹ΠΌΠΈ, хранящимися Π½Π° сСрвСрС. Π­Ρ‚ΠΎ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ ΠΊΠ°ΠΊ ΠΏΡ€ΠΎΠ³Π½ΠΎΠ·Ρ‹ ΠΏΠΎΠ³ΠΎΠ΄Ρ‹, ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅ΠΌΡ‹Π΅ сторонним API, Ρ‚Π°ΠΊ ΠΈ Π΄Π°Π½Π½Ρ‹Π΅ Π½Π°ΡˆΠΈΡ… ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ², Π±ΡƒΠ΄ΡŒ Ρ‚ΠΎ ΠΈΡ… Π»ΠΎΠ³ΠΈΠ½ ΠΈ ΠΏΠ°Ρ€ΠΎΠ»ΡŒ ΠΈΠ»ΠΈ список ΠΏΠΎΠΊΡƒΠΏΠΎΠΊ Π² ΠΌΠ°Π³Π°Π·ΠΈΠ½Π΅. Работая с SPA (Single Page Application) ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΠΌ, Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ эти самыС Π΄Π°Π½Π½Ρ‹Π΅ ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ, ΠΌΠΎΠ΄ΠΈΡ„ΠΈΡ†ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΈ ΠΎΡ‚ΠΏΡ€Π°Π²Π»ΡΡ‚ΡŒ со стороны ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°. Π‘Π»Π΅Π΄ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΠ½ΠΎ, Π½ΡƒΠΆΠ½ΠΎ ΠΈΠΌΠ΅Ρ‚ΡŒ ΠΊΠ°ΠΊΡƒΡŽ-Ρ‚ΠΎ прослойку, ΠΎΡ‚Π²Π΅Ρ‡Π°ΡŽΡ‰ΡƒΡŽ Π·Π° взаимодСйствиС с сСрвСром. Π’ этой ΡΡ‚Π°Ρ‚ΡŒΠ΅ рассмотрим использованиС API-ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° с Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΎΠΉ React, хотя Π΅ΠΉ ΠΌΠΎΠΆΠ½ΠΎ смСло ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒΡΡ Π½Π° Ρ‚ΠΎΠΌ ΠΆΠ΅ Vue, Svelte ΠΈ Ρ‚Π°ΠΊ Π΄Π°Π»Π΅Π΅.

ΠŸΠΎΡ‡Π΅ΠΌΡƒ Π½Π΅ ΠΏΡ€ΠΎΠΏΠΈΡΠ°Ρ‚ΡŒ всС запросы Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°Ρ…, Π³Π΄Π΅ ΠΎΠ½ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ΡΡ?

ВсС просто: Ссли Ρƒ вас помСняСтся интСрфСйс API, с ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ Π²Ρ‹ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅, Π²Π°ΠΌ придСтся ΠΏΡ€ΠΎΠΉΡ‚ΠΈΡΡŒ ΠΏΠΎ всСму ΠΊΠΎΠ΄Ρƒ ΠΈ Π½Π°ΠΉΡ‚ΠΈ всС Ρ‚ΠΎΡ‡ΠΊΠΈ ΠΈΠ·ΠΌΠ΅Π½Π΅Π½ΠΈΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ это Π·Π°Ρ‚Ρ€ΠΎΠ½ΡƒΠ»ΠΎ. МоТно ΠΏΠΎΠΏΡ€ΠΎΠ±ΠΎΠ²Π°Ρ‚ΡŒ вынСсти эту Π»ΠΎΠ³ΠΈΠΊΡƒ Π² React-Ρ…ΡƒΠΊΠΈ, Ρ€Π°Π· ΡƒΠΆ сСйчас Ρ€Π΅Ρ‡ΡŒ ΠΎ Π½Π΅ΠΌ, Π½ΠΎ это Ρ€Π΅ΡˆΠ΅Π½ΠΈΠ΅ Π½Π΅ получится ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π² Π΄Ρ€ΡƒΠ³ΠΈΡ… ΠΏΡ€ΠΎΠ΅ΠΊΡ‚Π°Ρ… c Π΄Ρ€ΡƒΠ³ΠΈΠΌΠΈ Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠ°ΠΌΠΈ.

РСализация Π½Π° Typescript

Для Π½Π°Ρ‡Π°Π»Π° вынСсСм Π΄ΠΎΠΌΠ΅Π½Ρ‹, Π³Π΄Π΅ находятся API, Π² своСго Ρ€ΠΎΠ΄Π° ΠΊΠΎΠ½Ρ„ΠΈΠ³, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΉ с .env-Ρ„Π°ΠΉΠ»ΠΎΠΌ:

REACT_APP_API_BASE_URL=http://localhost:8083
export default {
    get apiBaseUrl(): string {
        return process.env.REACT_APP_API_BASE_URL || "";
    },
}

Π—Π°Ρ‚Π΅ΠΌ напишСм сам абстрактный ΠΊΠ»ΠΈΠ΅Π½Ρ‚, Π½Π΅ привязанный ΠΊ Π΄Π°Π½Π½ΠΎΠΌΡƒ Π΄ΠΎΠΌΠ΅Π½Ρƒ. Для Π΅Π³ΠΎ Ρ€Π°Π±ΠΎΡ‚Ρ‹ ΠΏΠΎΡ‚Ρ€Π΅Π±ΡƒΡŽΡ‚ΡΡ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ axios ΠΈ axios-extensions.

Код ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°:

import axios, {AxiosInstance, AxiosRequestConfig} from "axios";
import {
    Forbidden,
    HttpError,
    Unauthorized
} from '../errors';
import {Headers} from "../types";

export class ApiClient {
    constructor(
        private readonly baseUrl: string,
        private readonly headers: Headers,
        private readonly authToken: string = ""
    ) {}

    public async get(endpoint: string = "", params?: any, signal?: AbortSignal): Promise<any> {
        try {
            const client = this.createClient(params);
            const response = await client.get(endpoint, { signal });
            return response.data;
        } catch (error: any) {
            this.handleError(error);
        }
    }

    public async post(endpoint: string = "", data?: any, signal?: AbortSignal): Promise<any> {
        try {
            const client = this.createClient();
            const response = await client.post(endpoint, data, { signal });
            return response.data;
        } catch (error) {
            this.handleError(error);
        }
    }

    public async uploadFile(endpoint: string = "", formData: FormData): Promise<any> {
        try {
            const client = this.createClient();
            const response = await client.post(endpoint, formData, {
                headers: {
                    "Content-Type": "multipart/form-data",
                }
            })
            return response.data;
        } catch (error) {
            this.handleError(error);
        }
    }

    private createClient(params: object = {}): AxiosInstance {
        const config: AxiosRequestConfig = {
            baseURL: this.baseUrl,
            headers: this.headers,
            params: params
        }
        if (this.authToken) {
            config.headers = {
                Authorization: `Bearer ${this.authToken}`,
            }
        }
        return axios.create(config);
    }

    private handleError(error: any): never {
        if (!error.response) {
            throw new HttpError(error.message)
        } else if (error.response.status === 401) {
            throw new Unauthorized(error.response.data);
        } else if (error.response.status === 403) {
            throw new Forbidden(error.response.data);
        } else {
            throw error
        }
    }
}

Π’ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π΅ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ΡΡ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΈΠ΅ Ρ‚ΠΈΠΏΡ‹, Ρ‚Π°ΠΊΠΈΠ΅ ΠΊΠ°ΠΊ Headers, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ, ΠΏΠΎ сути, являСтся просто словарСм [key: string]: string, ΠΈ Ρ€Π°Π·Π»ΠΈΡ‡Π½Ρ‹Π΅ ошибки, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ Π½Π°ΡΠ»Π΅Π΄ΡƒΡŽΡ‚ Π³Π»ΠΎΠ±Π°Π»ΡŒΠ½Ρ‹ΠΉ класс Error (Unauthorized, Forbidden, HttpError), Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π² дальнСйшСм Π±Ρ‹Π»ΠΎ ΠΏΡ€ΠΎΡ‰Π΅ ΠΏΠΎΠ½ΡΡ‚ΡŒ, Ρ‡Ρ‚ΠΎ послуТило ΠΈΡ… ΠΏΡ€ΠΈΡ‡ΠΈΠ½ΠΎΠΉ.

Π£ класса всСго Ρ‚Ρ€ΠΈ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹Ρ… ΠΌΠ΅Ρ‚ΠΎΠ΄Π°, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ ΠΏΡ€ΠΈ ΠΊΠ°ΠΆΠ΄ΠΎΠΌ использовании Π³Π΅Π½Π΅Ρ€ΠΈΡ€ΡƒΡŽΡ‚ axios-ΠΊΠ»ΠΈΠ΅Π½Ρ‚. Π­Ρ‚ΠΎΡ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΠΌΠΎΠΆΠ΅Ρ‚ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ ΠΊΠ°ΠΊ с ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΌΠΈ эндпоинтами API, Ρ‚Π°ΠΊ ΠΈ с Π·Π°Ρ‰ΠΈΡ‰Π΅Π½Π½Ρ‹ΠΌΠΈ, ΠΏΡƒΡ‚Π΅ΠΌ добавлСния Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠ° с Bearer-Ρ‚ΠΎΠΊΠ΅Π½ΠΎΠΌ. Как ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ этот самый Ρ‚ΠΎΠΊΠ΅Π½, Π±ΡƒΠ΄Π΅Ρ‚ рассмотрСно ΠΏΠΎΠ·ΠΆΠ΅. Как get-, Ρ‚Π°ΠΊ ΠΈ post-ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡŽΡ‚ Π½Π΅ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ abortSignal, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ позволяСт ΠΏΡ€Π΅Ρ€Π²Π°Ρ‚ΡŒ ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΡƒ запроса Π² зависимости ΠΎΡ‚ дСйствий ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ.

Π’ случаС с ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΎΠΉ ΠΊΠ°ΠΊΠΈΡ…-Π»ΠΈΠ±ΠΎ Ρ„Π°ΠΉΠ»ΠΎΠ² Π½Π° сСрвСр ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄ uploadFile(), отправляя Π½Π° сСрвСр запрос с Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΎΠΌ Content-Type: multipart/form-data.

Для инкапсуляции Π»ΠΎΠ³ΠΈΠΊΠΈ создания этих ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ², напишСм Ρ„Π°Π±Ρ€ΠΈΠΊΡƒ.

Код Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ:

import {Headers} from "../../types";
import {ApiClient} from "../../clients";

export class ApiClientFactory {
    constructor(
        private readonly baseUrl: string,
        private readonly headers: Headers = {}
    ) {}

    public createClient(): ApiClient {
        return new ApiClient(this.baseUrl, this.headers);
    }

    public createAuthorizedClient(authToken: string): ApiClient {
        return new ApiClient(this.baseUrl, this.headers, authToken);
    }
}

НичСго слоТного ΠΎΠ½Π° Π½Π΅ Π΄Π΅Π»Π°Π΅Ρ‚: просто создаСт Π»ΠΈΠ±ΠΎ ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΉ ΠΊΠ»ΠΈΠ΅Π½Ρ‚, Π»ΠΈΠ±ΠΎ Π°Π²Ρ‚ΠΎΡ€ΠΈΠ·ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ, пСрСдавая Π² конструктор Ρ‚ΠΎΠΊΠ΅Π½.

Π‘ΠΎΠ»ΡŒΡˆΠ΅ ΠΏΠΎΠ»Π΅Π·Π½Ρ‹Ρ… ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΎΠ² Π²Ρ‹ Π½Π°ΠΉΠ΄Π΅Ρ‚Π΅ Π½Π° нашСм Ρ‚Π΅Π»Π΅Π³Ρ€Π°ΠΌ-ΠΊΠ°Π½Π°Π»Π΅ Β«Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° Ρ„Ρ€ΠΎΠ½Ρ‚Π΅Π½Π΄Π΅Ρ€Π°Β»

ΠšΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Π°Ρ рСализация

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ Π°Π΄Π°ΠΏΡ‚ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ этот абстрактный ΠΊΠ»ΠΈΠ΅Π½Ρ‚ ΠΏΠΎΠ΄ ΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΉ эндпоинт. НапримСр, создадим ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€, ΠΏΠΎΠ»ΡƒΡ‡Π°ΡŽΡ‰ΠΈΠΉ с сСрвСра послСднСС состояниС профиля ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ:

import {ApiClientInterface} from "./clients";
import {Profile} from "./models";

export class ProfileManager {
    constructor(private readonly apiClient: ApiClientInterface) {}

    public async get(): Promise<Profile> {
        return this.apiClient.get("");
    }
}

Π’ Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ Π½Π°ΠΌ Π½Π΅ Π²Π°ΠΆΠ½Π° модСль, ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ ΠΌΡ‹ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ для профиля. Π‘ΡƒΠ΄Π΅ΠΌ просто ΡΡ‡ΠΈΡ‚Π°Ρ‚ΡŒ, Ρ‡Ρ‚ΠΎ ΠΎΠ½Π° совмСстима с ΠΏΠ΅Ρ€Π΅Π΄Π°Π²Π°Π΅ΠΌΡ‹ΠΌ с сСрвСра Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ΠΌ.

Π‘Π°ΠΌ класс ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Π° ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ ΠΊΠΎΠΌΠΏΠΎΠ·ΠΈΡ†ΠΈΡŽ ΠΈ Ρ…Ρ€Π°Π½ΠΈΡ‚ Π² своСм состоянии ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΠΏΠ΅Ρ€Π΅Π°Π΄Ρ€Π΅ΡΠΎΠ²Ρ‹Π²Π°Ρ‚ΡŒ всС API-запросы ΠΊ Π½Π΅ΠΌΡƒ, Π° Ссли Π½Π°Π΄ΠΎ, ΠΎΠ½ смоТСт Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΊΠ°ΠΊΡƒΡŽ-Ρ‚ΠΎ свою Π»ΠΎΠ³ΠΈΠΊΡƒ ΠΊ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½Π½ΠΎΠΌΡƒ Π·Π½Π°Ρ‡Π΅Π½ΠΈΡŽ (провСсти Π²Π°Π»ΠΈΠ΄Π°Ρ†ΠΈΡŽ, ΡΠΎΠ·Π΄Π°Ρ‚ΡŒ свой эндпоинт ΠΈ Ρ‚Π°ΠΊ Π΄Π°Π»Π΅Π΅).

Π§Π°Ρ‰Π΅ всСго, API Π³Ρ€ΡƒΠΏΠΏΠΈΡ€ΡƒΡŽΡ‚ Π΄ΠΎΠΌΠ΅Π½Π½ΡƒΡŽ Π»ΠΎΠ³ΠΈΠΊΡƒ, добавляя ΠΊ своим эндпоинтам ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΉ прСфикс. Π’Π°ΠΊΠΆΠ΅ Π±Ρ‹Π²Π°ΡŽΡ‚ случаи ΠΌΠΈΠ³Ρ€Π°Ρ†ΠΈΠΈ API с ΠΎΠ΄Π½ΠΎΠΉ вСрсии Π½Π° Π±ΠΎΠ»Π΅Π΅ Π½ΠΎΠ²ΡƒΡŽ. Π§Ρ‚ΠΎΠ±Ρ‹ всС это ΠΏΡ€Π΅Π΄ΡƒΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ, создадим Ρ„Π°Π±Ρ€ΠΈΠΊΡƒ для этого ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Π°.

Код Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ:

import {ApiClientFactory} from "./clients";
import {Headers} from "../types";
import {ProfileManager} from "../ProfileManager";

export class ProfileManagerFactory {
    private readonly apiClientFactory: ApiClientFactory;

    constructor(baseUrl: string, headers: Headers) {
        this.apiClientFactory = new ApiClientFactory(
            `${baseUrl}/api/v1/profile`,
            headers
        );
    }

    public createProfileManager(authToken: string): ProfileManager {
        return new ProfileManager(
            this.apiClientFactory.createAuthorizedClient(authToken)
        );
    }
}

ΠŸΡ€ΠΈ создании этой Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ, Π² конструктор пСрСдаСтся URL Π΄ΠΎΠΌΠ΅Π½Π° ΠΈ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ для запроса. Π—Π°Ρ‚Π΅ΠΌ эти ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΏΠ΅Ρ€Π΅Π΄Π°ΡŽΡ‚ΡΡ Π² конструктор Ρ„Π°Π±Ρ€ΠΈΠΊΠΈ API ΠΊΠ»ΠΈΠ΅Π½Ρ‚ΠΎΠ², дописывая послС ΠΏΠ΅Ρ€Π΅Π΄Π°Π½Π½ΠΎΠ³ΠΎ URL Π²Π΅Ρ€ΡΠΈΡŽ API ΠΈ Ρ‚ΠΎΡ‚ самый прСфикс, ΠΎΠ±ΠΎΠ·Π½Π°Ρ‡Π°ΡŽΡ‰ΠΈΠΉ Ρ‡Π°ΡΡ‚ΡŒ Π΄ΠΎΠΌΠ΅Π½Π½ΠΎΠΉ Π»ΠΎΠ³ΠΈΠΊΠΈ. ΠŸΡ€ΠΈ создании ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Π° ΠΏΡ€ΠΎΡ„ΠΈΠ»Π΅ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ, трСбуСтся авторизация, Ρ‚Π°ΠΊ Ρ‡Ρ‚ΠΎ Π² ΠΌΠ΅Ρ‚ΠΎΠ΄ пСрСдаСтся Ρ‚ΠΎΠΊΠ΅Π½, Π½Π° основС ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ создаСтся ΠΊΠ»ΠΈΠ΅Π½Ρ‚ с Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΎΠΌ Π°Π²Ρ‚ΠΎΡ€ΠΈΠ·Π°Ρ†ΠΈΠΈ.

Dependency injection

Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΎΡΡ‚Π°Π»ΠΎΡΡŒ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π½Π°ΠΏΠΈΡΠ°Ρ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ, которая Π±ΡƒΠ΄Π΅Ρ‚ ΠΎΡ‚Π²Π΅Ρ‡Π°Ρ‚ΡŒ Π·Π° прСдоставлСниС Ρ€Π°Π±ΠΎΡ‡Π΅Π³ΠΎ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Π° ΠΏΡ€ΠΎΡ„ΠΈΠ»Π΅ΠΉ Π² любой части ΠΊΠΎΠ΄Π°, Π±ΡƒΠ΄ΡŒ Ρ‚ΠΎ React-ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚ ΠΈΠ»ΠΈ нСзависимый Typescript-класс. Π’Ρ‹Π³Π»ΡΠ΄Π΅Ρ‚ΡŒ ΠΎΠ½Π° Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π½ΠΎ Ρ‚Π°ΠΊ:

export async function createProfileManager(): Promise<apiClient.ProfileManager> {
    const factory = new apiClient.ProfileManagerFactory(apiClientConfig.apiBaseUrl, getBaseHeaders());
    return factory.createProfileManager(await getAuthToken());
}

Π’Π½Π°Ρ‡Π°Π»Π΅ Π²Π½ΡƒΡ‚Ρ€ΠΈ создаСтся Ρ„Π°Π±Ρ€ΠΈΠΊΠ° этих самых ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ΠΎΠ², Π² ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ пСрСдаСтся Π΄ΠΎΠΌΠ΅Π½ сСрвСра ΠΈ Π±Π°Π·ΠΎΠ²Ρ‹Π΅ Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅ выглядят Ρ‚Π°ΠΊ:

function getBaseHeaders(): apiClient.Headers {
    return {
        "Accept-Language": "ru"
    }
}

ΠŸΡ€ΠΈ ΠΆΠ΅Π»Π°Π½ΠΈΠΈ ΠΌΠΎΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ Π½Π° ΡƒΡ€ΠΎΠ²Π½Π΅ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ создания ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Π° Π»ΡŽΠ±Ρ‹Π΅ свои Π·Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ.

Бпособ получСния API-Ρ‚ΠΎΠΊΠ΅Π½Π° ΠΈ Ρ€Π°Π±ΠΎΡ‚Ρ‹ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ getAuthToken() я Π½Π΅ Π±ΡƒΠ΄Ρƒ Ρ€Π°ΡΡΠΌΠ°Ρ‚Ρ€ΠΈΠ²Π°Ρ‚ΡŒ Π² этой ΡΡ‚Π°Ρ‚ΡŒΠ΅, ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ эта Ρ‚Π΅ΠΌΠ° заслуТиваСт ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎΠΉ ΠΏΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΈ.

async function getAuthToken(): Promise<string> {
    // Π—Π΄Π΅ΡΡŒ Π±Ρ‹Π» Π±Ρ‹ ΠΊΠΎΠ΄ получСния Ρ‚ΠΎΠΊΠ΅Π½Π°, Π½ΠΎ ΠΏΠΎΠΊΠ° Ρ‡Ρ‚ΠΎ просто...
    return localStorage.getItem("auth-token");
}

ИспользованиС Π² ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Π°Ρ….

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ Ρ€Π°Π±ΠΎΡ‚Ρ‹ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€Π° ΠΏΡ€ΠΎΡ„ΠΈΠ»Π΅ΠΉ прСдставлСн Π½ΠΈΠΆΠ΅:

useEffect(() => {
        (async () => {
            try {
                await initProfile();
            } catch (error: any) {
                await handleError(error);
            } finally {
                setLoading(false);
            }
        })()
    }, []);

    const initProfile = async () => {
        const manager = await createProfileManager();
        const profile = await manager.get();
        await dispatch(set(profile));
    }

ΠŸΡ€ΠΈ запускС Ρ„ΡƒΠ½ΠΊΡ†ΠΈΠΈ Π² Ρ…ΡƒΠΊΠ΅ useEffect, асинхронно создаСтся ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ€ ΠΏΡ€ΠΎΡ„ΠΈΠ»Π΅ΠΉ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ Π·Π°Ρ‚Π΅ΠΌ Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Π΅Ρ‚ с сСрвСра Ρ‚Π΅ΠΊΡƒΡ‰Π΅Π΅ состояниС профиля ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Π΅ΠΉ. Π’ Π΄Π°Π½Π½ΠΎΠΌ ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅ ΠΌΡ‹ просто записываСм ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½Π½ΠΎΠ΅ состояниС Π² Ρ…Ρ€Π°Π½ΠΈΠ»ΠΈΡ‰Π΅ Redux, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π·Π°Ρ‚Π΅ΠΌ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с этим ΠΏΡ€ΠΎΡ„ΠΈΠ»Π΅ΠΌ, Π½Π΅ ΠΏΠ΅Ρ€Π΅Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Ρ ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ Ρ€Π°Π· Π΅Π³ΠΎ с сСрвСра. Π’ случаС ошибки Ρ€Π°Π±ΠΎΡ‚Ρ‹ ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π°, запускаСтся функция handleError(), которая Π² зависимости ΠΎΡ‚ Ρ€ΠΎΠ΄Π° ошибки, ΠΎ Ρ‡Π΅ΠΌ я Π³ΠΎΠ²ΠΎΡ€ΠΈΠ» Ρ€Π°Π½Π΅Π΅, выполняСт Ρ‚Π΅ ΠΈΠ»ΠΈ ΠΈΠ½Ρ‹Π΅ дСйствия.

Π˜Ρ‚ΠΎΠ³ΠΈ

Данная рСализация нСзависима ΠΎΡ‚ Ρ„Ρ€Π΅ΠΉΠΌΠ²ΠΎΡ€ΠΊΠ°, с ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΌ Π²Ρ‹ Ρ€Π°Π±ΠΎΡ‚Π°Π΅Ρ‚Π΅, Π΅Π΅ ΠΌΠΎΠΆΠ½ΠΎ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π΄Π°ΠΆΠ΅ Π½Π° Π½Π°Ρ‚ΠΈΠ²Π½ΠΎΠΌ JS (TS). Π’ Π½Π΅ΠΉ ΠΌΠΎΠΆΠ½ΠΎ Π΅Ρ‰Π΅ ΠΌΠ½ΠΎΠ³ΠΎ Ρ‡Π΅Π³ΠΎ Π΄ΠΎΡ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ, Π½Π°ΠΏΡ€ΠΈΠΌΠ΅Ρ€ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΠΏΠ°Ρ‚Ρ‚Π΅Ρ€Π½ Β«β€ŽΠ‘Ρ‚Ρ€ΠΎΠΈΡ‚Π΅Π»ΡŒΒ» для создания API-ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° ΠΈ ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡ΠΈ Π² Π½Π΅Π³ΠΎ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ΠΎΠ², abortSignal-ΠΎΠ² ΠΈ ΠΏΡ€ΠΎΡ‡Π΅Π³ΠΎ, ΠΈΠ»ΠΈ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ Π²Π°Ρ€ΠΈΠ°Ρ‚ΠΈΠ²Π½ΡƒΡŽ систСму Π°ΡƒΡ‚Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ†ΠΈΠΈ Ρ‡Π΅Ρ€Π΅Π· JWT-Ρ‚ΠΎΠΊΠ΅Π½. ВсС Π½Π° вашС усмотрСниС). Π’ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅ΠΉ ΡΡ‚Π°Ρ‚ΡŒΠ΅ расскаТу Π²Π°ΠΌ ΠΏΡ€ΠΎ способ получСния ΠΈ Ρ€Π°Π±ΠΎΡ‚Ρƒ с API-Ρ‚ΠΎΠΊΠ΅Π½Π°ΠΌΠΈ Π½Π° ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π΅.

***

ΠœΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»Ρ‹ ΠΏΠΎ Ρ‚Π΅ΠΌΠ΅

Π›Π£Π§Π¨Π˜Π• БВАВЬИ ПО Π’Π•ΠœΠ•