Дженерики

  —  3 минуты

#theory#typescript#web
Читать статью в Telegram

Сколько я не бился в попытках объяснить что такое дженерик, кажется, что эта тема многим даётся тяжело.

Generic Typeобобщённый тип — это тип, который позволяет создавать по своему подобию другие типы или же динамически вычислять типы в зависимости от входных данных. Тут я максимально отойду от какой-то академичности и приведу простую аналогию:

Тип — это переменная

Дженерик тип — это функция, которая возвращает тип

И дженерик типы называть функциями в корне неверно, зато такая аналогия для начала будет понятна большинству.

Разберем на примере:

typescript
1const name = "Denis"
2
3const getPerson = (name) => ({
4    name,
5    type: "person"
6})
7
8const person = getPerson(name)
9

В этом примере у нас есть переменная, на основе которой мы можем получить некоторый объект person используя функцию getPerson. А теперь рассмотрим нечто похожее в типах:

typescript
1type Status = "success" | "error"
2
3type Response<T> = {
4    status: T,
5    message: string,
6    code: number,
7}
8
9type ApiResponse = Response<Status>
10

Уже в этом примере мы наблюдаем чем-то похожую ситуацию коду выше — мы создаём какую-то новую сущность на основе уже существующей. В первом случае — новый объект, а во втором — новый тип.

В таком контексте дженерики можно воспринимать как функции-конструкторы, которые, основываясь на некоторых входных параметрах, могут создавать новые типы. И из всего этого можно сделать вывод, что дженерики — это конструкторы типов.

На этом моменте у многих новичков часто возникает закономерный вопрос — а зачем их использовать? И ответ на него очень прост — почти за тем же, что и обычные функции.

Дженерик типы позволяют не дублировать код, а выносить некоторые общие части, чтобы далее их переиспользовать. Попробуем написать уже такой код, что легко встретится на практике:

typescript
1// объявим сущности приложения
2type User = {
3    name: string,
4    age: number,
5}
6
7type Task = {
8    completed: boolean,
9    title: string,
10}
11
12// ответ api в случае ошибки
13type ResponseError = {
14    code: number;
15    message: string;
16}
17
18// ответ api в случае успеха
19type ListResponseSuccess<T> = {
20    data: T[],
21    total: number;
22}
23
24// общий тип со всеми возможными
25// состояниями ответа
26type ListResponse<T> = ListResponseSuccess<T> | ResponseError
27
28// типы для наших сущностей
29type UserListResponse = ListResponse<User>
30type TaskListReponse = ListResponse<Task>
31
32// и их может быть сколько угодно...
33type CarListResponse = ListResponse<Car>
34type RoleListResponse = ListResponse<Role>
35type UserGroupListResponse = ListResponse<UserGroup> 
36// и тд
37

В этом случае мы создали тип конструктор ListResponse, который в дальнейшем сможем использовать для типизации всего нашего API без лишнего дублирования общих частей.

Это самый базовый пример использования дженериков, который может встретиться на практике.

Не забывайте, что дженерики есть не только у типов, а и у интерфейсов, классов, функций… Даже у React-компонентов и хуков (что в целом тоже либо класс либо функция)

Статья была полезной?