Дженерики в TypeScript: мой путь новичка (и первые успехи!)
Меня зовут Татьяна Киселёва, я работаю разработчиком в компании IBS. Недавно я решила взяться за TypeScript, потому что хочу писать более структурированный и безопасный код, а в перспективе — освоить React. В этой статье я делюсь своим опытом изучения и использования дженериков в TypeScript.
Зачем мне нужен TypeScript?
В мире JavaScript, где типы данных определяются на ходу, легко наткнуться на неожиданные ошибки. TypeScript помогает избежать этого, предлагая статическую типизацию. Мне кажется, это особенно важно для больших проектов, где в команде работают несколько разработчиков.
Я планирую использовать TypeScript в React, чтобы создавать более надежные и предсказуемые компоненты. Можно сказать, что TypeScript — это инвестиция в качество и надежность моего кода.
Дженерики: что это и для чего?
Дженерики (от англ. generic — «общий, обобщенный») как волшебная палочка в TypeScript. Они позволяют писать функции и классы, которые могут работать с разными типами данных, оставаясь при этом безопасными.
Представьте, что нужно написать функцию, которая сортирует массив. Без дженериков пришлось бы создавать отдельные функции для массивов чисел, строк, объектов... Брр, вспоминаю, как однажды копировала-вставляла код, меняя только тип данных. Дженерики обещают избавить от этой рутины.
Пример 1: от простого к универсальному (сумма и конкатенация)
Начнем с простой функции без дженериков, которая принимает массив чисел и возвращает их сумму.
В этом примере функция sum работает только с массивами чисел. Если мы захотим создать аналогичную функцию для строк, нам придется написать новый код.
А теперь функция для конкатенации строк:
В результате мы написали два разных кода для двух типов данных, что неэффективно. Дженерики это меняют!
Пример 2: универсальная функция с дженериками (identity)
Сначала объявим параметр типа. Он обозначает тип данных, передаваемых в функцию или класс. Для этого используется специальный символ, обычно буква Т. Параметр типа объявляется с помощью угловых скобок <>. Например, в функции identity<T>(arg: T):T параметр «T» обозначает, что функция может принимать аргумент любого типа.
Здесь identity — это функция, которая принимает аргумент типа T и возвращает его. Мы не указываем конкретный тип, а используем T, чтобы функция могла работать с любым типом данных.
Функция identity может принимать как числа, так и строки и возвращать их без изменений. Это уже реальный прогресс! Получается более универсальный код.
Пример 3: возвращаем значение по ключу (getValueByKey)
Теперь попробуем с применением дженериков создать функцию, которая будет возвращать значение по ключу из объекта.
В этом примере getValueByKey принимает объект obj и ключ key. При этом используем K extends keyof T, чтобы гарантировать, что ключ действительно существует в объекте. Функция возвращает значение, соответствующее этому ключу.
Мне кажется, это действительно удобно. Правда, иногда возникают вопросы:
Как правильно использовать extends для ограничения типов?
Как работать с несколькими параметрами типа?
Как лучше всего применять дженерики в классах?
Реальный пример (с моими комментариями и размышлениями)
Недавно я столкнулась с задачей, где нужно было получить данные пользователя из JSON-файла и вывести их на страницу. Вот как я попыталась это сделать, используя дженерики:
Файл: Users.json
Файл: app.ts
Результат — данные пользователя user2 отображаются на странице:
В этом примере мы использовали дженерик User для типизации данных, получаемых с users.json, чтобы TypeScript мог проверить корректность типов и выявить возможные ошибки на этапе разработки. Функция fetchUsers<User>() гарантирует, что мы получим объект объектов с полями id, name и email. В функции handleFetchUser получаем пользователя с ключом user2 из объекта пользователей, используя функцию getValueByKey, и выводим данные пользователя на страницу.
Вывод: первые шаги сделаны, дженерики работают!
Использование дженериков сразу делает код более чистым и типобезопасным. Я довольна первыми результатами, хотя понимаю, что еще многое предстоит узнать.
Честно говоря, я пока не уверена, что это идеальное решение. Возможно, есть более элегантные способы типизации, но рада, что попробовала применить дженерики на практике.
Что дальше? Планы и вопросы
Я хочу углубиться в изучение дженериков, разобраться с ограничениями, вариантами использования в классах и, конечно же, с практическими примерами из реальных проектов.
Мне интересно:
Какие распространенные ошибки допускают новички при работе с дженериками?
Как дженерики помогают писать более модульный и тестируемый код?
Как эффективно использовать дженерики в React-компонентах?
Как я пробую применять дженерики в своем коде:
Начинаю с простых примеров. Создаю функции, которые принимают и возвращают значения разных типов. Постепенно перехожу к более сложным структурам — классам, ограниченным дженерикам и т. д.
Использую комментарии в коде. Объясняю логику каждого примера, чтобы облегчить понимание.
Тестирую дженерики с различными типами данных, чтобы убедиться в их универсальности и корректности.
Обращаюсь к документации. Ее можно найти на официальном сайте TypeScript. Русскоязычная версия документации есть на scriptdev.ru. Также можно обратиться к платформам MDN, Stack Overflow и различным онлайн-курсам по TypeScript.
Я не эксперт, а скорее любопытный новичок. Если вы тоже изучаете дженерики или уже имеете опыт работы с ними — буду рада вашим советам!