Начнем с начала тайпскрипта, то бишь с его прошлого
Появился он еще в 2012 году zdnet.com/article/micros…
, релиз 1.0 состоялся в 2014 devblogs.microsoft.com/typescript/ann…
В то время язык был местами ближе к C# чем к JS, отсюда namespaces, classes, private/public/protected.
В целом это неудивительно ибо у истоков стоял (и стоит до сих пор) Андерс Хейлсберг, создатель Turbo Pascal, архитектор Delphi и, сюрприз C#
Итак на дворе 2012, Путин возвращется на президенское кресло после Медведева, до ES2015 еще три года (я кстати сначала загуглил в каком году он вышел), от ES5 уже прошло три года. И вот он, новый язык компилируемый в JS с прикольными фичами и со статической типизацией!
Несмотря на то что была взята часть фич шарпа, другая часть была переосмысленна
Типизация опциональна и имеет safehatches: any, Object, Function и т.п.
Синтаксис типов менее инвазивный, в отличии от c-style (int i) используется var i: number
Несмотря на то что в языке сразу были namespaces, назывались они в начале modules. Естественно это вызвало name clashing когда в ES2015 поэтому было решено переименовать их в неймспейсы
Вообще на релизе в ТС не было очень многого чем мы с вами пользуемся сейчас. Даже если не считать фичи ES2015 до TS 2.0 (а это было в 2016) появились: protected, unions, intersections, type, type guards, abstract classes и еще много всего
Но 2.0 превнес наиважнейшую фичу: nullable types. Возможность сказать: "Вот здесь будет точно string/number/какой-то объект, но точно не null и не undefined, а вот здесь может быть и null" это способ исправить billion dollar mistake infoq.com/presentations/…
Многие современные языки
являются функциональными языками с монадой Maybe (Haskell, Scala, F#, OCaml, ReasonML)
имеют эту фичу изначально (Kotlin)
завозят эту фичу себе (C#, TS)
планируют завезти (Dart)
Про монадки и прочее фп мы поговорим попозже, а пока вернемся к истории
Вообще, если хочется тщательно проследить историю развития ТС, то стоит просто почитать ченджлоги
typescriptlang.org/docs/handbook/…
Я же просто поделюсь своими впечатлениями и мыслями по поводу фич
Извиняюсь за перерыв, работать тоже надо (как ни странно)
Продолжаем
В 2.0 также появилась опция --skipLibCheck. Для тех кто печется о том чтобы не было ошибок при билде (а так делать стоит) можно отключить ошибки в чужих тайпингах
Варианты действий при ошибке компиляции в тайпингах
Идеальный: форкнуть существующие тайпинги, поправить ошибку, сделать PR, пока он не влит, поменять пакет на пропатченный
Нормальный: откатить пакет до версии где нет ошибки
И, наконец, приемлемый: использовать skipLibCheck
Дело просто в том что иногда такие ошибки возникают при апгрейде версии самого ТС, а времени делать целый PR нет
В таких случаях мне кажется использование оправдано
В 2.1 появился исключительно полезный типооператор keyof. На нем строится наверное процентов 80 (люди склонны верить рандомным цифрам, да?) преобразований типов и он дает клевый intellisense.
Скажем есть вас компонент с иконками и object-map для svg
И вы хотите его типизировать, но не просто icon: string, а чтобы можно было только plus и menu
Конечно можно написать ручками
Но можно просто написать keyof typeof svg и он автоматом возьмет все свойства svg!
Самое полезное здесь конечно то что при обновлении svg вам не придется обновлять еще и тип
Подробнее про keyof здесь
typescriptlang.org/docs/handbook/…
Из минусов (на самом деле скорее особенностей): keyof работает только на самом верхнем уровне
То есть вы не сможете описать что-то вроде
const obj = { field1: { field2: { field3: 'str' } } }
_.get(obj, 'field1.field2.field3')
Наверное стоит еще остановиться на typeof раз уж я его упомянул
Дело в том что для ТС при парсинге и компиляции существуют два мира: мир рантаймовых конструкций и мир конструкций типов (который в рантайме не будет)
Так как типов в рантайме нет (об этом мы с вами еще поговорим), то влиять на рантайм как бы нельзя
Но вот с помощью объектов/функций можно спокойно влиять на типы!
И чтобы перейти об объекта/функции/etc. к его типу нужно просто написать
const foo = () => {}
type A = typeof foo
Таким образом
const svg = {
plus: './plus.svg',
menu: './menu.svg
}
type Svg = typeof svg
// { plus: string; menu: string }
type SvgKey = keyof Svg // или keyof typeof Svg
// 'plus' | 'menu'
Лирическое отступление лирического отступления:
Особо пытливые заметили что typeof является правоассоциативным оператором, то есть сначала выполняется typeof Svg и только потом keyof от результата
Это я просто так выделываюсь перед компиляторщиками чтобы они подумали что я свой
Возвращаемся на землю (нет, не плоскую)
Еще в 2.1 появились mapped types. Это вторая основа тайплевела который можно творить на ТС и об этом тоже будем потом (сори)
И наконец опция с путающим названием --alwaysStrict
Она непохожа на остальные опции семейства --strict (strictNullableChecks, strictFunctionTypes etc.) ибо она про то что код парсится в strict-режиме и в каждый скомпилированный файлик кладется "use strict" директива
В 2.3 появилась важная опция --strict
Она включают кучу других опций которые делают ваши типы строже и правильней
typescriptlang.org/docs/handbook/…
Если честно, мне кажется все проекты на ТС должны стремится к тому чтобы компилироваться со strict: true
Следующим экспонатом (мы видимо в историческом музее) версии 2.3 будет --checkJs
Совместно с JSDoc это то на чем строится миграция большинства мало-мальски больших (pun intended) JS проектов на ТС (опять же попозже, вот я разрушитель надежд, да?)
В 2.4 завезли string enums
enum Colors {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
Несмотря на их кажущуюся пользу я считаю их вредными и буду подробнее об этом говорить в секции "Как Я такой-растакой буду указывать как ВЫ должны использовать ТС" (шучу)
В 2.6 добавили страшно желаемую фичу // [@ts](https://twitter.com/ts)-ignore
Если где-то есть ошибка и вы хотите заткнуть компилятор, но использовать as any или что-то подобное не хотите/не помогает, то вот ваш выход
Какие были кейсы для [@ts](https://twitter.com/ts)-ignore у вас?
Дело в чем. Раньше когда вы не знали что будет за тип, был один выход: any
any плох тем что с ним можно делать что угодно и он как рак распространяется по вашему коду
Но что если вы РЕАЛЬНО не знаете что за тип приходит
И вы хотите сначала отмерить, и только потом отрезать
А с unknown вы сначала должны доказать компилятору что он не пустой, в нем есть такое поле и оно такого-то типа и так далее
И только потом использовать
Для того чтобы доказать используются type refinements (они же type guards)
Выглядеть может примерно так
Можете поиграться сами
typescriptlang.org/play/#code/MYe…
Но если вот такие type guards работают не очень, то что делать?
Ответ: user-defined type guards
Поиграться
typescriptlang.org/play/#code/C4T…
Проблемы с ними две
Как видите надо много писать
Имплементация лежит исключительно на вас
То есть можно написать
const isMyType = (obj: unknown): obj is MyType => true
и ТС усом не поведет
Тем не менее само наличие unknown уже заставляет задуматься об избыточном использовании any, что не может не влиять на сознательность решений
То как в реальности стоит делать рантайм проверки обсудим попозже (да, там будет io-ts, но далеко не он один)
@timocov_ Да, я где-то видел как при breaking changes кор контрибьюторы писали codemodes для definitely typed и прогоняли на всем репозитории К тому же есть возможность поставлять разные тайпинги для разных версий ТС
В 3.1 как раз появилась возможность указывать разные версии тайпингов для разных версий компилятора
typescriptlang.org/docs/handbook/…
twitter.com/jsunderhood/st…
В 3.4 появилась наикрутейшая возможность const assertions, например
Раньше когда у вас была функция которая возвращала объектный литерал у вас не было возможности как-то указать что этот литерал планирует быть иммутабельным
Характерный пример это action creators в редаксе
const createLoad = () => ({
type: 'LOAD'
})
type LoadAction = ReturnType<typeof createLoad>
// { type: string }
Ну казалось бы стринг и стринг, что такого то
А такого то, что еще есть такая штука как discriminated unions которая позволяет делать две вещи
1 Различать различные типы по полю (различные экшены по type)
2 Проверять что все возможные экшены обработаны
basarat.gitbook.io/typescript/typ…
И для того чтобы они (discriminated unions) работали надо чтобы type был string literal, а не просто string
То есть надо
type LoadAction = { type: 'LOAD' }
Так вот с const expressions вы можете написать as const и type widening-а не произойдет
type widening это когда в результате некоторых манипуляций тип становиться менее строгим
вот мы возращаем в нашем экшене объектный литерал
{ type: 'LOAD' }
Но ТС справедливо считает что мы можем сделать вот так
const loadAction = createLoadAction()
loadAction.type = 'HECK'
Поэтому ему надо подсказать что литерал будет иммутабельный
const createLoadAction = () => ({
type: 'LOAD'
} as const)
type LoadAction = ReturnType<typeof createLoadAction>
// { type: 'LOAD' }
УРА
as const работает не только на объектных литералах но и на обычных
то есть можно написать
const createLoadAction = () => ({
type: 'LOAD' as const
})
и эффект будет похожим за тем исключением что остальные поля объекта останутся мутабельными
В 3.5 наконец-то добавили хелпер Omit в стандартную поставку
В 3.6 нормально затипизировали генераторы
typescriptlang.org/docs/handbook/…
В 3.7 в срочном порядке добавили поддержку nullish coalescing и optional chaining (причем прямо с 3-его стейджа ЕМНИП, рисковые парни)
И assertion functions
type-guards и custom type-guards у нас уже были, но они возвращали boolean
Соотвественно надо было обкладываться if-ами
Assertion function это история про exception flow control
То есть проверили предусловия контракта и пошли дальше писать код без вложенности
typescriptlang.org/docs/handbook/…
Еще в 3.6 пофиксили некоторые рекурсивные типы
typescriptlang.org/docs/handbook/…
Раньше можно было делать рекурсивные типы когда исходный тип не участвовал в рекурсивном юнионе напрямую
Нихера на словах непонятно
youtube.com/watch?v=LAAhV3…
Короче вот так было можно
type Tree<T> = {
value: T,
children: Tree<T>[]
}
а вот так вот нельзя
type Json =
| string
| number
| boolean
| null
| Record<any, Json>
| Json[]
Начиная с 3.6 можно и вот так ^
Кстати до 3.6 был интересный хак чтобы обходить такое
Дело в том что по стечению обстоятельств type вычисляется eager, а вот interface - lazy
Это позволяло оборвать бесконечную рекурсию вот таким способом:
type Json =
| string
| number
| boolean
| null
| JsonObject
| JsonArray;
interface JsonObject {
[property: string]: Json;
}
interface JsonArray extends Array<Json> {}
If you’re wondering a bit about what the use-case for import type
is used for in @typescript 3.8 - one of it’s main use cases will make @babeljs’ life a lot easier
typescriptlang.org/v2/en/play?ts=…
В 3.8 нас главным образом ожидают очередные новые фичи ECMAScript (например top-level await), но самая мякотка в type-only imports
twitter.com/dskr_dev/statu…
Если интересно чем это хорошо - почитайте вот этот тредик
twitter.com/orta/status/12…
Ну вот и все про историю!
Пойду поработаю, следующий тред будет про то как я вижу идеальный проект на ТС