В начале был Javascript. Это язык высокого уровня, который поддерживает почти все. Это мультипарадигмальный, управляемый событиями, функциональный и императивный подход. ECMAScript эволюционировал, чтобы расширить использование Javascript. Сейчас это одна из основных технологий Интернета. По состоянию на август 2022 года 98% веб-сайтов используют Javascript в качестве клиентского языка программирования. Его эволюция распространяется на NodeJS и становится очень популярным благодаря невероятной производительности с асинхронным вводом-выводом.
Хронология 25 успешных лет Javascript
Сегодня веб-сайты превратились в нечто большее, чем просто информационные страницы. Важно, чтобы Javascript был более надежным. Нам нужно, чтобы язык стал устойчивым и простым для крупномасштабной разработки. Так родился TypeScript.
Для меня изучение того, как использовать TypeScript, было долгим и непрерывным путешествием. Я работал с TypeScript на своей самой первой инженерной работе. TypeScript был новым, и все использовали тип any
, чтобы обойти некоторые сложные проверки типов. В то время это было скорее неприятностью, чем чем-то, что я считал полезным.
При переходе в мою следующую компанию у нас не было TypeScript. Я был счастлив иметь возможность кодировать, не беспокоясь об этих ошибках компиляции. В конце концов мы начали новый проект, в котором TypeScript снова был на переднем крае. Как и другие инженеры, мы все изучали TypeScript с нуля в очень больших масштабах (читали сотни миллионов пользователей ежедневно). Прибыв в DoorDash, TypeScript был здесь уже давно. Я смог увидеть зрелость языка и его использования. Со временем я увлекся языком. Сегодня я уже не могу представить проект без TypeScript.
В этой статье мы обсудим одну из самых замечательных функций TypeScript. Конечно, это только мое мнение, и я всегда в поиске других интересных качеств этого языка.
Базовая проверка типов
Объявив тип, мы можем обеспечить проверку типов для params
при вызове нашей функции.
type TParams = { first: string | number second: number } type TFunc = (params: TParams) => void const func: TFunc = ({ first, second }) => { console.log(first, second) } func({ first: '1', second: 2 }) // Good func({ first: '1', second: '2' }) // Bad
Первый вызов функции хорош тем, что оба свойства (first
и second
) имеют правильные типы. Второй вызов будет иметь ошибку компиляции из-за того, что свойство second
передается как string
.
Это самая простая проверка типов, и ее должен знать каждый, кто использует TypeScript.
Используйте дженерики для принудительного применения типов
Общие можно использовать для расширения типа, чтобы сделать его более пригодным для повторного использования. Ниже приведен пример объявления TParams
и TFunc
как двух типов с помощью универсальных шаблонов.
type TParams<T extends string | number> = { first: T second: T } type TFunc = <T extends string | number>(params: TParams<T>) => void const func: TFunc = ({ first, second }) => { console.log(first, second) }
Если мы вызовем функцию, как раньше
func({ first: 1, second: 2 }) // Good func({ first: '1', second: '2' }) // Good func({ first: 1, second: '2' }) // Bad func({ first: '1', second: 2 }) // Bad
Типы здесь будут подразумеваться и следовать за типом переменной first
. Это заставит first
и second
иметь один и тот же тип string или number.
Ниже приведена ошибка последнего вызова функции.
TParams
также обеспечит, чтобы параметр объекта имел оба ключа.
Дополнительные ключи
Что, если мы хотим передать параметр объекта, для которого требуются определенные ключи, а другие являются необязательными?
Обновив наш тип TParams
с помощью second
в качестве необязательного аргумента, мы можем сделать так, чтобы ошибка исчезла.
type TParams<T extends string | number> = { first: T second?: T }
Условные параметры
Но TypeScript не был бы суперполезным, если бы мы не могли легко расширять наши функции. Давайте введем нашу функцию enforceKey
ниже
type TParams<T extends string | number> = { first: T second: T } const enforceKey = <T>( params: keyof T extends 'first' | 'second' ? TParams<string> : T ) => { console.log(params) }
Это приведет к ошибкам всякий раз, когда мы используем enforceKey
как
enforceKey({ first: '1' }) // Bad enforceKey({ second: '1' }) // Bad enforceKey({ first: '1', second: '2' }) // Good
Используя keyof
, он будет обеспечивать, чтобы каждый раз, когда у нас есть объект в качестве параметра, он проверял, есть ли у нас ключ first
и по умолчанию TParams
в качестве его типа, иначе он по умолчанию будет использовать все, что передано в параметре. Тем не менее, это имеет некоторые проблемы
enforceKey({ first: '1', third: '3' }) // Good
Несмотря на то, что наш параметр здесь имеет first
в качестве ключа, он не интерпретирует его как тип TParams<string>
. Как мы можем применить это дальше, чтобы, пока он имеет first
в качестве ключа, он был принудительно переведен в TParams<string>
Немедленной мыслью было бы попробовать использовать тип соединения.
type TParams<T extends string | number> = { first: T second: T } type TParams1 = { third: string forth: string } const enforceKey = (params: TParams<string> | TParams1): void => { console.log(params) }
Это приведет к ошибке компиляции желания при вызове с параметром { first: ‘1’, third: ‘3’ }
. Однако, когда мы пытаемся получить доступ к ключу first
в нашем params
, мы получаем ошибку компиляции.
Перегрузка функций
Это концепция, согласно которой мы можем объединить несколько функций в одну. В приведенном ниже примере будет создана функция с +1 overload
.
type TParams<T extends string | number> = { first: T second: T } type TParams1 = { third: string forth: string } function enforceKey(params: TParams<string>) : void function enforceKey(params: TParams1): void function enforceKey<T>(params: T): void { console.log(params) }
Теперь все наши проверки будут выполнены правильно :)
enforceKey({ first: '1', second: '2' }) // Good enforceKey({ third: '3', forth: '4' }) // Good enforceKey({ first: '1' }) // Bad enforceKey({ second: '1' }) // Bad enforceKey({ first: '1', third: '3' }) // Bad
Давайте конвертируем в старый добрый тип const
const enforceKey: { (params: TParams<string>): void (params: TParams1): void } = <T>(params: T): void => { console.log(params.first) console.log(params.third) }
И снова наша ошибка компиляции будет отображаться правильно :)
enforceKey({ first: '1', second: '2' }) // Good enforceKey({ third: '3', forth: '4' }) // Good enforceKey({ first: '1' }) // Bad enforceKey({ second: '1' }) // Bad enforceKey({ first: '1', third: '3' }) // Bad
Это не идеально. Если мы попытаемся получить доступ к свойству в params
, у нас все еще будет та же ошибка, что и при предыдущей попытке с типом объединения.
Property 'first' does not exist on type 'T'.
Чтобы противодействовать этому, нам нужно обновить наш тип T
в функции другим типом объединения.
const enforceKey: { (params: TParams<string>): void (params: TParams1): void } = <T extends TParams<string> & TParams1>(params: T): void => { console.log(params.first) console.log(params.third) }
Теперь наши проверки на first
и third
будут проходить корректно и с честью.
Заключение
Сказать, что я люблю TypeScript, было бы преуменьшением! Хотя использование TypeScript было радостью, я знаю, что ему нужно учиться. Я видел аргументы от других о наличии большого количества шаблонов, которые могут замедлить разработку для новых пользователей. Тем не менее, я все же рекомендую изучить основы. Благодаря поддержке статической/строгой типизации проверка типов выявляет ошибки во время разработки. Между тестами TypeScript и unit/e2e мы можем создавать более качественные приложения.
И, конечно же, заходите в мой блог для будущих еженедельных сообщений, если вы найдете это полезным.