> 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/types-as-sets.md).

# Типы, как множества. Объединение, пересечения, литералы

## Типы, как множества

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

Как TypeScript понимает, что какое-либо выражение валидно с точки зрения типов? Рассмотрим следующий пример.

```typescript
const a: number = 10;
const b: number = a;
```

Мы явно типизировали переменные `a` и `b` - указали, что эти переменные являются хранилищами для значений, которые принадлежат множеству `number`. При этом, когда мы присваиваем переменной `b` значение переменной `a`, TypeScript проверяет, является ли множество значений (тип), которые могут находиться в переменной `a`, [подмножеством](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B4%D0%BC%D0%BD%D0%BE%D0%B6%D0%B5%D1%81%D1%82%D0%B2%D0%BE) множества значений, которые можно присвоить переменной `b`.

<div align="center"><img src="/files/-M7ploZ152wVXZwOjPNY" alt="Любое числовое значение принадлежит множеству number"></div>

Что будет происходить в таком случаи

```typescript
const a = "str";
const b: number = a;
```

Теперь тип переменной `a`  - это литеральный тип `"str"`, который принадлежит множеству `string`. Множество `number` не содержит значение `"str"`. Поэтому значение переменной `a` нельзя присваивать переменой `b`.

![Тип "str" не пренадлежит множеству number](/files/-M7pnZ8f6Q8Imeiqgt4a)

В этом заключается ключевая механика TypeScript. При выполнении различных операций: присваивание, сложение, вычитание, вызов функций, создание объектов и т.д. - TypeScript проверяет, является ли множество значений одного операнда подмножеством множества значений другого операнда (совместимы ли типы операндов).

## Массивы и кортежи

Рассмотрим следующий код:

```typescript
const a: [number, number, number] = [1, 2, 3];
const b: number[] = a;
```

Мы указали, что переменная `a` - это кортеж из 3 элементов и тип каждого элемента - это `number`. Переменная `b` - массив из любого количества элементов, при этом тип каждого элемента - это `number`. Получается, что кортеж `[number, number, number]` является подмножеством массива `number[]` и операция присваивания может быть выполнена. Почему? Потому что любое значение, которое можно присвоить переменной `a` можно присвоить и переменной `b`. Получается, что кортеж `[number, number, number]` является более строгим условием для массива `number[]`.

![Кортеж \[number, number, number\] принадлежит множеству number\[\]](/files/-M7pnl9-1D3wKC6m5BIW)

Однако обратная операция невозможна. Логично, что существует хотя бы одно значение, которое принадлежит множеству `number[]` и не принадлежит множеству `[number, number, number]`. Поэтому следующий код содержит ошибку:

```typescript
const b: number[] = [1];
const a: [number, number, number] = b;// Error: Type 'number[]' is missing the following properties from type '[number, number, number]': 0, 1, 2(2739)
```

## Литералы

Ранее мы утверждали следующее.

```typescript
10 // имеет тип number
```

На самом деле это не так. Литерал `10` имеет тип `10`, а не `number`. Это максимально точное множество значений для этого литерала - единичное множество, состоящее из значения 10. Такие типы называются литеральными или типами-литералами. В TypeScript можно указывать переменным тип, состоящий из одного единственного значения:

```typescript
const a: 0 = 0;
const b: "lalaka" = "lalaka";
const c: true = true;
const d: [1, 2, 3] = [1, 2, 3];
const e: [[[[null, 15]]]] = [[[[null, 15]]]]
```

При этом все эти единичные множества являются подмножествами более крупных множеств. Поэтому следующие выражения не содержат ошибок:

```typescript
const a1: number = a; // 0 входит во множество number
const b1: string = b; // "lalaka" входит во множесто string
const c1: boolean = c; // true входит во множество boolean
const d1: number[] = d; // рассматривали ранее
const e1: [null, number][][][] = e; // рассматривали ранее
```

По аналогии с массивами и кортежами - обратная операция содержит ошибку:

```typescript
const a2: 0 = a1; //Error: Type 'number' is not assignable to type '0'.(2322)
const b2: "lalaka" = b1; // Error: Type 'string' is not assignable to type '"lalaka"'.(2322)
const c2: true = c1; // Error: Type 'boolean' is not assignable to type 'true'.(2322)
const d2: [1, 2, 3] = d1; // Error: Type 'number[]' is missing the following properties from type '[1, 2, 3]': 0, 1, 2(2739)
const e2: [[[null, 15]]] = e1; // Error: Property '0' is missing in type '[null, number][][][]' but required in type '[[[null, 15]]]'.(2741)
```

## Автовывод и литеральные типы

Тип, который получится при автовыводе, зависит от того, что вы используете при объявлении переменной: `const`, `let` или `var`. Если вы используете `const` для объявления переменной с неизменяемым значением, то TypeScript выводит литеральный тип.

```typescript
const a = 10; // a: 10
const b = true; // b: true
const c = "lalaka"; // c: "lalaka"
const d = null; // d: null
```

Однако, если использовать `let` или `var` при объявлении переменной, то TypeScript выведет более "крупный" тип.

```typescript
let a = 10; // a: number
var b = true; // b: boolean
let c = "lalaka"; // c: string
var d = null; // d: any (что это узнаем позже)
```

Почему так происходит? Такое поведение TypeScript объясняется тем, что он выводит тип, который гарантировано бы соответствовал тому множеству значений, которое потенциально можно положить в эту переменную. В случаи объявления переменных, TypeScript опирается на JavaScript. А JavaScript в свою очередь гарантирует, что переменной, которая объявлена с использованием `const` нельзя будет изменить значение. Это означает, что множество значений, которые потенциально могут находиться в этой переменной, состоит из одного единственного значения и это то значение, которое задали во время инициализации переменной. Поэтому TypeScript может вывести литеральный тип.

JavaScript не может гарантировать, что переменная, объявленная через `let` или `var`, не изменит свое значение далее в программе. Соответственно и TypeScript не может гарантировать, что использование литерального типа опишет все возможные значения этой переменной.

По схожим причинам автовывод типов для значений массива (например, `[1, 2, 3]`) выводит массив (для примера, `number[]`), а не кортеж (для примера, `[number, number, number]`). Даже используя `const` в объявлении переменной, которой присваивается массив, JavaScript не может гарантировать, что внутреннее состояние массива не поменяется. Далее в программе вы сможете изменять этот массив как вам угодно: подменять значения элементов, добавлять новые элементы, удалять старые и т.д. Поэтому TypeScript не выводит литеральный тип `[1, 2, 3]` и даже не кортеж `[number, number, number]`, а именно массив `number[]`.

## Any

В JavaScript одна и та же переменная может содержать в себе значение разных типов. В TypeScript такие переменные можно описать несколькими способами. Один из таких способов - использовать тип `any`.

`any` - особенный тип в TypeScript. С одной стороны этот тип является множеством абсолютно всех возможных значений. Это означает, что в переменную с типом `any` можно записать абсолютно любое значение.

```typescript
let a: any = null;
a = 10;
a = true;
a = "lalaka";
a = [1, 2, 3];
a = [[null, true], [false, ["lalaka", "malaka"]]];
```

С другой стороны, тип `any` является подмножеством абсолютно любого типа. Не важно, что на самом деле находится в переменной с типом `any`, с этой переменной можно совершать любые операции.

```typescript
const a: any = "lalaka";
const b: number = a + a;
const c: true = a;
const d: [number, [string, string]] = a;
```

![Any - множество всех возможных значений и подмножество любого множества](/files/-M7po5L7hqt2cZwvtJfo)

Можно задавать `any`, как тип элементов массива или кортежа. У переменной такого типа будут доступны методы и поля массива, но в качестве элементов могут выступать значения любых типов.

```typescript
const a: any[] = [1, "lalaka", true];
const b = a.length // b: number = 3;
a.push("malaka");
const c = a.pop() //c: any = "malaka";
```

Чем чревато использование `any`? Для значений с типом `any` TypeScript выключает проверку типов, то есть работает как JavaScript. Все преимущества типизации TypeScript теряются. Поэтому использовать `any` нужно с осторожностью. Лучше стараться описывать тип при помощи синтаксиса TypeScript, а не использовать `any`. Однако бывают ситуации, когда без `any` не обойтись. Например, десериализация из JSON, взаимодействие с JavaScript кодом или не типизированными импортами webpack.

## Объединение типов

Как описать тип переменной в которую можно записывать значения разных типов, при этом не используя `any`? Для этого можно воспользоваться операцией объединения типов.

Допустим, мы хотим объявить переменную, в которую будем записывать значение css свойства [font-weight](http://htmlbook.ru/css/font-weight). Эта переменная может принимать как числовые, так и строчные значения. (Мы не будем описывать идеально точный тип для этого свойства, а решим модельную задачу в качестве демонстрации).

```typescript
let fontWeight: any = "bold";
fontWeight = 600;
```

Для того, чтобы использовать множества значений только двух типов, мы должны сказать TypeScript объединить два множества. Это делается при помощи оператора `|`.

```typescript
let fontWeight: number | string = "bold";
fontWeight = 600;
```

Переменная `fontWeight` имеет тип `number | string`. Это означает, что в эту переменную можно записывать значение из множества `number` **ИЛИ** из множества `string`.

![Тип, получившийся в результате объединения множеств number и string](/files/-M7poKGgfyYVcoZoFRXT)

Усложним условие и добавим возможность записывать в эту переменную значение `null` и `undefined`. Для этого нам нужно объединить не 2 множества, а 4: `number`, `string`, единичное множество `null` и единичное множество `undefined`.

```typescript
let fontWeight: number | string | null | undefined = "bold";
fontWeight = 600;
fontWeight = null;
fontWeight = undefined;
```

![](/files/-M7popucjXh50bShlRVZ)

Мы использовали операцию объединения типов 3 раза: сначала объединили тип `number` и `string`, затем результат объединения первых двух типов объединили с литеральным типом `null`, а затем с `undefined`. Получившийся тип сильно лучше, чем `any`, однако у него есть изъяны. Например:

```typescript
let fontWeight: number | string | null | undefined = "lalaka";
fontWeight = -300;
```

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

```typescript
let fontWeight: "bold" | "bolder" | 500 | 600 | null | undefined = "bold";
fontWeight = 600;
fontWeight = null;
fontWeight = undefined;
fontWeight = 500;
fontWeight = "bolder";
fontWeight = "lalaka"; // Error: Type '"lalaka"' is not assignable to type '"bold" | "bolder" | 500 | 600 | null | undefined'.(2322)
```

## Объединение массивов и кортежей

Как объединение работает с массивами и кортежами? Рассмотрим пример

```typescript
const a: number[] | string[] = [1, 2, 3];
const b: number[] | string[] = ["lalaka"];
```

В данном случаи типы у переменных `a` и `b` задаются как объединение множества `number[]` и `string[]`. Это означает, что этим переменным можно присвоить или массив чисел, или массив строк. Этим переменным нельзя присвоить массив, первый элемент которого будет `number`, а второй `string`.

```typescript
const a: number[] | string[] = [1, "lalaka"];
/* Error: Type '(string | number)[]' is not assignable to type 'number[] | string[]'.
  Type '(string | number)[]' is not assignable to type 'number[]'.
    Type 'string | number' is not assignable to type 'number'.
      Type 'string' is not assignable to type 'number'.(2322)
*/
```

Однако, такое значение можно присвоить этой переменной, если записать тип по-другому.

```typescript
const a: (number | string)[] = [1, "lalaka"];
```

В этом случаи мы указали тип переменной `a`, как массив, каждый элемент которого принадлежит типу `number` или типу `string`.

## Пересечение типов

По аналогии с объединением типов, в TypeScript существует операция пересечения типов. Она обозначается символом `&`. Если мы объявим переменную `const a: number & string`, то ей нужно присвоить такое значение, которое одновременно принадлежит множеству `number` и `string`, то есть является и числом, и строкой одновременно. Такого значения не существует. С точки зрения множеств мы пытаемся пересечь два множества, которые не пересекаются, и получаем пустое множество или *ничего* (в TypeScript есть специальный тип `never`, но мы поговорим про него в следующих частях).

## Пересечение массивов и кортежей

Пересекать кортежи не имеет смысла, потому, что мы получим пустое множество значений. Однако у массивов есть один интересный пример. Если мы будем пересекать несколько массивов с разными типами элементов или использовать массив с пересечением простых типов - у нас всегда будет один элемент, который будет удовлетворять всем типам. Это пустой массив `[]`.

```typescript
const a: (number & string & boolean)[] = [];
const b: number[] & string[] & boolean[] = [];
```

Ранее мы говорили, что любому типу массива принадлежит значение пустого массива. Поэтому в TypeScript такое присваивание вполне валидно. Однако, применение такого пересечения на практике вызывает сомнения.

## Задания

* [Задание #3](https://www.typescriptlang.org/play?strictNullChecks=false\&ts=4.1.3#code/PQKgsAUABDWFggg+EEDwgTCsIIXhBBCIIBhBACIIDhBB5ECXygAEAXAZwFoBTADwAc6BjC+gJ04HtOpA-CDJUgcRAMgaRAogQRBMuBCMBcIPgA0UQEwgUBQkwJc4wKIgUQCwgCQNwggNhB0kwIwg4zIBkQKAkASIPmtL1UQCIg+dNlP26MhQuKjOGPj4AHSQsFCAqCBSpqQagLIgmPiGMnKK2IYubggKUOioUNaAwiBGqJiqWkgC+KaEOrhQACoAyuriRtgyTq7uFZrIjc2AYiCRqkJGctalZUgIlgiCOfg4DlCA7CDVUvYAXLGwABYUFExUh8DAAO4PURQAnixUrJwAlkwUADYAhgA7ADmUT4wOAABMeKwqMBTkDIQAjHg8ADWwE4dF+dH+VDoNEBPAodDhLzeH2+HAAzDQAJxRc4AW1+AGIaNR6Mw2Bw6Nw+DRWDwmUy6IDqJAQMBIJBQCcYIBiEAU2HMmyW2AUUnwSH8gTW2QQgGYQfBlDICSRGZpQf4ARlU-wATPbqaoRDpbCUyrZTGsCtYiqMGk1CFNopLpRAcRRrTbDgBuSC2qAAXigNoTECTqYdGcgUetDvjiYdKbTGcdpZzxdLNqgAGooFWIHm6NH-tSi5nqaWAEQA349+tQHv-QcNnto0fl7upiicACudFzEDl0FgSpVaqgOs12t1QWksiNJqgZotVqRdqgSKd15dUDdCA9pXK4h9g0KxXq4xDkRiEClFto0vTtL1LABtG0AAZVBtABWABdDMwNTSC4KHAB2WCoIQ2VgHITlGBYdguF4ThICRSCEJrAA2Zd8xvUCHXAnDe37f5Jx7ZDmNY1MbxY6iG34nCMyFQEqGjVhTjYNEAH0b1k2Nr1RHEgXA6i+OYm0kLwgjaCInlSL4CitIQ8CHVw5tI1bO9OzEiSoCkmT5OpRTSyRakohxEEKFOUSeHEyTpNYOSPNkwsoAkr4QXc6lwJoqCcIE-zAsc4LQtcjsoEBecmSRPkoAAH2UnhVMBWK0LM7TkM8ph5yoU4AApwJ7HK8r5HtsIQgBKGqojqhrmp7FFStxQFOqgOdFx6jNbj0rliN5flyIgDz+vqpqWra-LOAmgAzf5fnxHrdMofTuRIvkyIo2qNqGkayomm0aJmmUV3ANdFWVVVMHVXcdQCA8DWNU18HNYwrQEVBQjQdArEMSFaxoKBEZgh93UkF9vV9IYA2-YNQ3DSB7Mk2tUwS1RWBLVM+0OjjR0pmcoAOo6lzekmUaU0tWHTYmAocyEIu52sGx5lKBay4Whyp8Xo0hAAWQ4YFTHmU2TcmoNllG4KViCecp6q+dSyEaN11CogtqmdIgDnIQws2oHAi2onA-XHO063bYADgdp2Lddq8rc9-m5bpX3nYD1Q-aiIPg+N6Dw4tk3KdvCPWHvHmEOt2UPriDcfrKUxADkQf1AE4QU8wYhwhBGh1BYaCcu6FdDHyjMYp0Hwcwynx5I5DUfwpEIInrOjOhOyNhy6DJqA2YgOh1sG2mAU43qJ9HiKHrG0s6HMuPJ6yyC0fgjSZ61uhFcdqLPhBE-Z45ugdagABBbh-meAAecCr5i4roIQgA+CCzsd5wRwkhIAA)

* [Тест](https://docs.google.com/forms/d/e/1FAIpQLSdoEt54rr4l0UZaxu9GnlUVC-KOwNt7yYgyGAxCok3X2FX80A/viewform?usp=sf_link)

* [Пример решения задания #3](https://www.typescriptlang.org/play?strictNullChecks=false\&ts=4.1.3#code/PQKgsAUABDWFggg+EEDwgTCsIIXhBBCIIBhBACIIDhBB5ECXygAEAXAZwFoBTADwAc6BjC+gJ04HtOpA-CDJUgcRAMgaRAogQRBMuBCMBcIPgA0UQEwgUBQkwJc4wKIgUQCwgCQNwggNhB0kwIwg4zIBkQKAkASIPmtL1UQCIg+dNlP26MhQuKjOGPj4AHSQsFCAqCBSpqQagLIgmPiGMnKK2IYubggKUOioUNaAwiBGqJiqWkgC+KaEOrhQACoAyuriRtgyTq7uFZrIjc2AYiCRqkJGctalZUgIlgiCOfg4DlCA7CDVUvYAXLGwABYUFExUh8DAAO4PURQAnixUrJwAlkwUADYAhgA7ADmUT4wOAABMeKwqMBTkDIQAjHg8ADWwE4dF+dH+VDoNEBPAodDhLzeH2+HAAzDQAJxRc4AW1+AGIaNR6Mw2Bw6Nw+DRWDwmUy6IDqJAQMBIJBQCcYIBiEAU2HMpCQ2AUUnw6oCQWksgQgGYQfBlDICSRGZpQf4ARlU-wATPbqaoRDpbCUyrZTGsCtYiqMGk1CFNopLpRAcRRrTbDlAbVAAD5QB0AbkgtqgAF54+mIJmc2mZZG6NHHXHAQBXJlIvl5x3Z3MZh2Nov5ls5hMAahTecgUet1LjVAoXxBSago8rdHr1MbACIAb951Ae-P-iu12iN7PG1OZ8W5dBYEqVWqNVqdYE1tkjSaoGaLVakXaoEinW+XVA3QgPaVyuIPqDIUxT1OMIaRDEEBSv2pZvrGUAANpVjWfKqDaACsE42gADAAunmL6NohuHoRhBGQEROYkVhPYAOzofhsrAOQnKMCw7BcLwnCUTaJF4Y2NoAGx9iW0bvnGiEjmOwKqIhKI8DiQKqApSmAnhFEQO+iH4QuS7-Nu86EQ6OkCTm2m6T2FmaUKgIjlArCnGwaIAPrvi5CGqbigKIWZb4mTamm3KxtDsTyXF8JRAV4YhDp4cWA5IkOUAAILcP8zwADyIfOKG1pw86MQJyY5V5QKFZOnDTnhAB8ea2fZjnOW51IeY2SVRDiIIUKc9U8HZ0ZNawrlJS5DrDqOnzjuZ1KIUJOH4aZfUDQ5TnDS1LnJXlfITmVgLtbNmExYFhHUlETCVlQpwABQ5dtBVFQAlKd52XTdpWompFX7nhz3MSFXIcby-I8VpZ0XVdt25dW+UVQAZv8vz4r9-2UKF3KcXy3GUeDb1Q3tFXCb9olHnEp6qlA6qatq-jXvqcjGqa+DmsYVoCKgoRoOgViGJCCY0FAfM4a67qSP+3q+kMAZgcGobhpADWDQmObzaorAdlAi6IwZG5q3OOYI0jB4QAr-X2XzcbzTAOasDay3m+NUD3dbDndq79vRpCyXSVNwKNrbq4OW2iuCwALHGe3+8rWYqzhHuCxhkmq1A81+YhttqydpsrZCQkTTJvnEVExfqzZZue3RFYw3yhfUcXUTp6+tsafHkIABxxmlnAZZlPvjsm901UXxeN2rcVlzndKd+lWXO8mffAr5Q91yPGdIfXpct9n5u4dP3dZQvu2fd5E6D8PUS52Pqj1+nX7N5psrgMeirKhTpiAHIg-qAJwgD7M6zhCCA5qgLmQQf50BFr+GwZhijoHwOYMoMtkhyDUP4KQhB5ZiSgHQOMQJniiRDnQZWWC8x0FepDLWAJDJ-QgAQx2kccx0FihPeydBkokWFvGcijZjYEPDkhBefkeHlywYnVKM9sqH2TLhWq59GEYXwgRIAA)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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/types-as-sets.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.
