> For the complete documentation index, see [llms.txt](https://race-timo.gitbook.io/typescript/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://race-timo.gitbook.io/typescript/other-features.md).

# Конструкции языка

## Приведение типов

До этого момента мы ни разу не говорили о том, как изменять тип значения. В TypeScript есть несколько способов изменения типа. Один из них - это приведение типа или каст (cast). Каст изменяет тип значения на тот, который был указан. Существует два синтаксиса для каста - с использованием `<>` и `as`.

```typescript
const a: number = 10;

//Без cast-а
const b1: 10 = a; // Error: Type 'number' is not assignable to type '10'.

//cast через <>
const b2: 10 = <10>a;

//cast через as
const b3: 10 = a as 10;
```

Каст через `<>` и через `as` - это эквивалентные действия. Однако каст через `<>` не будет работать в `.tsx` файлах (это как `.jsx` файлы, только на TypeScript), потому что такой каст будет интерпретироваться как компонент React. Поэтому предпочтительней использовать `as` каст.

По своей сути каст - это ручное преобразование одного типа в другой. Это задача возникает например тогда, когда нужно более общий тип преобразовать к более конкретному: из `number` получить числовой литерал, от базового класса перейти к конкретному наследнику и так далее.

```typescript
abstract class Base {
    public a: number = 10;
}

class A extends Base {
    public b: number = 15;
}

function some(b: Base) {
    const a: A = b as A;
}

some(new A());
```

Каст может создавать проблемы. TypeScript следит за тем, чтобы преобразование одного типа осуществлялось в совместимый тип. Например, нельзя преобразовать `number` в `string`.

```typescript
const a = 10;
const b = a as string; //Error: Conversion of type 'number' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
```

Однако это ограничение можно обойти, сделав сначала каст до `any`, а потом до нужного типа.

```typescript
const a = 10;
const b = a as any as string;
```

Иногда преобразование в несовместимый тип приходится делать. Но кастом через `any` не стоит злоупотреблять. Использование подобной конструкции может вызвать проблемы в runtime. После приведения типов, TypeScript использует для компиляции приведенный тип. Однако значение, тип которого был изменен (особенно через `as any as`) может быть не совместимо с операциями, которые можно делать с приведенным типом. Например, не иметь каких то методов. В таком случаи ошибка возникнет в runtime, а не на этапе компиляции.

```typescript
const a = 15
const b = a as any as string;
b.slice(1); //Runtime Error: Uncaught TypeError: b.slice is not a function
```

Для того, чтобы сломать runtime при помощи каста, необязательно преобразовывать несовместимые типы. Можно выполнять преобразование совместимых типов и все равно получить ошибку в runtime.

```typescript
function getValue(index: 0 | 1) {
    const tupple: [number, number] = [1, 1];
    return tupple[index];
}

const a = 15;
getValue(a as 0 | 1).toString() //Runtime Error: Uncaught TypeError: Cannot read property 'toString' of undefined
```

## Type guards

В TypeScript есть другие способы преобразования типов. Один из таких способов - это использование type guard-ов. Рассмотрим пример:

```typescript
interface Keyboard {
    pressKey: (key: number) => void;
}

interface Mouse {
    click: (button: "left" | "right") => void;
}

function makeAction(pointer: Mouse | Keyboard) {
    ...
}
```

В коде выше объявлено интерфейсы клавиатуры и мыши. Также объявлена функция, которая принимает объект совместимый с типом клавиатуры или типом мыши. Далее нужно написать код, который проверит, какой объект был передан в функцию и вызовет соответствующий метод.

```typescript
function makeAction(pointer: Mouse | Keyboard) {
    if ("click" in pointer) {
        pointer.click("left")
    } else {
        pointer.pressKey(85)
    }
}
```

Выражение `"click" in pointer` является type guard-ом. Оно в runtime проверяет, что поле `"click"` содержится внутри объекта `pointer`. Если это выражение возвращает `true`, то внутри блока `if` гарантируется, что `"click"` в объекте присутствует, а значит и с точки зрения типов у `pointer` должно быть поле `click`. Гарантируется также, что в блоке `else` у объекта `pointer` нет поля `click`.

На самом деле в коде выше описан особый случай для type guard-а `in`. `pointer` имеет тип `Mouse | Keyboard`, а оба интерфейса имеет разные названия полей. Соответственно, внутри блока `if`, TypeScript не просто убедился, что у `pointer` есть `click`. Он также определил, что `click` есть только у `Mouse`, а значит значения типа `Keyboard` не могут попасть в блок `if`, поэтому `pointer` внутри блока `if` имеет тип `Mouse`. Верно и обратное утверждение. Если `"click" in pointer` возвращает `false`, то в блоке `else` объект `pointer` не может иметь тип `Mouse`, потому что для этого у него должно быть поле `click`. Поэтому в блоке `else` у `pointer` тип `Keyboard`.

Операторы `===` и `!==` в TypeScript тоже являются type guard-ами.

```typescript
function foo(a: number) {
    if (a === 1) {
        a //type: 1
    }
    if (a === 1 || a === 2) {
        a //type: 1 | 2
    }
    a //type: number
}
```

При помощи `===` можно определять одно значение из множества других значений, а также исключать это значения из множества при помощи `!==` или блока `else`.

```typescript
function foo(a: "lalaka" | "malaka" | "palaka") {
    if (a === "lalaka") {
        a //type: "lalaka"
    } else {
        a //type: "malaka" | "palaka"
        if (a !== "palaka") {
            a //type: "malaka"
        } else {
            a //type: "palaka"
        }
    }
}
```

При помощи `===`/`!==` и оператора `typeof` можно определять тип значения, если изначально тип этого значения состоял из объединения примитивов.

```typescript
function foo(a: string | number | boolean) {
    if (typeof a === "string") {
        a //type: string
    } else {
        a //type: number | boolean
    }
}
```

Оператор `instanceof` также является type guard-ом.

```typescript
class A {
}

class B {
    a: number = 10;
}

function foo(a: A) {
    if (a instanceof B) {
        const q = a.a;
    }
}
```

В TypeScript можно определить функции, которые являются tpye guard-ами. Для этого используется синтаксическая конструкция `{paramter} is {type}`

```typescript
function isNumber(a: any): a is number {
    return typeof a === "number"
}

function foo(a: string | number) {
    if (isNumber(a)) {
        a //type: number
    }
}
```

В коде выше объявлена функция `isNumber` возвращаемое значение которой `a is number`. По сути это `boolean`, но здесь также указано, что если возвращаемое значение `true`, то значение в параметра `a` - это `number`. Таким образом, функция `isNumber` становится type guard-ом.

В TypeScript уже есть встроенные функции type guard-ы, например `Array.isArray`.

## Pattern Matching

Pattern Matching еще один подход, который позволяет преобразовать один тип во второй. Допустим, есть такой код:

```typescript
interface A {
    kind: "a",
    do: (a: string) => boolean;
}

interface B {
    kind: "b",
    do: (b: number) => boolean;
}

function foo(q: A | B): boolean {
    q.kind //type: "a" | "b"
}
```

Тип поля `q.kind` TypeScript выводит как `"a" | "b"`. Можно воспользоваться оператором `===`, для того, чтобы преобразовать тип `A | B` в `A` или `B`.

```typescript
function foo(q: A | B): boolean {
    if (q.kind === "a") {
        return q.do("lalaka");
    } else {
        return q.do(15);
    }
}
```

В такой ситуации можно воспользоваться оператором `switch` по полю `q.kind`:

```typescript
function foo(q: A | B): boolean {
    switch (q.kind) {
        case "a":
            return q.do("lalaka");
        case "b":
            return q.do(15);
    }
}
```

В этом случае TypeScript также выводит тип внутри блоков `case`. Этот подход называется pattern matching.

## Enum

Enum (enumeration - перечисление) - это еще одна синтаксическая конструкция TypeScript, которая пригождается в реальных задачах. Она не связан с преобразованием типов. Ее использования чаще всего связаны с взаимодействием с сервером.

Во многих языках программирования, на которых разрабатываются сервера, есть возможность объявить специальную структуру данных, которая хранит в себе именованный набор констант. Например, если мы разрабатываем какое-нибудь консольное приложение для отрисовки 2D графики в черно-белом формате, то для определения цвета можно использовать булевой флаг `isBlack`. Но когда цветов становится больше чем 2, использовать булевой флаг не получится. Можно использовать числовые значения от 0 до n. Пример сигнатуры функции, которая рисует пиксель по заданным координатам, на TypeScript может выглядеть так:

```typescript
function printPixel(x: number, y: number, color: number) {
}
```

У такого подхода есть несколько проблема. Например, если поддерживается всего 4 цвета: 0, 1, 2 и 3, то в данном случаи типизация позволяет передавать любое число в качестве параметра `color`. Это может привести к проблемам в runtime. Конкретно в TypeScript для решения этой проблемы можно использовать специальный тип - объединение числовых литералов `0 | 1 | 2 | 3`. Но очень многие языки программирования не поддерживают литеральные типы. Более того, при таком подходе становится сложно читать код:

```typescript
printPixel(0, 0, 0);
printPixel(0, 1, 1);
printPixel(1, 0, 1);
printPixel(1, 1, 2);
```

В качестве цвета можно использовать его название в виде строки. Но тогда не решается проблема с передачей не валидных данных.

В разных языках программирования (в частности в TypeScript) есть enum-ы или подобные структуры данных. В TypeScript он выглядит так:

```typescript
enum Color {
    Black,
    White,
    Red,
    Green
}

const color = Color.Black;
```

Enum в TypeScript - это объект, ключами которого являются строчные значения, указанные в enum-е, а значениями - числа или строки. Для кода выше TypeScript при компиляции сгенерирует следующий JavaScript:

```javascript
var Color;
(function (Color) {
    Color[Color["Black"] = 0] = "Black";
    Color[Color["White"] = 1] = "White";
    Color[Color["Red"] = 2] = "Red";
    Color[Color["Green"] = 3] = "Green";
})(Color || (Color = {}));
const color = Color.Black;
```

Если ключам enum-а явно не передавать значения, то им присвоятся значения начиная с 0 сверху вниз увеличиваясь на 1. Любому элементу можно задать значение явно. Тогда у следующего элемента значение будет увеличено на 1 от предыдущего (если оно явно не указано).

```typescript
enum Color {
    Black = 3,
    White,
    Red = 10,
    Green
}

const black: 3 = Color.Black;
const white: 4 = Color.White;
const red: 10 = Color.Red;
const green: 11 = Color.Green;
```

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

```typescript
enum Color {
    Black = "black",
    White = "white",
    Red = "red",
    Green = "green"
}

enum WrongColor {
    Black = "black",
    White //Error: Enum member must have initializer.
}
```

## Задания

* [Задание #11](https://www.typescriptlang.org/play?#code/PQKgBI6CCAwggsIFi8IIDhBCsIALgFBi2QBCDKoNwgig7CCDCIIJwggQiDJiAiIKfIFwggYiCKByIGAI5iVSMA0YQOIglQHwggRhBA0iBh4NKQVFhRgCRBE40YzCNRgHhBA-CCICgeRBWiMGb2jAgiAEagJhBJlgrKRHM2O1tHwroomHkoMGIoAytAGRAMbDAAIgBGAAYwAEsAZzAAU04AVwBDABt0gBcAezBEgDowACYAVhT0rLzCsFKa2qrqpLTMnILisq6Y9yxADBArJ3CyKhoigE8ABwywAHM8gCcAEwBaSUEScWRAZhAoGbAAY1zUot2K9BBgdHRkgDsijPWAM1zz5YBBMAAbxGYE+JRKqDAAApcpCXtkALYAIw+AEowABeAB8YHhyI+AG50ABfJ6vd5fH7LABCQJBYIh0NhuMRKPWgiRcNZaMxOLxbKJ0RKRQAFh9IVD0diWfj1kTSc83h9vr8wABhOnRJG5dYS5n8nnSg1yklPT7ZF7nIrJEovLgwyEAgA+YFpLrVqK5ss12HWGSK2XWdri8qeoDAgHwQQACIKI7Mgo4wgg90OdbdcuHFMfbAaDwXqvWypTjcmBiaiiamXunONUs5woTmGXqOUWwCWANRgJGCYVi3XQ1txeplitpopcADMdYbXZ1kJL0pLZaeldSJXyGQq+RKKyhAAMACSAzhxYkNXrNAblBJVI81s89Jr9VqDDpgO8Th+NPotNpdPflkAA)

* [Задание #12](https://www.typescriptlang.org/play?#code/PQKgBI6CCAwggsIFi8IIDhBCsIALgFBi2QxCCEYQZQARBFAhEGTAEMA7ATzEGEQMExQfhBA+EECYQMAZQFcBbAGK8qAYwA0YAKIBHXhQA2AZ2FiwiMACUApgDdtAJ1HbVo9WDYtEgCRBS5RIFEQJlEBcIOMzZA4iAk2eQNIgYPDk-oDcIGwWtnhsLmAubIA8IFYhgPIggGIgiIgAdGCA+CBsgIIgIeQcfuwhQUjJsWxwYFxx8PlsIRowzYBsIPDh-iSAMiBgAMxg+YTRiBjYYABESgKSAEwArFMeWFPacopKkgAuBrzaU+6TUwZ6hsaSALQAjAAMKyDA6Og7NAAO2jwCpmAAvJRaABuV4fL6yeTKX4A6g0EFvT5ac5GEwiMww4EvBFfUxKf7fIRosAAH2kmyhRNJOn0KNMIPQADM0TsAJYAeyoYAZbLZAAoKKgwFQBAAjQySEWC4X8MUGSRMsRKQW4gCUYAA3qswEoAO4snaiAAWYF5CtESky2LVmsmk1EFCUXxmAimE1t7tEHKUbIU2kyCjZAHNec7+EcuWiLWb+RKVSqQe73SKzhQANYJxP2x3TDaQpSurWJsCeqje33+oMh3NbcNmqNomNgEVxjNFrDJ7Rp1u2rNOs404wFtvYEtlv0B4OnZGD+WRzLRihxwuJjtdrUAX3Qm8ZPN590kN0WknVYGxgtDtbRgsbzf+AD5KGAANRNsDr+M7vkDO6Sb-H09gue1bKJeYjXhQsb3o+fwwa+74gtyfL7mA9w-hqAGfOe-YXIcs5gdBD5XBQb7xkAA)

* [Пример решения задания #11](https://www.typescriptlang.org/play?#code/PQKgBI6CCAwggsIFi8IIDhBCsIALgFBi2QBCDKoNwgig7CCDCIIJwggQiDJiAiIKfIFwggYiCKByIGAI5iVSMA0YQOIglQHwggRhBA0iBh4NKQVFhRgCRBE40YzCNRgHhBA-CCICgeRBWiMGb2jAgiAEagJhBJlgrKRHM2O1tHwroomHkoMGIoAytAGRAMbDAAIgBGAAYwAEsAZzAAU04AVwBDABt0gBcAezBEgDowACYAVhT0rLzCsFKa2qrqpLTMnILisq6Y9yxADBArJ3CyKhoigE8ABwywAHM8gCcAEwBaSUEScWRAZhAoGbAAY1zUot2K9BBgdHRkgDsijPWAM1zz5YBBMAAbxGYE+JRKqDAAApcpCXtkALYAIw+AEowABeAB8YHhyI+AG50ABfJ6vd5fH7LABCQJBYIh0NhuMRKPWgiRcNZaMxOLxbKJ0RKRQAFh9IVD0diWfj1kTSc83h9vr8wABhOnRJG5dYS5n8nnSg1yklPT7ZF7nIrJEovLgwyEAgA+YFpLrVqK5ss12GSn2hMQZMRSdtyYAAZOHYsKxetg68wLl0cDotF1hkitl1qGKgyoYlBLkKjGPpLUYLsKTon6A0GQ4nkyC0xmszm84lyyCq9h05ns4mKtr1vmEp2FaAwIB8EEAAiCiOzIaeMIIPdDnW3XLhxTH2wGg8F6r1sqU4sPEztrl4bzjVbecKG7hl6jnHxNgADUYCRghLuuhL7i9RnkSF5XgAzLe96fjqkJhtKYYfokYBnk8IElPkGQVPkJQrFCAAGAAkgKcHExINL0zQDOUCRVIR16kT0TT9K0gwdGAtGgfRjR9C0bRdLh5ZAA)

* [Пример решения задания #12](https://www.typescriptlang.org/play?#code/PQKgBI6CCAwggsIFi8IIDhBCsIALgFBi2QxCCEYQZQARBFAhEGTAEMA7ATzEGEQMExQfhBA+EECYQMAZQFcBbAGK8qAYwA0YAKIBHXhQA2AZ2FiwiMACUApgDdtAJ1HbVo9WDYtEgCRBS5RIFEQJlEBcIOMzZA4iAk2eQNIgYPDk-oDcIGwWtnhsLmAubIA8IFYhgPIggGIgiIgAdGCA+CBsgIIgIeQcfuwhQUjJsWxwYFxx8PlsIRowzYBsIPDh-iSAMiBgAMxg+YTRiBjYYABESgKSAEwArFMeWFPacopKkgAuBrzaU+6TUwZ6hsaSALQAjAAMKyDA6OgAllQ7hgBmFMY8AqYwABvVZgHY0AAO2lQ01m-CmAG5QV8RKIYQAKCgwqgCABGhkkuOxeMMAEowABeAB8YBx-HxBiRAF8Xu9PgYfn9ZPJlICQZNwVCYetNspEcjURisbSSQZCcT6WTKTTcQB7VUKbTUZmsj7fX7aLTnIwmVHA0GC6HTM76E3iyYosRShUM8nUmWKxnoFnoS1gUxKSn-IRmgA+0lFKjDRttxlMSPQjtEO1eqqoYC+6sxLoJYCJHoZkiTShhAfJ-OwSgA7q8dqIABZgdHFzKW8ugyaiChKQ0zARTCaTIdYURppQa7SZBSqgDm6L78KLqKUmSTmMJpNJSOHw9xZwoAGttzuuz3phseUoBx2T2OJ1PZ-OL1sjhnl6vUeu85vjzvsHutSPG8R27XsbQuQ5Bz-Ec701B851OY1jFfFs1woTdgKHADD1-MAWR9TNVXRe5JBuRZJCBMFIStBcUMlJsKA3ZVKDAABqPM8K3RMswGO5JF4iiqKFc9Izop0GKY90KEpCkKQ4pkuMI4i+LAe4VMoy1hXAu0l3E6T3SuaSFIRIA)
