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

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

До этого момента мы ни разу не говорили о том, как изменять тип значения. В 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://race-timo.gitbook.io/typescript/other-features.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
