- PVSM.RU - https://www.pvsm.ru -

Всё больше и больше проектов и команд используют TypeScript. Однако просто применять TypeScript и выжимать из него максимум пользы — это очень разные вещи.
Представляю вам список высокоуровневых передовых практик использования TypeScript, которые помогут получить максимум преимуществ от применения этого языка.
Типы — это контракт. Что это значит? Когда вы реализуете функцию, её тип становится обещанием, данным другим разработчикам (или вам же самим в будущем!), что, будучи вызвана, эта функция вернет определенный тип значения.
В следующем примере тип функции getUser гарантирует, что она возвращает объект, у которого всегда есть два свойства: name и age.
interface User {
name: string;
age: number;
}
function getUser(id: number): User { /* ... */ }
TypeScript — очень гибкий язык. В нем много компромиссов, призванных облегчить внедрение языка. Например, он позволяет вам реализовать функцию getUser вот так:
function getUser(id: number): User {
return { age: 12 } as User;
}
Не делайте так! Это ЛОЖЬ. Создавая такой код, вы ЛЖЕТЕ другим разработчикам (которые будут использовать вашу функцию в своих функциях). Они ожидают, что у объекта, возвращаемого функцией getUser, всегда будет какое-то поле name. Но его нет! Далее, что произойдет, когда ваш коллега напишет getUser(1).name.toString()? Вы прекрасно знаете, что…
Здесь, конечно, ложь выглядит очевидной. Однако, работая с большой базой кода, вы будете часто оказываться в ситуациях, когда значение, которое вы хотите вернуть (или передать), почти совпадает с ожидаемым типом. Чтобы найти причину несовпадения типов, нужны время и усилия, а вы торопитесь… поэтому вы решаете использовать приведение типов.
Однако, делая это, вы нарушаете священный контракт. ВСЕГДА лучше выделить время и понять, почему типы не совпадают, чем использовать приведение типов. Очень вероятно, что под поверхностью скрывается какой-нибудь баг времени выполнения.
Не лгите. Соблюдайте свои контракты.
Типы — это документация. Документируя функцию, разве вы не хотите донести как можно больше информации?
// Возвращает объект
function getUser(id) { /* ... */ }
// Возвращает объект с двумя свойствами: name и age
function getUser(id) { /* ... */ }
// Если id является числом и пользователь с данным id существует,
// возвращает объект с двумя свойствами: name и age.
// В противном случае возвращает undefined.
function getUser(id) { /* ... */ }
Какой комментарий для функции getUser вам бы больше понравился? Чем больше вы знаете о том, что возвращает функция, тем лучше. Например, зная, что она может вернуть undefined, вы можете написать блок if для проверки того, определен ли объект, который вернула функция, — перед тем, как запрашивать свойства этого объекта.
Ровно то же самое и с типами: чем более точно описан тип, тем больше информации он передает.
function getUserType(id: number): string { /* ... */ }
function getUserType(id: number): 'standard' | 'premium' | 'admin' { /* ... */ }
Вторая версия функции getUserType гораздо более информативна, и поэтому вызывающий ее находится в гораздо более удобной ситуации. Обрабатывать величину легче, если вы наверняка знаете (контракты, помните?), что это будет одна из трех заданных строк, а не просто любая строка. Начать с того, что вы точно знаете — величина не может быть пустой строкой.
Рассмотрим более реальный пример. Тип State описывает состояние компонента, который запрашивает некоторые данные с бекэнда. Точен ли этот тип?
interface State {
isLoading: boolean;
data?: string[];
errorMessage?: string;
}
Клиент, использующий данный тип, должен обрабатывать некоторые маловероятные сочетания значений свойств состояния. Например, невозможна ситуация, когда будут одновременно определены свойства data и errorMessage: запрос данных может либо быть успешным, либо завершиться с ошибкой.
Мы можем сделать тип намного более точным с помощью разграничивающих объединяющих типов (discriminated union types [1]):
type State =
| { status: 'loading' }
| { status: 'successful', data: string[] }
| { status: 'failed', errorMessage: string };
Теперь у клиента, использующего данный тип, гораздо больше информации: ему больше не нужно обрабатывать неверные сочетания свойств.
Будьте точны. Передавайте в своих типах как можно больше информации.
Так как типы являются одновременно и контрактом, и документацией, они отлично подходят для проектирования ваших функций (или методов).
В Интернете есть множество статей, которые советуют программистам подумать перед тем, как писать код. Я полностью разделяю этот подход. Соблазн сразу перейти к коду велик, но это часто приводит к плохим решениям. Немного времени, потраченного на обдумывание реализации, всегда окупается сторицей.
Типы чрезвычайно полезны в этом процессе. Обдумывание приводит к созданию сигнатур типов функций, связанных с решением вашей задачи. И это замечательно, потому что вы фокусируетесь на том, что делают ваши функции, вместо раздумий о том, как они это делают.
В React JS есть понятие компонента высшего порядка (Higher Order Components, HOC). Это функции, которые каким-либо образом расширяют заданный компонент. К примеру, вы можете создать компонент высшего порядка withLoadingIndicator, который добавляет индикатор загрузки в существующий компонент.
Давайте напишем сигнатуру типа для этой функции. Функция принимает на вход компонент и возвращает тоже компонент. Для представления компонента мы можем воспользоваться типом React ComponentType.
ComponentType является обобщенным типом (generic type), который параметризуется типом свойств компонента. withLoadingIndicator принимает компонент и возвращает новый компонент, который отображает либо оригинальный компонент, либо индикатор загрузки. Решение о том, что именно отобразить, принимается исходя из значения нового логического свойства — isLoading. Таким образом, возвращаемому компоненту необходимы те же свойства, что и оригинальному, добавляется лишь новое свойство isLoading.
Окончательно оформим тип. withLoadingIndicator принимает компонент типа ComponentType<P>, где P обозначает тип свойств. withLoadingIndicator возвращает компонент с расширенными свойствами типа P & { isLoading: boolean }.
const withLoadingIndicator = <P>(Component: ComponentType<P>)
: ComponentType<P & { isLoading: boolean }> =>
({ isLoading, ...props }) => { /* ... */ }
Разбираясь с типами функции, мы были вынуждены думать о том, что будет у нее на входе и что на выходе. Другими словами, нам пришлось проектировать функцию. Написать её реализацию теперь — проще простого.
Начинайте с типов. Пусть типы вынуждают вас сначала проектировать, и лишь после этого писать реализацию.
Первые три заповеди требуют от вас уделять особое внимание типам. К счастью, решая эту задачу, вы не обязаны делать все самостоятельно — зачастую сам компилятор TypeScript даст вам знать, когда ваши типы лгут или когда они недостаточно точны.
Можно помочь компилятору выполнять эту работу еще лучше, включив флаг --strict. Это мета-флаг, который подключает все опции строгой проверки типов: --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictBindCallApply, --strictNullChecks, --strictFunctionTypes и --strictPropertyInitialization.
Что делают это флаги? Говоря в общем, их включение приводит к увеличению количества ошибок компиляции TypeScript. И это хорошо! Больше ошибок компиляции — больше помощи от компилятора.
Посмотрим, как включение флага --strictNullChecks помогает выявить ложь в коде.
function getUser(id: number): User {
if (id >= 0) {
return { name: 'John', age: 12 };
} else {
return undefined;
}
}
Тип getUser гарантирует, что функция всегда возвращает объект типа User. Однако посмотрите на реализацию: функция может также вернуть значение undefined!
К счастью, включение флага --strictNullChecks приводит к ошибке компиляции:
Type 'undefined' is not assignable to type 'User'.
Компилятор TypeScript обнаруживает ложь. Чтобы избавиться от этой ошибки, просто честно скажите всю правду:
function getUser(id: number): User | undefined { /* ... */ }
Примите строгость проверки типов. Пусть компилятор оберегает вас от ошибок.
Язык TypeScript развивается очень быстрыми темпами. Новый релиз выходит каждые два месяца. Каждый релиз привносит значительные улучшения языка и новые возможности.
Часто бывает так, что новые возможности языка позволяют определить типы точнее и проверять их строже.
Например, в версии 2.0 были представлены Discriminated Union Types (я упомянул их в заповеди Будьте точны).
Версия 3.2 представила флаг компилятора --strictBindCallApply, который включает корректную типизацию для функций bind, call и apply.
Версия 3.4 улучшила выведение типов (type inference) в функциях высшего порядка [2], что облегчило использование точных типов при написании кода в функциональном стиле.
Моя позиция такова, что знакомство с возможностями языка, вводимыми в последних версиях TypeScript, на самом деле стоит того. Часто это может помочь вам следовать остальным четырем заповедям из списка.
Хорошей стартовой точкой является официальная дорожная карта TypeScript [3]. Также будет неплохо регулярно проверять раздел TypeScript в Microsoft Devblog [4], так как все анонсы релизов выходят именно там.
Будьте в курсе новых возможностей языка, и пусть это знание работает на вас.
Надеюсь, изложенный список показался вам полезным. Как всегда и во всем, не стоит слепо следовать этим заповедям. Но я твердо убежден, что эти правила сделают вас более хорошим TypeScript-разработчиком.
Буду рад увидеть ваши мысли на этот счет в комментариях.
Понравилась эта статья о TypeScript? Уверен, вам также понравится и этот бесплатный PDF: 10 ошибок разработки на TypeScript, которые делают ваш код небезопасным [5]
Автор: bevalorous
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sovershenny-j-kod/325258
Ссылки в тексте:
[1] discriminated union types: https://www.typescriptlang.org/docs/handbook/advanced-types.html#discriminated-unions
[2] Версия 3.4 улучшила выведение типов (type inference) в функциях высшего порядка: https://codewithstyle.info/TypeScript-3-4-hidden-gem-propagated-generic-type-arguments/
[3] официальная дорожная карта TypeScript: https://github.com/Microsoft/TypeScript/wiki/Roadmap
[4] раздел TypeScript в Microsoft Devblog: https://devblogs.microsoft.com/typescript/
[5] 10 ошибок разработки на TypeScript, которые делают ваш код небезопасным: https://typescriptmasterclass.com/
[6] Источник: https://habr.com/ru/post/461565/?utm_source=habrahabr&utm_medium=rss&utm_campaign=461565
Нажмите здесь для печати.