beta

4.1

Изменение механизма проверки индексной сигнатуры

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

ts
type T = { [key: string]: number | string; } function f(p: T) { /** * Обращение к несуществующим полям */ p.bad.toString(); // Ok -> Ошибка времени исполнения p[Math.random()].toString(); // Ok -> Ошибка времени исполнения }

Для решения данной проблемы был создан механизм активируемый с помощью нового флага --noUncheckedIndexedAccess ожидающий в качестве значения true либо false. Активация механизма позволяет обращаться к динамическим полям только после подтверждения их наличия в объекте, а также совместно при совместном использовании с такими операторами, как оператор опциональной последовательности ?. и опциональный оператор !..

json
// @filename: tsconfig.json { "compilerOptions": { "noUncheckedIndexedAccess": true } }
ts
type T = { [key: string]: number | string; } function f(p: T) { /** * Обращение к несуществующим полям */ p.bad.toString(); // Error -> Object is possibly 'undefined'.ts(2532) p[Math.random()].toString(); // Error -> Object is possibly 'undefined'.ts(2532) // Проверка наличия поля bad if("bad" in p){ p.bad?.toString(); // Ok } // Использование опционального оператора p[Math.random()]!.toString(); // Ok -> ошибка во время выполнения p[Math.random()]?.toString(); // Ok -> Ошибка не возникнет }

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

ts
function f(array: string[]) { for(let i = 0; i < array.length; i++){ array[i].toString(); // Error -> Object is possibly 'undefined'. } }

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

Шаблонный литеральный строковый тип

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

ts
function setAnimation(animationType: "ease" | "ease-in" | "ease-out"){ // ... какая-то логика } setAnimation("ease"); // Error -> Argument of type '"ease"' is not assignable to parameter of type '"ease" | "ease-in" | "ease-out"'.

...а также используются при определении новых типов выступают в качестве ключей сопоставленных типов.

ts
type Animation = { [K in "ease" | "ease-in" | "ease-out"]?: boolean; }

И вот, начиная с версии v4.1 они нашли новое применение в удивительном механизме получившем название Шаблонный литеральный строковый тип.

Шаблонный литеральный строковый тип — это тип, позволяющий на основе литеральных строковых типах динамически определять новый литеральный строковый тип. Простыми словами, это известный по JavaScript механизм создания шаблонных строк только для типов.

ts
type Type = "Type"; type Script = "Script"; /** * type Message = "I ❤️ TypeScript" */ type Message = `I ❤️ ${Type}${Script}`;

Но вся мощь данного типа раскрывается в момент определение нового типа на основе объединения (union). В подобных случаях новый тип будет также представлять объединение элементы которого представляют все возможные варианты полученные на основе исходного объединения.

ts
type Sides = "top" | "right" | "bottom" | "left"; /** * type PaddingSides = "padding-top" | "padding-right" | "padding-bottom" | "padding-left" */ type PaddingSides = `padding-${Sides}`;

Аналогичное поведение будет справедливо и для нескольких типов объединения.

ts
type AxisX = "top" | "bottom"; type AxisY = "left" | "right"; /** * type Sides = "top-left" | "top-right" | "bottom-left" | "bottom-right" */ type Sides = `${AxisX}-${AxisY}`; /** * type BorderRadius = "border-top-left-radius" | "border-top-right-radius" | "border-bottom-left-radius" | "border-bottom-right-radius" */ type BorderRadius = `border-${Sides}-radius`;

Поскольку с высокой долей вероятности в подобных операциях потребуется трансформация регистра строк, создателями данного механизма так же были добавлены новые операторы преобразования uppercase, lowercase, capitalize и uncapitalize. Данные операторы применяются непосредственно к литеральному строковому типу который указывается справа от него.

ts
type A = `${uppercase "AbCd"}`; // type A = "ABCD" type B = `${lowercase "AbCd"}`; // type B = "abcd" type C = `${capitalize "abcd"}`; // type C = "Abcd" type D = `${uncapitalize "Abcd"}`; // type D = "abcd"

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

Переопределение ключей в сопоставленных типах

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

ts
type T = { [K in STRING_VALUES as NEW_KEY]: K // K преобразованный }

Таким образом совмещая данный механизм с шаблонными литеральными строковыми типами можно добиться переопределения исходных ключей.

ts
type ToGetter<T> = `get${capitalize T}`; type Getters<T> = { [K in keyof T as ToGetter<K>]: () => T[K]; } type Person = { name: string; age: number; } /** * type T = { * getName: () => string; * getAge: () => number; * } */ type T = Getters<Person>

Рекурсивные условные типы

При разработке программ часто возникают потребности в создании значений при помощи рекурсии в основе которой лежит логическое условие.

ts
function flat(value){ if(Array.isArray(value)){ // логическое условие return value.reduce((result, current) => [ ...result, ...flat(current) ], []); } return [value]; } flat([0, [1, [2]], 3]); // [0, 1, 2, 3]

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

ts
type GetItemType<T> = T extends ReadonlyArray<infer U> ? GetItemType<U> : T; declare function flat<T extends readonly unknown[]>(value: T): GetItemType<T>[]; let result = flat([0, [1, [2]], 3]); // let result: number[] = [0, 1, 2, 3]

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

paths без baseUrl

Ранее указание псевдонима для пути с помощью paths требовало также установление значения параметру baseUrl. Это не позволяло автоимпорту указывать правильные пути. Поэтому начиная с текущей версии paths больше не зависит от параметра baseUrl.

checkJs не требует активации allowJs

Раньше, что бы активировать проверку JavaScript кода с помощью параметра checkJs было необходимо также активировать флаг allowJs. Поскольку указание выполнять проверку JavaScript кода де-факто подразумевает его наличие на конвейере TypeScript, данный факт раздражал многих разработчиков. Поэтому начиная с текущей версии активация параметра checkJs больше не требует активации allowJs.

jsx фабрики для React 17

Текущая версия TypeScript получила поддержку будущих jsx и jsxs фабрик предполагаемых React 17. Для этого были реализованны две новые опции react-jsx и react-jsxdev.

При разделении конфигурации на production и development конфигурация проекта могла бы выглядеть следующим образом.

json
// tsconfig.json { "compilerOptions": { "module": "esnext", "target": "es2015", "jsx": "react-jsx", "strict": true }, "include": [ "./**/*" ] }
json
// tsconfig.dev.json { "extends": "./tsconfig.json", "compilerOptions": { "jsx": "react-jsxdev" } }

Поддержка тега @see для JSDoc

Теперь JSDoc поддерживает тег @see упрощающий работу с кодом за счет возможности перехода к определению (go-to-definition).

ts
// @filename: animals.ts export class Animal { } export class Fish { } export class Bird { } // @filename: index.js import * as animals from './animals'; /** * @see animals.Bird */ function related() { }

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] abstract больше не совместим с async

Начиная с текущей версии абстрактные методы не могут помечаться ключевым словом async.

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] any и unknown доминируют в ложных позициях

Если в условии с логическим И (&&) значение левого операнда принадлежало к типу any или unknown, то вывод типа выводил тип правого операнда. Начиная с текущей версии в подобных условиях всегда будут выводиться any или unknown.

ts
declare let a: any; declare let n: number; declare let u: unknown; /** * Вывод типов видит так -> * <v4.1 | >=v4.1 * let v0: number any * let v1: number unknown * let v2: any any * let v3: unknown unknown */ let v0 = a && n; let v1 = u && n; let v2 = n && a; let v3 = n && u;

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] --declaration и --outFile требуют имя корневого пакета

До текущего момента при совместном использовании --declaration и --outFile пути в .d.ts файлах выглядели привычным образом.

ts
/** * До компиляции */ // @filename: ./utils.ts export const toLowerCase = (text: string) => text.toLowerCase(); // @filename: ./index.js export * from "./utils";
ts
/** * После компиляции .d.ts */ declare module "utils" { export const toLowerCase: (text: string) => string; } declare module "index" { export * from "utils"; }

Начиная с текущей версии при совместном использовании параметров --declaration и --outFile необходимо задавать значение (имя пакета) параметру bundledPackageName. В противном случае возникнет ошибка - The bundledPackageName option must be provided when using outFile and node module resolution with declaration emit..

json
{ "compilerOptions": { "module": "amd", "target": "esnext", "jsx": "preserve", "sourceMap": true, "declaration": true, "outFile": "./dest/my-lib.js", "bundledPackageName": "my-lib" }, "include": ["./src/"], "exclude": [ "node_modules", "**/node_modules/*" ] }
ts
/** * После компиляции .d.ts */ declare module "my-lib/utils" { export const toLowerCase: (text: string) => string; } declare module "my-lib/index" { export * from "utils"; }

[КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ] параметры resolve теперь обязательные

До текущей версии функцию resolve, участвующей в работе логики Promise, можно было вызывать без аргументов, поскольку её параметры описаны, как необязательные.

ts
new Promise(resolve => { resolve(); // Ok });

Начиная с текущей версии описание функции resolve изменило поведение для её параметров сделав их обязательными. Теперь при отсутствии параметров будет возникать ошибка.

ts
new Promise(resolve => { resolve(); // Error -> Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'? });

Как следует из ошибки, при сценариях подразумевающих вызов функции resolve без аргументов, Promise в качестве аргумента типа необходимо установить тип void.

ts
new Promise<void>(resolve => { resolve(); // Ok });