Дженерики (Generics)
Дженерик — условные тип, который может определять значения других. Считайте, что дженерик — это аргумент для типа, например, как в функции. Рассмотрим пару примеров. К слову, я уже упоминал, когда говорил о типах массива, первые пример будет на нём:
const array: Array<string> = [
"Hello", ",", " ", "World", "!"
];Разберём тип Array<string>, а точнее то, что написано в стрелках. Кстати, воспринимайте стрелки как скобки в функциях, а всё то, что написано в них как аргументы, Вам, возможно, будет проще так воспринимать дженерики. Итак, тип string в типе Array говорит нам, что он расширяет тип Array, делая из него массив строк. Также можно написать Array<Array<string>>, тогда это будет вложенный массив строк в массиве. То есть [["string"]].
Дженерики в своих типах
Желательно знать про создание своих типов. Итак, дженерик — это что-то в роде аргумента в функции, но только как тип. Рассмотрим простые примеры.
// Пример 1
type Foo<T> = T;
const foo: Foo<string> = "123";
// type foo: string
// Пример 2
type Foo<T> = { [key: string]: T };
const foo: Foo<Array<string>> = {
bar1: ["asd", "qwe"],
bar2: []
};
// type foo: objectВ повседневном использовании, как я думаю, не должно возникнуть вопросов по их использованию, однако это простые примеры, давайте перейдём к чему-нибудь посложнее.
type Members = "FOCKUSTY"|"Valentin Bird"|"beyz1k"|"Omonillo";
type Foo<T extends string, K> = Map<T, K>;
const foo: Foo<Members, string> = new Map([
["FOCKUSTY", "ceo"],
["Valentin Bird", "team leader"],
["beyz1k", "promoter"],
["Omonillo", "promoter"]
]);Итак… Думаю, что некоторым тут итак всё ясно, но давайте я лучше поясню.
Первым делом мы создали тип Members, который содержит всех (год 2025) участников команды LAF , — запомним и оставим на попозже.
Далее мы создали тип Foo, который принимает два дженерика: T и K (стандартные названия для дженерик-типов, однако я рекомендую называть нормально, так T стал бы Key, а K — Value). Далее, мы засунули эти дженерики в дженерики Map соответственно по их расположению, делайте так всегда, чтобы было проще понимать, с чем работает разработчик.
Вернёмся к первому типу. Итак, так мы говорим, что ключи Map не могут содержать другого значения, а сами значения Map могут быть (перешли к Foo<Members, string>, говорим про string) только строками.
Однако, что это за extends такой? — Так мы говорим, что дженерик T может быть только строковым литералом.
Рассмотрим ещё другие примеры из реальной разработки на примере KakDela :
type IResponse<T, K=null> = ({
successed: true
data: T
error: null
} | {
successed: false
data: K
error: string
});
export interface IUser {
id: string;
username: string;
created_at: string;
avatar_url?: string;
global_name?: string;
};
const response1: IResponse<IUser> = {
successed: true,
data: {
id: "1",
username: "FOCKUSTY",
created_at: new Date().toISOString(),
},
error: null
};
const response2: IResponse<IUser> = {
successed: false,
data: null,
error: "user not found"
};
// Свойство "response2" объявлено, но его значение не было прочитано.
// Тип "{ successed: false; data: { id: string; username: string; created_at: string; }; error: null; }" не может быть назначен для типа "IResponse<IUser, null>".
// Типы свойства "data" несовместимы.
// Тип "{ id: string; username: string; created_at: string; }" не может быть назначен для типа "null".
const errorResponse: IResponse<IUser> = {
successed: false,
data: {
id: "1",
username: "FOCKUSTY",
created_at: new Date().toISOString(),
},
error: null
};Что ж… Начнём с самого начала.
IResponse<T, K=null>, где k=null — это дефолтное значение, прям как в функциях.
А дальше мы говорим, что когда successed будет равен true, то у нас другие значения будут принимать: data: T, error: null. А когда successed будет равен false, то уже data: K, error: string.
На самом деле так очень удобно описывать типы, ибо может появится такая ситуация, что successed равен true, а data будет T | K. Прошлым же способ мы явно указываем при каких значения successed какие значения будут принимать соответствующие типы. Такое можно использовать не только при boolean-типах, а при любых. Например, когда мы явно указываем тип объекта. Однако это уже не про дженерики.
Пожалуй начальная информация на этом всё.