ایجاد انواع عمومی برای پاسخ های API (backend).

من میخواستم یک نکته کوچک TypeScript را به اشتراک بگذارم که در پروژههایم از آن استفاده میکنم هر زمان که یک REST API بین ظاهر و باطن وجود دارد. من امیدوارم که شما آن را به عنوان مفید من پیدا کنید!
تنظیم صحنه
فرض کنید در حال توسعه یک برنامه وب TypeScript هستید و باطن شما از REST API استفاده می کند. در واقع شاید آنها قبلاً تمام نقاط پایانی و پاسخها را برای درخواستهای آینده شما ساخته باشند و تنها چیزی که دارید اسناد Open API (Swaggger) است که API شما را مستند میکنند.
شما در حال غرق شدن در انواع داده ها برای تمام نقاط پایانی مختلف و روش های آنها هستید: GET on api/v1/blogs/:id
POST on api/v1/blogs/
، وارد شوید api/v1/token
، ثبت نام کنید، توکن را به روز کنید، کاربر اضافه کنید، این را وصله کنید و غیره و غیره.
اگر شما به اندازه من طرفدار TypeScript هستید، احتمالاً مایلید که تجزیه و تحلیل کد استاتیک TypeScript به شما کمک کند تا قراردادهای Backend/Frontend را مدیریت کنید و آنچه را که چیست را پیگیری کنید.
اما مسئله این است که دادههای یکسان بسته به اینکه آنها را در باطن پست میکنید یا در پاسخ دریافت میکنید، میتوانند اشکال متفاوتی داشته باشند. چگونه می توان با TS این کار را انجام داد و در حین ایجاد ده ها/صدها نوع/رابط مختلف مسیر را کاملا از دست ندهیم؟
انواع داده ها
ایده اصلی این است: شما با ایجاد تمام انواعی که در واقع به عنوان یک برنامه کاربردی استفاده میکنید شروع میکنید (معتبر ثبت نام/نمایه کاربر، اعتبارنامه ورود، شکلهای داده ارسال فرم برای همه منطق ارسال دادههای مختلف که در برنامه خود دارید و غیره).
اما شما عناصری را نادیده می گیرید که توسط backend مدیریت می شوند، به عنوان مثال، id
و created_at
فیلدها، نتایج صفحه بندی شده یا پرس و جو و غیره
فرض کنید ما رابط هایی مانند این داریم:
interface Blogpost {
title: string;
content: string;
date: string;
summary: string:
tags: string[];
// ... you get the idea
}
interface Comment {
parent_id: number;
author: string;
email?: string;
}
همه آنها با استفاده از عملیات ایجاد در فهرست CRUD REST API ما مدیریت می شوند. این بدان معناست که باطن ما همه آنها را (در این مثال: هر دو) دقیقاً به یک شکل اداره می کند. بنابراین Wen می تواند یک نوع اتحادیه کمکی ایجاد کند (معنی است هر چه برنامه شما به انواع بیشتری نیاز داشته باشد، به خصوص اگر اشکال مشابه اما متفاوت داشته باشند):
type ApiTypes = Comment | Blogpost;
انواع ApiResponse
اکنون که آنچه را که ما – فرانتاند – استفاده میکنیم، تعریف کردیم، زمان آن رسیده است که نوعی ایجاد کنیم که “موارد”ی را که باطن به ما میفرستد مدیریت کند. ما یک نوع عمومی ایجاد می کنیم به عنوان مثال:
type ApiResponse<T extends ApiTypes> = T & {
id: number,
created_at?: string,
}
این extends
در آرگومان عمومی، انواع/اینترفیس هایی را که می توانیم به ApiResponse ارائه کنیم محدود می کند (این همان چیزی است که می خواهیم زیرا همه پاسخ های پشتیبان ما یک شکل نیستند، به عنوان مثال یک پاسخ فرم ورود ممکن است فقط حاوی نشانه های مجوز باشد).
طرحی دیگر ممکن است برای نتیجه صفحه بندی شده مناسب باشد:
interface PaginatedResults<T extends ApiTypes> = {
count: number;
previous: string | null;
next: string | null;
results: T[];
}
اکنون هر زمان که نیاز به پاسخگویی از API خود داشته باشیم، فقط میتوانیم نوع مناسب را به انواع عمومی خود تغییر دهیم، به عنوان مثال هنگام استفاده از TanStack Query و Axios هنگام ارسال داده ( جهش در TanStack Query):
تابع جهش:
function sendFormData<T extends ApiTypes>(url: string) {
return async (data: T): Promise<ApiResponse<T>> => {
const response: AxiosResponse<ApiResponse<T>> = await
axios.post<ApiResponse<T>>(url, JSON.stringify(data));
return response.data;
};
}
واقعی useMutation
قلاب:
const mutation: UseMutationResult<
ApiResponse<T>,
ErrorResponse
> = useMutation<ApiResponse<T>, ErrorResponse, T>({
mutationFn: sendFormData<T>(URL),
});
به عنوان یک امتیاز، می توانید ببینید که چگونه به صراحت TanStack Query تایپ شده است useMutation
به نظر می رسد. ممکن است بیش از حد به نظر برسد، اما مانند تمام TypeScript، از این به بعد در طول توسعه کار را برای شما انجام می دهد.
و هنگامی که نیاز به افزودن یک نوع داده جدید به این طرح دارید، فقط از بالا شروع کنید: نوع را ایجاد کنید، آن را به ApiTypes
union و voilà – اکنون می توانید از توابع حالت سرور ارائه شده در بالا استفاده کنید، فقط با استفاده از نوع جدید به جای حالت عمومی. T
نوع
نظر در مورد انواع در مقابل رابط ها
همانطور که احتمالاً می دانید انواع (معروف به نام مستعار نوع) و رابط ها تقریباً موارد استفاده و کاربرد یکسانی دارند. پس چرا من در اینجا اینقدر ناسازگار از آنها استفاده کردم، به طور خاص قرارداد استفاده از رابط ها را برای معرفی زیر پا گذاشتم ApiResponse
به عنوان نام مستعار نوع؟
گام طبیعی بعدی در مسیر تبدیل جهان به مکانی تمیزتر و سازگارتر، معرفی خواهد بود ApiResponse
به عنوان چیزی شبیه به این:
interface ApiResponse<T extends ApiTypes> extends T {
id: number;
created_at?: string;
}
متأسفانه این کار نمی کند – ابزار تحلیل کد استاتیک TS دلیل آن را توضیح می دهد:
TS2312: An interface can only extend an object type or intersection of object types with statically known members.
خودشه. دلیلش همینه اگر بیشتر با TypeScript آشنا هستید، مطمئن باشید که من و خوانندگان میدانند که چرا TS به این شکل طراحی شده است، اما تا آنجا که من میبینم، این فقط یک تفاوت ظریف بین یک نوع و یک رابط است که باید بپذیریم.
منبع تصویر: عکس ساواس استاورینوس