بررسی Generic تایپ اسکریپت در سه الگوی ساده

آنچه که ممکن است در تایپ اسکریپت به عنوان Generic درنظر بگیریم در واقع سه مفهوم مجزا است که عبارتند از:

  • انتقال تایپ به تایپ
  • انتقال تایپ به توابع
  • استنباط تایپ از آرگومان‌های ارسال شده به توابع
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// ۱٫ Passing types to types
type PartialUser = Partial<{ id: string; name: string }>;
// ۲٫ Passing types to functions
const stringSet = new Set<string>();
// ۳٫ Inferring types from arguments passed to functions
const objKeys = <T extends object>(obj: T): Array<keyof T> => {
return Object.keys(obj) as Array<keyof T>;
};
const keys = objKeys({ a: 1, b: 2 });
// ^? const keys: ("a" | "b")[]
// ۱٫ Passing types to types type PartialUser = Partial<{ id: string; name: string }>; // ۲٫ Passing types to functions const stringSet = new Set<string>(); // ۳٫ Inferring types from arguments passed to functions const objKeys = <T extends object>(obj: T): Array<keyof T> => { return Object.keys(obj) as Array<keyof T>; }; const keys = objKeys({ a: 1, b: 2 }); // ^? const keys: ("a" | "b")[]
// ۱٫ Passing types to types
type PartialUser = Partial<{ id: string; name: string }>;
// ۲٫ Passing types to functions
const stringSet = new Set<string>();
// ۳٫ Inferring types from arguments passed to functions
const objKeys = <T extends object>(obj: T): Array<keyof T> => {
  return Object.keys(obj) as Array<keyof T>;
};
const keys = objKeys({ a: 1, b: 2 });
//    ^? const keys: ("a" | "b")[]

انتقال تایپ به تایپ

با انتقال تایپ به تایپ شروع می‌کنیم.

در تایپ اسکریپت، ما می‌توانیم تایپی را تعریف کنیم که نشان‌دهنده یک آبجکت، primitive، تابع یا هر چیز دیگری که می‌خواهیم، باشد.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export type User = {
id: string;
name: string;
};
export type ToString = (input: any) => string;
export type User = { id: string; name: string; }; export type ToString = (input: any) => string;
export type User = {
  id: string;
  name: string;
};
export type ToString = (input: any) => string;

اما فرض کنید باید چند تایپ با ساختار مشابه ایجاد کنیم. به عنوان مثال، یک data shape.

مثال زیر را در نظر بگیرید:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
type ErrorShape = {
message: string;
code: number;
};
type GetUserData = {
data: {
id: string;
name: string;
};
error?: ErrorShape;
};
type GetPostsData = {
data: {
id: string;
title: string;
};
error?: ErrorShape;
};
type GetCommentsData = {
data: {
id: string;
content: string;
};
error?: ErrorShape;
};
type ErrorShape = { message: string; code: number; }; type GetUserData = { data: { id: string; name: string; }; error?: ErrorShape; }; type GetPostsData = { data: { id: string; title: string; }; error?: ErrorShape; }; type GetCommentsData = { data: { id: string; content: string; }; error?: ErrorShape; };
type ErrorShape = {
  message: string;
  code: number;
};
type GetUserData = {
  data: {
    id: string;
    name: string;
  };
  error?: ErrorShape;
};
type GetPostsData = {
  data: {
    id: string;
    title: string;
  };
  error?: ErrorShape;
};
type GetCommentsData = {
  data: {
    id: string;
    content: string;
  };
  error?: ErrorShape;
};

اگر مایل باشیم که از OOP استفاده کنیم، می‌توانیم این کار را با استفاده از یک interface با قابلیت استفاده مجدد مانند کد زیر انجام دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
interface DataBaseInterface {
error?: ErrorShape;
}
interface GetUserData extends DataBaseInterface {
data: {
id: string;
name: string;
};
}
interface GetPostsData extends DataBaseInterface {
data: {
id: string;
title: string;
};
}
interface GetCommentsData extends DataBaseInterface {
data: {
id: string;
content: string;
};
}
interface DataBaseInterface { error?: ErrorShape; } interface GetUserData extends DataBaseInterface { data: { id: string; name: string; }; } interface GetPostsData extends DataBaseInterface { data: { id: string; title: string; }; } interface GetCommentsData extends DataBaseInterface { data: { id: string; content: string; }; }
interface DataBaseInterface {
  error?: ErrorShape;
}
interface GetUserData extends DataBaseInterface {
  data: {
    id: string;
    name: string;
  };
}
interface GetPostsData extends DataBaseInterface {
  data: {
    id: string;
    title: string;
  };
}
interface GetCommentsData extends DataBaseInterface {
  data: {
    id: string;
    content: string;
  };
}

اما ایجاد یک type function مختصرتر است، که تایپ داده را دریافت کرده و data shape جدید را برمی‌گرداند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Our new type function!
type DataShape<TData> = {
data: TData;
error?: ErrorShape;
};
type GetUserData = DataShape<{
id: string;
name: string;
}>;
type GetPostsData = DataShape<{
id: string;
title: string;
}>;
type GetCommentsData = DataShape<{
id: string;
content: string;
}>;
// Our new type function! type DataShape<TData> = { data: TData; error?: ErrorShape; }; type GetUserData = DataShape<{ id: string; name: string; }>; type GetPostsData = DataShape<{ id: string; title: string; }>; type GetCommentsData = DataShape<{ id: string; content: string; }>;
// Our new type function!
type DataShape<TData> = {
  data: TData;
  error?: ErrorShape;
};
type GetUserData = DataShape<{
  id: string;
  name: string;
}>;
type GetPostsData = DataShape<{
  id: string;
  title: string;
}>;
type GetCommentsData = DataShape<{
  id: string;
  content: string;
}>;

درک این سینتکس مهم است، زیرا در ادامه مقاله با آن سروکار خواهیم داشت.

براکت‌های موجود در

<TData>
<TData>به تایپ اسکریپت می‌گویند که ما می‌خواهیم یک تایپ آرگومان به این تایپ اضافه کنیم. ما می‌توانیم
TData
TDataرا هر چیزی نام‌گذاری کنیم، این دقیقاً مانند آرگومان یک تابع می‌باشد.

مثالی که در ادامه داریم یک تایپ generic است:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Generic type
type DataShape<TData> = {
data: TData;
error?: ErrorShape;
};
// Passing our generic type
// another type
type GetPostsData = DataShape<{
id: string;
title: string;
}>;
// Generic type type DataShape<TData> = { data: TData; error?: ErrorShape; }; // Passing our generic type // another type type GetPostsData = DataShape<{ id: string; title: string; }>;
// Generic type
type DataShape<TData> = {
  data: TData;
  error?: ErrorShape;
};
// Passing our generic type
// another type
type GetPostsData = DataShape<{
  id: string;
  title: string;
}>;

تایپ‌های generic می‌توانند چندین نوع آرگومان را بپذیرند. ما می‌توانیم پیش‌فرض‌ها و همچنین محدودیت‌هایی را برای تایپ آرگومان‌ها تنظیم کنیم تا فقط بتوانیم انواع خاصی را به آن‌ها ارسال کنیم.

اما فقط تایپ‌ها نیستند که می‌توانیم تایپ را به آن‌ها منتقل کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const createSet = <T>() => {
return new Set<T>();
};
const stringSet = createSet<string>();
// ^? const stringSet: Set<string>
const numberSet = createSet<number>();
// ^? const numberSet: Set<number>
const createSet = <T>() => { return new Set<T>(); }; const stringSet = createSet<string>(); // ^? const stringSet: Set<string> const numberSet = createSet<number>(); // ^? const numberSet: Set<number>
const createSet = <T>() => {
  return new Set<T>();
};
const stringSet = createSet<string>();
// ^? const stringSet: Set<string>
const numberSet = createSet<number>();
// ^? const numberSet: Set<number>

صحیح است، درست مانند تایپ‌ها که می‌توانند تایپ‌های دیگر را به عنوان آرگومان بپذیرند، توابع نیز می‌توانند این کار را انجام دهند.

انتقال تایپ به توابع

در این مثال، زمانی که createSet را تعریف می‌کنیم، یک

<T>
<T>را به قبل از پرانتز اضافه می‌نماییم. سپس آن
<T>
<T>را به صورت دستی به
Set()
Set()انتقال می‌دهیم، که به ما اجازه می‌دهد تا تایپ آرگومان را ارسال کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export const createSet = <T>() => {
return new Set<T>();
}
export const createSet = <T>() => { return new Set<T>(); }
export const createSet = <T>() => {
  return new Set<T>();
}

این بدان معناست که وقتی آن را فراخوانی می‌کنیم، می‌توانیم یک تایپ آرگومان

<string>
<string>را به
createSet
createSetارسال کنیم.

اکنون مجموعه‌ای را بررسی می‌کنیم که فقط می‌توانیم رشته‌ها را به آن منتقل کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const stringSet = createSet<string>();
// Argument of type 'number' is not assignable to parameter of type 'string'.
stringSet.add(123);
const stringSet = createSet<string>(); // Argument of type 'number' is not assignable to parameter of type 'string'. stringSet.add(123);
const stringSet = createSet<string>();
// Argument of type 'number' is not assignable to parameter of type 'string'.
stringSet.add(123);

این موضوع دومین چیزی است که هنگام صحبت در مورد genericها به آن اشاره می‌شود: انتقال دستی تایپ‌ها به توابع.

اگر از React استفاده کنیم این را خواهیم دید، و ما باید یک تایپ آرگومان را به

useState
useStateارسال کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const [index, setIndex] = useState<number>(0);
// ^? const index: number
const [index, setIndex] = useState<number>(0); // ^? const index: number
const [index, setIndex] = useState<number>(0);
// ^? const index: number

اما متوجه رفتار دیگری در React می‌شویم که در برخی موارد، برای استنباط آن نیازی به ارسال تایپ آرگومان نداریم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const [index, setIndex] = useState(0);
// ^? const index: number
// WHAT IS THIS SORCERY
const [index, setIndex] = useState(0); // ^? const index: number // WHAT IS THIS SORCERY
const [index, setIndex] = useState(0);
// ^? const index: number
// WHAT IS THIS SORCERY

برای اینکه ببینیم چه اتفاقی افتاده است، بار دیگر تابع

createSet
createSetرا بررسی می‌کنیم. باید به این نکته توجه داشته باشیم که فقط تایپ‌ آرگومان‌ها را دریافت می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export const createSet = <T>() => {
return new Set<T>();
}
export const createSet = <T>() => { return new Set<T>(); }
export const createSet = <T>() => {
  return new Set<T>();
}

اگر تابع خود را طوری تغییر دهیم که یک مقدار اولیه برای مجموعه بپذیرد، در این صورت چه اتفاقی می‌افتد؟

می‌دانیم که مقدار اولیه باید از نوع Set باشد، بنابراین آن را به صورت

T
Tمی‌نویسیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export const createSet = <T>(initial: T) => {
return new Set<T>([initial]);
}
export const createSet = <T>(initial: T) => { return new Set<T>([initial]); }
export const createSet = <T>(initial: T) => {
  return new Set<T>([initial]);
}

اکنون وقتی آن را فراخوانی می‌کنیم، باید یک

initial
initialارسال کنیم، و این باید دارای همان نوع تایپ آرگومانی باشد که به
createSet
createSetمی‌فرستیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const stringSet = createSet<string>("matt");
// ^? const stringSet: Set<string>
// Argument of type 'string' is not assignable to parameter of type 'number'.
const numberSet = createSet<number>("pocock");
const stringSet = createSet<string>("matt"); // ^? const stringSet: Set<string> // Argument of type 'string' is not assignable to parameter of type 'number'. const numberSet = createSet<number>("pocock");
const stringSet = createSet<string>("matt");
//    ^? const stringSet: Set<string>
// Argument of type 'string' is not assignable to parameter of type 'number'.
const numberSet = createSet<number>("pocock");

این اتفاق عجیبی نیست زیرا تایپ اسکریپت می‌تواند تایپ

T
Tرا از
initial
initialاستنباط کند. به عبارت دیگر، تایپ آرگومان از آرگومان زمان اجرا استنباط می‌شود.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const stringSet = createSet("matt");
// ^? const stringSet: Set<string>
const numberSet = createSet(123);
// ^? const numberSet: Set<number>
const stringSet = createSet("matt"); // ^? const stringSet: Set<string> const numberSet = createSet(123); // ^? const numberSet: Set<number>
const stringSet = createSet("matt");
//    ^? const stringSet: Set<string>
const numberSet = createSet(123);
//    ^? const numberSet: Set<number>

می‌توانیم این موضوع را با نگه داشتن ماوس روی یکی از فراخوانی‌های

createSet
createSetبررسی کنیم. وقتی رشته‌ای را به آن ارسال می‌کنیم، خواهیم دید که
<string>
<string>استنباط می‌شود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// hovering over createSet
const createSet: <string>(initial: string) => Set<string>
// hovering over createSet const createSet: <string>(initial: string) => Set<string>
// hovering over createSet
const createSet: <string>(initial: string) => Set<string>

در

useState
useStateنیز همین امر صادق است، برای آن باید به سینتکس
useState<number>
useState<number>در tooltip مراجعه کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// hovering over useState
useState<number>(initialState: number | (() => number)): [number, Dispatch<SetStateAction<number>>]
// hovering over useState useState<number>(initialState: number | (() => number)): [number, Dispatch<SetStateAction<number>>]
// hovering over useState
useState<number>(initialState: number | (() => number)): [number, Dispatch<SetStateAction<number>>]

این موضوع همچنین برای تابع

objKeys
objKeysکه شامل برخی مزایای بیشتر می‌باشد نیز صادق است:

  • ما
    T
    Tرا به یک آبجکت محدود می‌کنیم تا بتوانیم آن را به
    Object.keys
    Object.keys(که فقط آبجکت‌ها را می‌پذیرد) ارسال کنیم.
  • ما مجبور می‌کنیم که تایپ بازگشتی
    Object.keys
    Object.keysاز نوع
    Array<keyof T>
    Array<keyof T>باشد.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const objKeys = <T extends object>(obj: T) => {
return Object.keys(obj) as Array<keyof T>;
};
const objKeys = <T extends object>(obj: T) => { return Object.keys(obj) as Array<keyof T>; };
const objKeys = <T extends object>(obj: T) => {
  return Object.keys(obj) as Array<keyof T>;
};

جمع‌بندی

در این مقاله سعی کردیم تا با مفهوم Generic در تایپ اسکریپت بیشتر آشنا شویم. به طور خلاصه، آنچه که ما به عنوان generic فکر می‌کنیم در واقع سه الگوی مختلف است که عبارتند از:

  • انتقال تایپ به تایپ –
    DataShape<T>
    DataShape<T>
  • انتقال تایپ به توابع –
    createSet<string>()
    createSet<string>()
  • استنباط تایپ از آرگومان‌های ارسال شده به توابع –
    useState(0)
    useState(0)

دیدگاه‌ها:

افزودن دیدگاه جدید