Hello, world!


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


В первой части мы говорили о возможностях JS, во второй поговорим о возможностях TS.


Это вторая часть.


Вот ссылка на первую часть.


Обратите внимание: названия многих возможностей — это также ссылки на соответствующие разделы документации TypeScript.


Руководства, шпаргалки, вопросы и другие материалы по JavaScript, TypeScript, React, Next.js, Node.js, Express, Prisma, GraphQL, Docker и другим технологиям, а также Блог по веб-разработке.


TypeScript


Основы (контекст для дальнейшего изложения)


Дженерики / Generics: позволяют определять (передавать) параметры типов (type parameters). Это позволяет типам быть одновременно общими и типобезопасными (typesafe). Дженерики следует использовать вместо any или unknown везде, где это возможно.


// Без дженериков:
function getFirstUnsafe(list: any[]): any {
  return list[0];
}

const firstUnsafe = getFirstUnsafe(['test']); // any

// С дженериками:
function getFirst<Type>(list: Type[]): Type {
  return list[0];
}

const first = getFirst<string>(['test']); // string

// В данном случае параметр типа может быть опущен, поскольку тип автоматически выводится (inferred) из аргумента
const firstInferred = getFirst(['test']); // string

// Параметр типа может ограничиваться с помощью ключевого слова `extends`
class List<T extends string | number> {
  private list: T[] = [];

  get(key: number): T {
    return this.list[key];
  }

  push(value: T): void {
    this.list.push(value);
  }
}

const list = new List<string>();
list.push(9);
// TypeError: Argument of type 'number' is not assignable to parameter of type 'string'.
const booleanList = new List<boolean>();
// TypeError: Type 'boolean' does not satisfy the constraint 'string | number'.

До TS4 (возможности, о которых многие не знают)


Утилиты типов / Utility types: позволяют легко создавать типы на основе других типов.


interface Test {
  name: string;
  age: number;
}

// `Partial` делает все свойства опциональными
type TestPartial = Partial<Test>;
// { name?: string | undefined; age?: number | undefined; }

// `Required` делает все свойства обязательными
type TestRequired = Required<TestPartial>;
// { name: string; age: number; }

// `Readonly` делает все свойства доступными только для чтения
type TestReadonly = Readonly<Test>;
// { readonly name: string; readonly age: string }

// `Record` облегчает типизацию объектов. Является более предпочтительным способом, чем использование сигнатур доступа по индексу (index signatures)
const config: Record<string, boolean> = { option: false, anotherOption: true };

// `Pick` извлекает указанные свойства
type TestLess = Pick<Test, 'name'>;
// { name: string; }
type TestBoth = Pick<Test, 'name' | 'age'>;
// { name: string; age: string; }

// `Omit` игнорирует указанные свойства
type TestFewer = Omit<Test, 'name'>;
// { age: string; }
type TestNone = Omit<Test, 'name' | 'age'>;
// {}

// `Parameters` извлекает типы параметров функции
function doSmth(value: string, anotherValue: number): string {
  return 'test';
}
type Params = Parameters<typeof doSmth>;
// [value: string, anotherValue: number]

// `ReturnType` извлекает тип значения, возвращаемого функцией
type Return = ReturnType<typeof doSmth>;
// string

// Существует много других утилит

Условные типы / Conditional types: позволяют определять типы условно на основе совпадения/расширения других типов. Читаются как тернарные операторы в JS.


// Извлекает тип из массива или возвращает переданный тип
type Flatten<T> = T extends any[] ? T[number] : T;

// Извлекает тип элемента
type Str = Flatten<string[]>; //string

// Возвращает сам тип
type Num = Flatten<number>; // number

Вывод типов с помощью условных типов: некоторые дженерики могут быть выведены на основе кода. Для реализации условий на основе выводимых типов используется ключевое слово extends. Оно позволяет определять временные (temporary) типы:


// Перепишем последний пример
type FlattenOld<T> = T extends any[] ? T[number] : T;

// Вместо индексации массива, мы можем просто вывести из него тип `Item`
type Flatten<T> = T extends (infer Item)[] ? Item : T;

// Что если мы хотим написать тип, извлекающий тип, возвращаемый функцией, или `undefined`?
type GetReturnType<Type> = Type extends (...args: any[]) => infer Return ? Return : undefined;

type Num = GetReturnType<() => number>; // number

type Str = GetReturnType<(x: string) => string>; // string

type Bools = GetReturnType<(a: boolean, b: boolean) => void>; // undefined

Необязательные и прочие (rest) элементы кортежа: опциональные элементы кортежа обозначаются с помощью ?, прочие — с помощью ...:.


// Предположим, что длина кортежа может быть от 1 до 3
const list: [number, number?, boolean?] = [];
list[0] // number
list[1] // number | undefined
list[2] // boolean | undefined
list[3] // TypeError: Tuple type '[number, (number | undefined)?, (boolean | undefined)?]' of length '3' has no element at index '3'.

// Кортежи можно создавать на основе других типов
// Оператор `rest` можно использовать, например, для добавления элемента определенного типа в начало массива
function padStart<T extends any[]>(arr: T, pad: string): [string, ...T] {
  return [pad, ...arr];
}

const padded = padStart([1, 2], 'test'); // [string, number, number]

Абстрактные классы / Abstract classes: абстрактные классы и абстрактные методы классов обозначаются с помощью ключевого слова abstract. Такие классы (методы) не могут инстанцироваться напрямую.


abstract class Animal {
  abstract makeSound(): void;

  move(): void {
    console.log('Гуляет...');
  }
}

// Абстрактные методы должны быть реализованы при расширении класса
class Cat extends Animal {}
// CompileError: Non-abstract class 'Cat' does not implement inherited abstract member 'makeSound' from class 'Animal'

class Dog extends Animal {
  makeSound() {
    console.log('Гав!');
  }
}

// Абстрактные классы не могут инстанцироваться (как интерфейсы), а абстрактные методы не могут вызываться напрямую
new Animal();
// CompileError: Cannot create an instance of an abstract class

const dog = new Dog().makeSound(); // Гав!

Сигнатуры конструктора / Construct signatures: позволяют определять типы конструкторов классов за пределами классов. В большинстве случаев вместо сигнатур конструкторов используются абстрактные классы.


interface MyInterface {
  name: string;
}

interface ConstructsMyInterface {
  new(name: string): MyInterface;
}

class Test implements MyInterface {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class AnotherTest {
  age: number;
}

function makeObj(n: ConstructsMyInterface) {
    return new n('hello!');
}

const obj = makeObj(Test); // Test
const anotherObj = makeObj(AnotherTest);
// TypeError: Argument of type 'typeof AnotherTest' is not assignable to parameter of type 'ConstructsMyInterface'.

Утилита типа ConstructorParameters: извлекает типы параметров конструктора класса (но не тип самого класса).


interface MyInterface {
  name: string;
}

interface ConstructsMyInterface {
  new(name: string): MyInterface;
}

class Test implements MyInterface {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

function makeObj(test: ConstructsMyInterface, ...args: ConstructorParameters<ConstructsMyInterface>) {
  return new test(...args);
}

makeObj(Test); // TypeError: Expected 2 arguments, but got 1.
const obj = makeObj(Test, 'test'); // Test

TS4.0


Типы вариативных кортежей / Variadic tuple types: прочие (rest) элементы кортежей могут быть общими (generic). Разрешается использование нескольких прочих элементов.


// Что если нам нужна функция, комбинирующая 2 кортежа неизвестной длины?
// Как определить возвращаемый тип?

// Раньше:
// Приходилось писать перегрузки (overloads)
declare function concat(arr1: [], arr2: []): [];
declare function concat<A>(arr1: [A], arr2: []): [A];
declare function concat<A, B>(arr1: [A], arr2: [B]): [A, B];
declare function concat<A, B, C>(arr1: [A], arr2: [B, C]): [A, B, C];
declare function concat<A, B, C, D>(arr1: [A], arr2: [B, C, D]): [A, B, C, D];
declare function concat<A, B>(arr1: [A, B], arr2: []): [A, B];
declare function concat<A, B, C>(arr1: [A, B], arr2: [C]): [A, B, C];
declare function concat<A, B, C, D>(arr1: [A, B], arr2: [C, D]): [A, B, C, D];
declare function concat<A, B, C, D, E>(arr1: [A, B], arr2: [C, D, E]): [A, B, C, D, E];
declare function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];
declare function concat<A, B, C, D>(arr1: [A, B, C], arr2: [D]): [A, B, C, D];
declare function concat<A, B, C, D, E>(arr1: [A, B, C], arr2: [D, E]): [A, B, C, D, E];
declare function concat<A, B, C, D, E, F>(arr1: [A, B, C], arr2: [D, E, F]): [A, B, C, D, E, F];
// Согласитесь, что выглядит это не очень хорошо

// Также можно было комбинировать типы
declare function concatBetter<T, U>(arr1: T[], arr2: U[]): (T | U)[];
// Но это приводило к типу (T | U)[]

// Сейчас:
// Тип вариативного кортежа позволяет легко комбинировать типы с сохранением информации о длине кортежа
declare function concatNew<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U];

const tuple = concatNew([23, 'hey', false] as [number, string, boolean], [5, 99, 20] as [number, number, number]);
console.log(tuple[0]); // 23
const element: number = tuple[1];
// TypeError: Type 'string' is not assignable to type 'number'.
console.log(tuple[6]);
// TypeError: Tuple type '[23, "hey", false, 5, 99, 20]' of length '6' has no element at index '6'.

Помеченные элементы кортежа / Labeled tuple elements: элементы кортежа могут быть именованными, например [start: number, end: number]. Если один элемент является именованным, то остальные элементы также должны быть именованными.


type Foo = [first: number, second?: string, ...rest: any[]];

declare function someFunc(...args: Foo);

Вывод типа свойства класса из конструктора: при установке свойства в конструкторе тип свойства выводится автоматически.


class Animal {
  // Раньше тип объявляемого свойства должен быть определяться вручную
  name;

  constructor(name: string) {
    this.name = name;
    console.log(this.name); // string
  }
}

Поддержка тега deprecated JSDoc:


/** @deprecated message */
type Test = string;

const test: Test = 'dfadsf'; // TypeError: 'Test' is deprecated.

TS4.1


Типы шаблонных литералов / Template literal types: позволяют определять сложные строковые типы, например, путем комбинации нескольких строковых литералов.


type VerticalDirection = 'top' | 'bottom';
type HorizontalDirection = 'left' | 'right';
type Direction = `${VerticalDirection} ${HorizontalDirection}`;

const dir1: Direction = 'top left';
const dir2: Direction = 'left';
// TypeError: Type '"left"' is not assignable to type '"top left" | "top right" | "bottom left" | "bottom right"'.
const dir3: Direction = 'left top';
// TypeError: Type '"left top"' is not assignable to type '"top left" | "top right" | "bottom left" | "bottom right"'.

// Комбинироваться также могут дженерики и утилиты типов
declare function makeId<T extends string, U extends string>(first: T, second: U): `${Capitalize<T>}-${Lowercase<U>}`;

// Предположим, что мы хотим, чтобы ключи объекта начинались с нижнего подчеркивания
const obj = { value1: 0, value2: 1, value3: 3 };
const newObj: { [Property in keyof typeof obj as `_${Property}`]: number };
// { _value1: number; _value2: number; _value3: number; }

Рекурсивные условные типы: условные типы можно использовать внутри их определений. Это позволяет распаковывать типы бесконечно вложенных значений.


type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;

type P1 = Awaited<string>; // string
type P2 = Awaited<Promise<string>>; // string
type P3 = Awaited<Promise<Promise<string>>>; // string

Поддержка тега see JSDoc:


const originalValue = 1;
/**
  * Копия другого значения
  * @see originalValue
  */
const value = originalValue;

explainFiles: при использовании флага CLI --explainFiles или установке одноименной настройки в файле tsconfig.json, TS сообщает, какие файлы и почему компилируются. Может быть полезным для отладки. Обратите внимание: для уменьшения вывода (output) в больших и сложных проектах можно, например, использовать команду tsc --explainFiles | less.


Явное определение неиспользуемых переменных: при деструктуризации неиспользуемые переменные могут быть помечены с помощью нижнего подчеркивания. Это предотвращает соответствующую ошибку.


const [_first, second] = [3, 5];
console.log(second);

// или даже короче
const [_, value] = [3, 5];
console.log(value);

TS4.3


Разделение типов аксессоров: при определении аксессоров get/set тип записи/set может быть отделен от типа чтения/get. Это позволяет сеттерам принимать значения разных типов.


class Test {
  private _value: number;

  get value(): number {
    return this._value;
  }

  set value(value: number | string) {
    if (typeof value === 'number') {
      this._value = value;
      return;
    }
    this._value = parseInt(value, 10);
  }
}

override: индикатор перезаписи наследуемого класса. Используется для обеспечения типобезопасности в сложных паттернах наследования. Вместо ключевого слова override можно использовать одноименный декоратор.


class Parent {
  getName(): string {
    return 'name';
  }
}

class NewParent {
  getFirstName(): string {
    return 'name';
  }
}

class Test extends Parent {
  override getName(): string {
    return 'test';
  }
}

class NewTest extends NewParent {
  override getName(): string { // TypeError: This member cannot have an 'override' modifier because it is not declared in the base class 'NewParent'.
    return 'test';
  }
}

Статические сигнатуры доступа по индексу / Static index signatures:


// Раньше:
class Test {}

Test.test = '';
// TypeError: Property 'test' does not exist on type 'typeof Test'.

// Сейчас:
class NewTest {
  static [key: string]: string;
}

NewTest.test = '';

Поддержка тега link JSDoc:


const originalValue = 1;
/**
  * Копия {@link originalValue}
  */
const value = originalValue;

TS4.4


exactOptionalPropertyTypes: использование флага CLI --exactOptionalPropertyTypes или установка одноименной настройки в файле tsconfig.json запрещает неявную неопределенность поля — вместо property?: string следует использовать property: string | undefined.


class Test {
  name?: string;
  age: number | undefined;
}

const test = new Test();
test.name = undefined;
// TypeError: Type 'undefined' is not assignable to type 'string' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the type of the target.
test.age = undefined;
console.log(test.age); // undefined

TS4.5


Утилита типа Awaited: извлекает тип значения бесконечно вложенных промисов. Это также улучшает вывод типов для Promise.all().


type P1 = Awaited<string>; // string
type P2 = Awaited<Promise<string>>; // string
type P3 = Awaited<Promise<Promise<string>>>; // string

Модификатор type в именованном импорте: индикатор того, что значение требуется только для проверки типов и может быть удалено при компиляции.


// Раньше:
// Импорт значений и типов приходилось разделять во избежание импорта типов после компиляции
import { something } from './file';
import type { SomeType } from './file';

// Сейчас:
// Значения и типы могут импортироваться с помощью одной инструкции
import { something, type SomeType } from './file';

Утверждения const / const assertions: позволяют корректно типизировать константы как литеральные типы. Это может использоваться во многих случаях и существенно повышает точность типизации. Это также делает объекты и массивы readonly, что предотвращает их мутации.


// Раньше:
const obj = { name: 'foo', value: 9, toggle: false };
// { name: string; value: number; toggle: boolean; }
// Полю может присваиваться любое значение соответствующего типа
obj.name = 'bar';

const tuple = ['name', 4, true]; // (string | number | boolean)[]
// Длина кортежа и тип каждого элемента неизвестны
// Могут присваиваться любые значения соответствующих типов
tuple[0] = 0;
tuple[3] = 0;

// Сейчас:
const objNew = { name: 'foo', value: 9, toggle: false } as const;
// { readonly name: "foo"; readonly value: 9; readonly toggle: false; }
// Значения полей доступны только для чтения (не могут модифицироваться)
objNew.name = 'bar';
// TypeError: Cannot assign to 'name' because it is a read-only property.

const tupleNew = ['name', 4, true] as const; // readonly ["name", 4, true]
// Длина кортежа и тип каждого элемента теперь известны
tupleNew[0] = 0;
// TypeError: Cannot assign to '0' because it is a read-only property.
tupleNew[3] = 0;
// TypeError: Index signature in type 'readonly ["name", 4, true]' only permits reading.

Автозавершение методов классов:





TS4.6


Улучшение вывода типов при доступе по индексу: более точный вывод типов при доступе по ключу в рамках одного объекта.


interface AllowedTypes {
  'number': number;
  'string': string;
  'boolean': boolean;
}

//  `UnionRecord` определяет типы значений полей с помощью `AllowedTypes`
type UnionRecord<AllowedKeys extends keyof AllowedTypes> = { [Key in AllowedKeys]:
{
  kind: Key;
  value: AllowedTypes[Key];
  logValue: (value: AllowedTypes[Key]) => void;
}
}[AllowedKeys];

// `logValue` принимает только значения типа `UnionRecord`
function processRecord<Key extends keyof AllowedTypes>(record: UnionRecord<Key>) {
  record.logValue(record.value);
}

processRecord({
  kind: 'string',
  value: 'hello!',
  // `value` может иметь тип `string | number | boolean`,
  // но в данном случае правильно выводится тип `string`
  logValue: value => {
    console.log(value.toUpperCase());
  }
});

Флаг CLI --generateTrace: указывает TS генерировать файл, содержащий подробности проверки типов и процесса компиляции. Может быть полезным для оптимизации сложных типов.


TS4.7


Поддержка модулей ES в Node.js: для типобезопасного использования модулей ES вместо модулей CommonJS предназначена следующая настройка, устанавливаемая в файле tsconfig.json:


{
  "compilerOptions": {
    "module": "es2020"
  }
}

Поле type файла package.json: вместо указанной выше настройки можно определить следующее поле в файле package.json:


"type": "module"

Выражения инстанцирования / Instantiation expressions: позволяют определять параметры типов при ссылке на значения. Это позволяет конкретизировать (narrow) общие типы без создания оберток.


class List<T> {
  private list: T[] = [];

  get(key: number): T {
    return this.list[key];
  }

  push(value: T): void {
    this.list.push(value);
  }
}

function makeList<T>(items: T[]): List<T> {
  const list = new List<T>();
  items.forEach(item => list.push(item));
  return list;
}

// Предположим, что мы хотим определить функцию, создающую список
// элементов определенного типа

// Раньше:
// Требовалось создавать функцию-обертку и передавать ей аргумент с указанием типа
function makeStringList(text: string[]) {
  return makeList(text);
}

// Сейчас:
// Можно использовать выражение инстанцирования
const makeNumberList = makeList<number>;

extends и infer: при выводе переменных типов в условных типах, они могут конкретизироваться/ограничиваться с помощью ключевого слова extends.


// Предположим, что мы хотим извлекать тип первого элемента массива только в случае,
// если такой элемент является строкой
// Для этого можно применить условные типы

// Раньше:
type FirstIfStringOld<T> =
  T extends [infer S, ...unknown[]]
    ? S extends string ? S : never
    : never;

// Вместо 2 вложенных условных типов можно использовать 1
type FirstIfString<T> =
  T extends [string, ...unknown[]]
    // Извлекаем первый тип из типа `T`
    ? T[0]
    : never;
// Но код все равно выглядит не очень хорошо

// Сейчас:
type FirstIfStringNew<T> =
  T extends [infer S extends string, ...unknown[]]
    ? S
    : never;
// Обратите внимание: типизация работает как раньше, но код стал чище

type A = FirstIfStringNew<[string, number, number]>; // string
type B = FirstIfStringNew<["hello", number, number]>; // "hello"
type C = FirstIfStringNew<["hello" | "world", boolean]>; // "hello" | "world"
type D = FirstIfStringNew<[boolean, number, string]>; // never

Опциональные аннотации вариативности для параметров типов: дженерики могут вести себя по-разному при проверке на совпадение (match), например, разрешение наследования выполняется в обратном порядке для геттеров и сеттеров. Это может быть определено в явном виде для ясности.


// Предположим, что у нас имеется интерфейс, расширяющий другой интерфейс
interface Animal {
  animalStuff: any;
}

interface Dog extends Animal {
  dogStuff: any;
}

// А также общий "геттер" и "сеттер".
type Getter<T> = () => T;

type Setter<T> = (value: T) => void;

// Если мы хотим выяснить, совпадают ли Getter<T1> и Getter<T2> или Setter<T1> и Setter<T2>,
// нам следует учитывать ковариантность (covariance)
function useAnimalGetter(getter: Getter<Animal>) {
  getter();
}

// Теперь мы можем передать `Getter` в функцию
useAnimalGetter((() => ({ animalStuff: 0 }) as Animal));
// Это работает

// Что если мы хотим использовать `Getter`, возвращающий `Dog`?
useAnimalGetter((() => ({ animalStuff: 0, dogStuff: 0 }) as Dog));
// Это работает, поскольку `Dog` - это также `Animal`

function useDogGetter(getter: Getter<Dog>) {
  getter();
}

// Если мы попытаемся сделать тоже самое для функции `useDogGetter`,
// то получим другое поведение
useDogGetter((() => ({ animalStuff: 0 }) as Animal));
// TypeError: Property 'dogStuff' is missing in type 'Animal' but required in type 'Dog'.
// Это не работает, поскольку ожидается `Dog`, а не просто `Animal`

useDogGetter((() => ({ animalStuff: 0, dogStuff: 0 }) as Dog));
// Однако, это работает

// Можно предположить, что сеттеры работает как геттеры, но это не так
function setAnimalSetter(setter: Setter<Animal>, value: Animal) {
  setter(value);
}

// Если мы передадим `Setter` такого же типа, все будет хорошо
setAnimalSetter((value: Animal) => {}, { animalStuff: 0 });

function setDogSetter(setter: Setter<Dog>, value: Dog) {
  setter(value);
}

// И здесь
setDogSetter((value: Dog) => {}, { animalStuff: 0, dogStuff: 0 });

// Но если мы передадим `Dog Setter` в функцию `setAnimalSetter`,
// поведение будет противоположным (reversed) `Getter`
setAnimalSetter((value: Dog) => {}, { animalStuff: 0, dogStuff: 0 });
// TypeError: Argument of type '(value: Dog) => void' is not assignable to parameter of type 'Setter<Animal>'.

// Обходной маневр выглядит несколько иначе
setDogSetter((value: Animal) => {}, { animalStuff: 0, dogStuff: 0 });

// Сейчас:
// Не является обязательным, но повышает читаемость кода
type GetterNew<out T> = () => T;
type SetterNew<in T> = (value: T) => void;

Кастомизация разрешения модулей: настройка moduleSuffixes позволяет указывать кастомные суффиксы файлов (например, .ios) при работе в специфических окружениях для правильного разрешения импортов.


{
  "compilerOptions": {
    "moduleSuffixes": [".ios", ".native", ""]
  }
}

import * as foo from './foo';
// Сначала проверяется ./foo.ios.ts, затем ./foo.native.ts и, наконец, ./foo.ts

Переход к определению источника / Go to source definition: новый пункт меню в редакторе кода. Он похож на "Перейти к определению" (Go to definition), но "предпочитает" файлы .ts и .js вместо определений типов (.d.ts).


TS4.9


Оператор satisfies: позволяет проверять совместимость значения с типом без присвоения типа. Это делает вывод типов более точным при сохранении совместимости.


// Раньше:
// Предположим, что у нас есть объект, в котором хранятся разные элементы и их цвета
const obj = {
  fireTruck: [255, 0, 0],
  bush: '#00ff00',
  ocean: [0, 0, 255]
} // { fireTruck: number[]; bush: string; ocean: number[]; }

const rgb1 = obj.fireTruck[0]; // number
const hex = obj.bush; // string

// Допустим, мы хотим ограничить типы значений объекта
// Для этого можно применить утилиту типа `Record`
const oldObj: Record<string, [number, number, number] | string> = {
  fireTruck: [255, 0, 0],
  bush: '#00ff00',
  ocean: [0, 0, 255]
} // Record<string, [number, number, number] | string>
// Но это приводит к потере типизации свойств
const oldRgb1 = oldObj.fireTruck[0]; // string | number
const oldHex = oldObj.bush; // string | number

// Сейчас:
// Оператор `satisfies` позволяет проверять совместимость значения с типом без присвоения типа
const newObj = {
  fireTruck: [255, 0, 0],
  bush: '#00ff00',
  ocean: [0, 0, 255]
} satisfies Record<string, [number, number, number] | string>
// { fireTruck: [number, number, number]; bush: string; ocean: [number, number, number]; }

// Типизация свойств сохраняется
// Более того, массив становится кортежем
const newRgb1 = newObj.fireTruck[0]; // number
const newRgb4 = newObj.fireTruck[3];
// TypeError: Tuple type '[number, number, number]' of length '3' has no element at index '3'.
const newHex = newObj.bush; // string

Новые команды: в редакторе кода появились команды "Удалить неиспользуемые импорты" (Remove unused imports) и "Сортировать импорты" (Sort imports), облегчающие управления импортами.


На этом перевод второй части, посвященной возможностям TS, завершен.


С возможностями TS5, можно ознакомиться здесь.


Надеюсь, вы узнали что-то новое и не зря потратили время.


Happy coding!




Комментарии (0)