<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Frontend Development blog by Mykyta Monastyrskyi</title><description>Welcome to Web development blog with articles on Typescript, React, and more.</description><link>https://www.mykytam.com/</link><item><title>Чисті функції, мутації та побічні ефекти</title><link>https://www.mykytam.com/uk/blog/pure-functions/</link><guid isPermaLink="true">https://www.mykytam.com/uk/blog/pure-functions/</guid><description>Пояснення і приклади використання</description><pubDate>Wed, 20 Nov 2024 00:00:00 GMT</pubDate><content:encoded>
У контексті JS, React, Redux та функціонального програмування часто використовуються такі терміни як

- [мутації / мутабельність (mutations / mutability)](#мутації),
- [побічні ефекти (side effects)](#побічні-ефекти-side-effects),
- [чисті функції (pure functions)](#чисті-функції-pure-functions),
- [ідемпотентні функції (idempotent functions)](#ідемпотентні-функції).

Давайте з’ясуємо, що вони означають, як використовуються (в контексті JS), та, нарешті, поставимо крапку в термінологічній плитунині, яка притаманна цим термінам.

## Мутації

&lt;div class=&quot;max-w-lg m-auto&quot;&gt;![ninja-turtles](../../images/ninja-turtles.png)&lt;/div&gt;

Мутація в програмуванні означає зміну стану об’єкта, змінної чи структури даних після їх створення. І нажаль ніяк не пов’язана з черепашками-ніндзя, або з людьми-мутантами.

У JavaScript примітивні значення (всі типи данних крім обʼєктів) є іммутабельними (immutable), тобто вони не можуть бути змінені, але втім, можуть бути переназначені.

Часто можна почути, що `let` є мутабельним, а `const` — ні. Це не зовсім правильно.

Різниця між `let` та `const` саме в тому, що `const` не можна переназначити, але так само як і для `let`, обʼекти і массиви в `const` **не** є іммутабельними.

### Приклад

```js
const person = {name: &apos;Cassius Marcellus Clay&apos;};
person.name = &apos;Muhammad Ali&apos;;
```

У цьому прикладі властивість `name` об’єкта `person` змінюється. Це є мутацією.

### Чому це вважається поганим?

Мутації самі по собі не є шкідливими, але вони можуть призводити до помилок і ускладнювати розуміння коду.

Короткий приклад:

```js
const a = [1, 2, 3];
const b = a;
b.push(4);
console.log(a); // [1, 2, 3, 4]
```

У цьому прикладі ми мутуємо масив `b`, але він вказує на той самий об’єкт, що і `a`. Це може призвести до непередбачуваної поведінки.

Щоб уникнути цього, можна використовувати методи, які повертають новий об’єкт, а не мутують старий:

```js
const a = [1, 2, 3];
const b = {...a, 4};
console.log(a); // [1, 2, 3]
```

### Іммутабельність в React/Redux

#### React

Одне з основних правил React - це те що [`props` і `state` повинні бути іммутабельними](https://react.dev/reference/rules/components-and-hooks-must-be-pure#props-and-state-are-immutable).

#### Redux

Так само і в Redux - перше правило в [Redux Style Guide](https://redux.js.org/style-guide/style-guide#do-not-mutate-state) - не мутувати стан.

### Бібліотеки для роботи з іммутабельністю

Для того щоб уникнути мутацій, можна використовувати вбудований в JavaScript метод `Object.freeze` або бібліотеки, які допомагають працювати з іммутабельністю, наприклад:

- [Immer](https://immerjs.github.io/immer/docs/introduction)
- [Immutable.js](https://immutable-js.github.io/immutable-js/)

## Побічні ефекти (Side Effects)

&lt;div class=&quot;m-auto max-w-sm&quot;&gt;![side-effects](https://i.imgflip.com/9b13jd.jpg)&lt;/div&gt;

Побічний ефект — це будь-яка зміна в системі, яка впилває на зовнішній світ.

Можливо, зараз це звучить дещо абстрактно.

Коли ви викликаєте функцію, ваш комп’ютер працює інтенсивніше, виділяючи тепло. Це тепло може поступово підвищувати температуру в кімнаті. Чи можна це вважати побічним ефектом функції?

Що ж, це залежить від контексту:

В контексті фізики — так. Але в контексті программи, яку ми розробляємо, — ні. Зазвичай побічні ефекти в програмуванні стосуються лише того, що відбувається в межах программи, наприклад:

- Зміна глобальної змінної.

```js
let counter = 0;

function increment() {
	counter++; // Побічний ефект: змінюється зовнішній стан
}

increment();
console.log(counter); // 1
```

- Логування в консоль.

```js
function logMessage(message) {
	console.log(message); // Побічний ефект: вивід у консоль
}

logMessage(&apos;Hello, world!&apos;);
```

Логування не змінює стан програми, але впливає на середовище виконання, залишаючи слід у консолі.

- Зміни в DOM.

```js
function updateTitle(newTitle) {
	document.title = newTitle; // Побічний ефект: зміна заголовка сторінки
}

updateTitle(&apos;Новий заголовок&apos;);
console.log(document.title); // &quot;Новий заголовок&quot;
```

### Побічні ефекти в React/Redux

#### React

В функціональних компонентах React місцем для побічних ефектів є хук `useEffect`. Там можуть відбуватися такі побічні ефекти як:

- Запити до сервера

```jsx
useEffect(() =&gt; {
	fetch(&apos;https://api.example.com/data&apos;)
		.then((response) =&gt; {
			if (!response.ok) {
				throw new Error(&apos;Network response was not ok&apos;);
			}
			return response.json();
		})
		.then((data) =&gt; setData(data))
		.catch((error) =&gt; console.error(&apos;Fetch error:&apos;, error));
}, []);
```

Варто зазначити, що для запитів до сервера можна використовувати бібліотеки, які дозволяють писати код більш декларативно, наприклад [React Query](https://react-query.tanstack.com/) або [SWR](https://swr.vercel.app/).

- Підписка на події

  ```jsx
  useEffect(() =&gt; {
  	const handleOffline = () =&gt; setIsOffline(true);
  	const handleOnline = () =&gt; setIsOffline(false);

  	window.addEventListener(&apos;offline&apos;, handleOffline);
  	window.addEventListener(&apos;online&apos;, handleOnline);

  	return () =&gt; {
  		window.removeEventListener(&apos;offline&apos;, handleOffline);
  		window.removeEventListener(&apos;online&apos;, handleOnline);
  	};
  }, []);
  ```

- Робота з локальним сховищем

  ```jsx
  useEffect(() =&gt; {
  	const data = JSON.parse(localStorage.getItem(&apos;data&apos;));
  	setData(data);
  }, []);
  ```

- Маніпуляції з DOM

  ```jsx
  useEffect(() =&gt; {
  	document.title = &apos;Новий заголовок&apos;;
  }, []);
  ```

#### Redux

В Redux управління побічними ефектами у ньому реалізується через middleware.
Найпопулярнішим middleware для цього — [Redux Thunk](https://github.com/reduxjs/redux-thunk)

```jsx
const fetchUserById = createAsyncThunk(&apos;users/fetchByIdStatus&apos;, async (userId: number, thunkAPI) =&gt; {
	const response = await userAPI.fetchById(userId);
	return response.data;
});
```

## Чисті функції (Pure Functions)

![Pure function](https://i.imgflip.com/9b1btv.jpg)

Чисті функції — це функції, які мають дві властивості:

1. Вони не мають [побічних ефектів](#побічні-ефекти-side-effects).
2. Детермінізм - це значить, що для одних і тих самих вхідних даних функція завжди повертає один і той самий результат.

Давайте розглянемо пару прикладі

### Приклади

Чи буде ця функція чистою?

```js
function addRandom(a) {
	return a + Math.random();
}
```

Для цього нам потрібно відповісти на два питання:

1. Чи має ця функція побічні ефекти?

Ні, побічних ефектів тут немає.

2. Чи є ця функція детермінованою? Тобто, чи завжди повертає один і той самий результат для одних і тих самих вхідних даних?

Ні, ця функція не є детермінованою, оскільки `Math.random()` повертає випадкове число. Через що при першому виклику `addRandom(2)` може повернути, наприклад, `2.123`, а при другому, скажімо, — `2.456`.

Отже, ця функція не є чистою.

А ця?

```js
function add(a, b) {
	return a + b;
}
```

Ця функція відповідає обом вимогам чистої функції: вона не має побічних ефектів і є детермінованою.

#### Чисті функції в Redux

Одним з трьох основних принципов в Redux є [використання чистих функцій](https://redux.js.org/understanding/thinking-in-redux/three-principles#changes-are-made-with-pure-functions)

## Ідемпотентні функції

&lt;div className=&quot;flex flex-wrap sm:flex-nowrap gap-4 justify-center&quot;&gt;
	&lt;div className=&quot;bg-white w-full max-w-xs&quot;&gt;![Idempodent](https://i.imgflip.com/9b1sg7.jpg)&lt;/div&gt;
	&lt;div className=&quot;bg-white w-full max-w-xs&quot;&gt;![Non-idempotent](https://i.imgflip.com/9b1tjc.jpg)&lt;/div&gt;
&lt;/div&gt;

Термін [«ідемпотентність»](https://uk.wikipedia.org/wiki/%D0%86%D0%B4%D0%B5%D0%BC%D0%BF%D0%BE%D1%82%D0%B5%D0%BD%D1%82%D0%BD%D1%96%D1%81%D1%82%D1%8C) означає властивість, яка проявляється в тому, що повторне застосування операції над об’єктом не змінює його стан після першого виконання.

Функція є ідемподентною, якщо її повторні виклики з однаковими вхідними даними не змінюють результат або стан після першого виклику.

Тобто вликлик функції `f(x)` декілька разів, наприклад `f(x); f(x); f(x);` дасть той самий результат, що і один виклик `f(x);`.
(Тут варто зазначити, що в математиці ідемподентність означає, що `f(f(x)) = f(x)`, але то вже трохи інша історія. Якщо цікаво, можете прочитати це в чудовій книзі Кайла Сімпсона [Functional-Light JavaScript](https://github.com/getify/Functional-Light-JS/blob/40a68612275b23904c7b37abdb724b250bdf75e7/manuscript/ch5.md/#mathematical-idempotence))

### Приклади

```js
function convertToUpper(str) {
	return str.toUpperCase();
}
```

Ця функція є ідемпотентною, тому що вилкик цієї функції, скажімо, 10 разів, дає той же самий еффект, що й виклик один раз.
Ця функція також є й чистою. Чисті функції завжди є ідемпотентними, але не обов&apos;язково навпаки.

---

```js
function setUserActive(user) {
	user.status = &apos;active&apos;;
}
```

Ця функція є ідемпотентною, оскільки при кожному виклику з однаковим об&apos;єктом `user` буде встановлювати статус `active`.
Але ця функція не є чистою, оскільки мутує об&apos;єкт `user`.

---

```js
function addHiddenClass(element) {
	element.classList.add(&apos;hidden&apos;);
}
```

Аналогічно, функція addHiddenClass є ідемпотентною: після першого виклику клас hidden додається до елемента, і наступні виклики не змінюють стан.
Однак через те, що функція мутує DOM, вона не є чистою.

---

Тут у нас не буде прикладів використання в React/Redux, тому що частіше термін ідемпотентність використовується в [контексті API](https://www.youtube.com/watch?v=t99NvIazD68) або [HTTP методів](https://developer.mozilla.org/en-US/docs/Glossary/Idempotent).

### Висновок

На цьому все. Сподіваюсь ця стаття додала трошки ясності в вищезгадані терміни. Якщо у вас виникли питання, або ви знайшли помилку, можете написати тут в коментарях або на будь-який з контактів зазначенних в футері.
</content:encoded></item><item><title>Цікаві медіа-запити (media queries)</title><link>https://www.mykytam.com/uk/blog/media-queries/</link><guid isPermaLink="true">https://www.mykytam.com/uk/blog/media-queries/</guid><description>Незвичні способи використання медіа-запитів</description><pubDate>Sun, 10 Nov 2024 00:00:00 GMT</pubDate><content:encoded>
## Інтро

Вам, напевно, доводилося використовувати медіа-запити для адаптації вашого сайту під різні пристрої, і ви могли писати щось на зразок:

```css
@media only screen and (max-width: 768px) {
	/* ... */
}
```

Але чи знали ви, що спектр можливостей `@media` набагато ширший? Далі ми розберемо цікаві й незвичні способи використання `@media`, але спочатку коротко згадаємо основи медіа-запитів. Якщо ви вже знайомі з основами, можете одразу перейти до [незвичайних способів використання медіа-запитів](#незвичні-способи-використання-медіа-запитів).

## Основи `@media` і медіа-запитів

`@media` і медіа-запити часто вважають одним і тим самим, але між ними є невелика відмінність:

- `@media` — це [@-правило (at-rule)](https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule) в CSS, яке використовується для оголошення медіа-запиту. Воно застосовує певні стилі тільки в тому випадку, якщо задані умови для медіа-запиту є правдивими.
- Медіа-запит (media query) — це умова, що міститься в `@media` і перевіряє певні параметри поточного пристрою (наприклад, ширину, висоту, орієнтацію тощо).
- @-правило (at-rule) в CSS — це команда, яка починається зі знаку `@` і використовується для керування поведінкою стилів. @-правила дозволяють CSS вказувати особливі інструкції для браузера. Крім `@media`, існують й інші @-правила, наприклад:

  - `@import` — дозволяє імпортувати інші CSS-файли.
  - `@font-face` — використовується для оголошення власних шрифтів.
  - `@keyframes` — визначає анімацію з ключовими кадрами.
  - `@container` — дозволяє застосовувати стилі на основі розмірів або інших властивостей контейнера елемента.

Отже, повертаючись до нашого прикладу:

```css
@media only screen and (max-width: 768px) {
	/* ... */
}
```

- `@media` — це @-правило.
- `only screen and (max-width: 768px)` — це медіа-запит.

Кожен медіа-запит може складатися з:

- **Медіа-типів** ([\&lt;media-type\&gt;](https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_types)) — це типи пристроїв, на які застосовуються стилі. Наприклад:

  - `screen`: для екранів (комп&apos;ютерів, телефонів, планшетів).
  - `print`: для друку.
  - `all`: для всіх медіа-типів.

- **Медіа-фіч** ([\&lt;media-feature\&gt;](https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_features)) — це характеристики пристрою, які перевіряються у медіа-запитах, щоб застосувати потрібні стилі. Наприклад:

  - `width`: ширина області перегляду.
  - `height`: висота області перегляду.
  - `orientation`: орієнтація екрану (`portrait` чи `landscape`).

- **Логічних операторів** ([Logical operators](https://developer.mozilla.org/en-US/docs/Web/CSS/@media#logical_operators)) — дозволяють комбінувати медіа-тип та медіа-фічі в одному запиті:
  - `and`: для одночасного виконання всіх умов.
  - `or`: для виконання хоча б однієї з умов.
  - `not`: для виключення певних умов.
  - `only`: обмежує застосування стилів тільки до конкретних медіа-типів або умов, ігноруючи сумісність зі старішими браузерами.
  - `,` (кома): для об’єднання кількох медіа-запитів (аналогічно логічному &quot;або&quot;).

---

Тепер давайте розберемо медіа-запит із нашого прикладу:

```css
@media only screen and (max-width: 768px) {
	/* ... */
}
```

- `only` — обмежує дію стилів тільки для пристроїв із підтримкою медіа-запитів. Раніше це було актуально, оскільки старі браузери без підтримки медіа-запитів інтерпретували `screen and (max-width: 768px)` як `screen`, ігноруючи решту запиту, таким чином застосовуючи стилі всередині на всіх екранах. Але тут варто зазначити, що коли ми говоримо &quot;старі браузери&quot;, то ці браузери дійсно [дуже старі](https://caniuse.com/css-mediaqueries). Це буквально Internet Explorer 8 і нижче. Тож у сучасному коді про `only` можна сміливо забути.

- `screen` — медіа-тип, який вказує, що стилі застосовуються тільки для екранних пристроїв (наприклад, моніторів, смартфонів, планшетів).
- `and` — логічний оператор, який використовується для об’єднання кількох медіа-фіч в один медіа-запит. У цьому випадку медіа-тип і медіа-фічу.
- `(max-width: 768px)` — медіа-фіча, яка вказує, що стилі будуть застосовуватися тільки для екранів із шириною включно до 768 пікселів.

Отже, цей медіа-запит обмежує стилі для екранів шириною включно до 768 пікселів.
Ключове слово `only` можна вважати залишком минулого, тому запит можна спростити до:

```css
@media screen and (max-width: 768px) {
	/* ... */
}
```

---

## Незвичні способи використання медіа-запитів

Крім звичного використання медіа-запитів у CSS, як показано в прикладах вище, існують й інші, не менш корисні способи:

### @import

Ви можете комбінувати медіа-запит із `@import` для умовного завантаження CSS-файлів. Наприклад:

```css
@import url(&apos;styles.css&apos;) screen and (min-width: 768px);
```

### HTML

Атрибут `media` можна використовувати в HTML-елементах, таких як:

#### `&lt;link&gt;`

```html
&lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot; media=&quot;screen and (max-width: 768px)&quot; /&gt;
```

- У цьому випадку, якщо ширина екрану менша або дорівнює `768px`, стилі з файлу `styles.css` будуть завантажені і застосовані.
- Але, якщо ширина екрану більша за `768px`, `styles.css` все одно буде завантажено (без [блокування візуалізації](https://web.dev/articles/critical-rendering-path/render-blocking-css)), але стилі не будуть застосовані.

Давайте ще раз:

- Якщо медіа-запит атрибуту `media` оцінюється як `true` (у цьому випадку, якщо ширина менша або дорівнює `768px`), то цей `.css` файл і його вміст буде завантажено з [блокуванням візуалізації](https://web.dev/articles/critical-rendering-path/render-blocking-css), і його стилі будуть застосовані як зазвичай.
- Але якщо медіа-запит оцінюється як `false`, то файл буде завантажено без блокування візуалізації, і стилі застосовані не будуть.

#### `&lt;style&gt;`

```html
&lt;style media=&quot;screen and (max-width: 768px)&quot;&gt;
	/* ... */
&lt;/style&gt;
```

- Якщо медіа-запит виконується, стилі всередині `&lt;style&gt;` будуть застосовані, інакше — ні.

#### `&lt;source&gt;`

```html
&lt;picture&gt;
	&lt;source srcset=&quot;image.webp&quot; type=&quot;image/webp&quot; media=&quot;(min-width: 768px)&quot; /&gt;
	&lt;img
		src=&quot;image.jpg&quot;
		alt=&quot;An image

&quot;
	/&gt;
&lt;/picture&gt;
```

- Якщо медіа-запит виконується, використовується `image.webp`, інакше — `image.jpg`.

### Атрибут `sizes`

Крім атрибуту `media` у прикладах вище, медіа-запити також використовуються в атрибуті `sizes` для вказівки розміру зображення в `&lt;img&gt;` або `&lt;source&gt;`. Наприклад:

```html
&lt;img
	src=&quot;image.jpg&quot;
	alt=&quot;An image&quot;
	sizes=&quot;(max-width: 768px) 100vw, 50vw&quot;
	srcset=&quot;image-400.jpg 400w, image-800.jpg 800w&quot;
/&gt;
```

- Атрибут `sizes` вказує браузеру, якою буде ширина зображення залежно від умов медіа-запиту.
- У цьому випадку, якщо ширина екрану менша або дорівнює `768px`, зображення буде шириною `100vw`, інакше — `50vw`.

### `window.matchMedia`

І, нарешті, ви можете використовувати JavaScript для перевірки медіа-запитів. Для цього використовується метод [`window.matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia), який повертає об&apos;єкт [MediaQueryList](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList), який можна використовувати для:

- Миттєвої перевірки: визначення, чи відповідає документ заданому медіа-запиту (`.matches`).
- Відстеження змін: реагування на зміни, коли документ починає або перестає відповідати медіа-запиту (`.addListener`).

Приклад використання ви зможете побачити трохи далі в секції з [`prefers-color-scheme`](#prefers-color-scheme).

## Незвичні медіа-фічі

Розібравшись з основами медіа-запитів і усіма способами їх використання, давайте розглянемо деякі цікаві медіа-фічі. Крім стандартних — таких як ширина, орієнтація тощо, існує ще цілий ряд медіа-фіч, які можуть стати в нагоді.  
Повний список можна подивитись [тут](https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_features), а ми з вами розглянемо найбільш цікаві, на мою думку.

### `prefers-color-scheme`

[MDN Link](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme)

- Дозволяє визначити, чи віддав користувач перевагу світлій або темній темі інтерфейсу на поточному девайсі.

Нижче найпростіший спосіб використання цієї фічі:

```css
body {
	color: black;
	background-color: white;
}

@media (prefers-color-scheme: dark) {
	body {
		color: white;
		background-color: black;
	}
}
```

Але, як правило, сайти або додатки, які підтримують темну і світлу теми, мають також можливість вибору теми користувачем. Яскравий приклад — це сайт блогу, який ви зараз читаєте.

За замовчуванням тема визначається відповідно до `prefers-color-scheme`, а потім, якщо ви вирішите її змінити, вибір буде збережено в `local storage`, і далі тема буде застосована на основі вашого попереднього вибору.

В коді це може виглядати так:

```ts
type Theme = &apos;light&apos; | &apos;dark&apos;;

function getTheme(): Theme {
	const localStorageTheme = localStorage.getItem(&apos;theme&apos;);

	if (localStorageTheme === &apos;light&apos; || localStorageTheme === &apos;dark&apos;) {
		return localStorageTheme;
	} else {
		return window.matchMedia(&apos;(prefers-color-scheme: dark)&apos;).matches ? &apos;dark&apos; : &apos;light&apos;;
	}
}
```

Зверну вашу увагу на використання функції [`window.matchMedia`](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia), про яку ми згадували [раніше](#windowmatchmedia).

### `pointer`/`any-pointer`

[`pointer`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer) і [`any-pointer`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-pointer) дозволяють визначити, чи має користувач вказівник (pointer) (наприклад, мишка або стилус), і якщо так, то наскільки точним є цей пристрій. Це дає змогу адаптувати стилі та взаємодію на сторінці відповідно до можливостей пристрою.

Можливі значення:

- `none`: Не має вказівника.
- `coarse`: Вказівник з обмеженою точністю, наприклад, палець на сенсорному екрані.
- `fine`: Точний вказівник, такий як мишка або стилус.

Але в чому ж все ж таки різниця між `pointer` та `any-pointer`?

А в тому, що `pointer` враховує лише основний механізм введення, тоді як `any-pointer` враховує будь-який.

Наприклад, у вас є планшет з підключеним стилусом, в такому випадку всі наступні три медіа-запити будуть виконані:

- `@media (pointer: coarse)` — тому що основний механізм введення — палець, який не відрізняється своєю точністю.
- `@media (any-pointer: fine)` — тому що на планшеті підключений стилус, який є точним вказівником.
- `@media (any-pointer: coarse)` — тому що, попри те, що у вас є стилус, палець все ще при вас :)

Приклад використання:

```css
/* Стилі для точних пристроїв вказівника */
@media (pointer: fine) {
	button {
		padding: 10px;
	}
}

/* Стилі для пристроїв з обмеженою точністю вказівника */
@media (pointer: coarse) {
	button {
		padding: 20px;
	}
}
```

У цьому прикладі кнопки отримують різні відступи залежно від точності вказівника.

### `hover` / `any-hover`

Медіа-фічі [`hover`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover) і [`any-hover`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/any-hover) перевіряють, чи механізм введення користувача може наводити курсор на елементи. Вона має два можливі значення:

- `hover`: пристрій введення підтримує hover (наприклад, миша або трекпад).
- `none`: пристрій введення не підтримує hover.

Різниця між `hover` і `any-hover` аналогічна різниці між `pointer` і `any-pointer`. `hover` враховує лише основний механізм введення, тоді як `any-hover` враховує будь-який.

Наприклад, на телефоні з підключеною мишею наступні три медіа-запити будуть виконані:

- `@media (hover: none)` — тому що основний пристрій введення — це ваш палець, а його hover не розпізнається :).
- `@media (any-hover: hover)` — тому що на телефоні підключена мишка, яка підтримує наведення.
- `@media (any-hover: none)` — тому що, попри те, що у вас є миша, на телефоні все ще можна використовувати сенсорний екран.

Приклад використання:

```css
/* Стилі для пристроїв, які не підтримують hover */
@media (hover: none) {
	.tooltip {
		display: none;
	}
}
```

У цьому прикладі, якщо пристрій не підтримує hover, то елемент з класом `.tooltip` буде приховано.

### `update`

[MDN Link](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/update)  
Дозволяє визначити, як часто пристрій може оновлювати відображення контенту після його рендерингу.

Можливі значення:

- `none`: пристрій не може оновлювати відображення після рендерингу. Приклад: друковані матеріали.
- `slow`: пристрій може оновлювати відображення, але з низькою частотою, що не підходить для плавних анімацій. Приклад: електронні книги.
- `fast`: пристрій може швидко оновлювати відображення, підтримуючи плавні анімації. Приклад: сучасні монітори.

Приклад використання:

```css
@media (update: fast) {
	.animated-element {
		animation: slide 2s infinite;
	}
}

@media (update: slow) {
	.animated-element {
		animation: none;
	}
}
```

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

### Інші:

Ще є багато медіа-фіч, які можуть вас зацікавити, ви можете знайти повний список на [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@media#media_features). Коротко можу ще виділити:

- [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion) — визначає, чи користувач бажає мінімізувати анімації.
- [`prefers-contrast`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-contrast) — визначає, чи користувач бажає збільшити або зменшити контрастність.
- [`display-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/display-mode) — визначає, яким чином відображається сторінка (наприклад, в окремому вікні, на повноекранному режимі тощо).

## Кінець

Дякую за увагу, сподіваюсь, ця інформація була корисною.

Якщо у вас є які-небудь питання або коментарі, пишіть на будь-яку з моїх соціальних мереж, які ви можете знайти внизу сторінки.
</content:encoded></item><item><title>Ефект Протеже</title><link>https://www.mykytam.com/uk/blog/protege-effect/</link><guid isPermaLink="true">https://www.mykytam.com/uk/blog/protege-effect/</guid><description>Навчайся навчаюячи</description><pubDate>Fri, 08 Nov 2024 00:00:00 GMT</pubDate><content:encoded>

## Що це таке?
Ефект протеже — це концепція в навчанні, яка передбачає, що ми навчаємось ефективніше, навчаючи інших, і не обов’язково, щоб ці &quot;інші&quot; були менш обізнаними. 

Ця ідея не є новою. Ще 2000 років назад відомий філософ-стоїк Сенека  писав: &quot;[Docendo discimus](https://en.wikipedia.org/wiki/Docendo_discimus)&quot;, що означає &quot;Ми навчаємося, навчаючи&quot;. 

Але навіть більше мені подобається цитата, яку приписують письменнику Роберту Хайнлайну - &quot;Коли один навчає, вчаться двоє&quot;.

## Чому це працює? 
Якщо коротко, то пояснюючи матеріал іншим, в нас задіюються певні механізми, які відсутні при пасивному сприйнятті інформації.

1. Перш ніж пояснити іншим, ми маємо спочатку структурувати та організувати інформацію в голові. 
2. Навчаючи, ми стаємо відповідальними не тільки за свої знання, але і за знання того, хто нас слухає або читає. Що також мотивує нас до більш детального вивчення матеріалу.
3. Вмикається механізм [метакогніції](https://en.wikipedia.org/wiki/Metacognition). Тобто, ми починаємо усвідомлювати свої власні прогалини в знаннях, що спонукає до їх заповнення та покращення розуміння.


## Мій особистий досвід

На своєму досвіді я завжди переконувався що цей підхід працює. 

Найперший приклад, який я можу пригадати стосується шкільних часів, коли наш вчитель з історії Андрій Олексійович, який зараз захищає країну в якості військовослужбовця, дав завдання підготувати презентацію на тему Кримської війни. Скільки часу минуло з цього уроку, а я й досі памʼятаю якісь цікаві факти, зокрема, що великою перевагою на полі бою для союзників було використання нарізних стволів порівняно з гладкоствольні.

Далі мені доводилось і робити презентації на різні теми, і проводити лекції, і кожен раз я відкривав для себе що, поперше, ці знання глибші і структурованіші ніж ті, які ти зазвичай отримуєш читаючи, слухаючи, чи дивлячись матеріал. І по-друге, ці знання живуть з тобою набагато довше.

Нещодавно, я започаткував свій блог і поки що встиг написати дві статті. І крім теплого відчуття, що вони можуть бути комусь корисними, я також збагнув, що підготовка статей мотивує глибше зануритись в тему, яку ти обрав.

## Наукове обгрунтування
Але це був мій особливий досвід, і мені цікаво те, наскільки він обгрунтований дослідженнями.

Перше, що я знайшов, і одразу ж впізнав, тому що скоріш за все бачив це в стрічці LinkedIn - це [піраміда навчання](https://en.wikipedia.org/wiki/Pyramid_of_learning). 

&lt;div class=&quot;dark:bg-slate-300&quot;&gt;
![cone-of-learning.png](./cone-of-learning.png)
&lt;/div&gt;

Коефіцієнт утримання	| Навчальна діяльність перед перевіркою знань
----------------|-------------------------------------------
90 %	| Навчити когось іншого/використовувати негайно.
75 %	| Практикувати те, чому навчилися.
50 %	| Брати участь у груповій дискусії.
30 %	| Дивитися демонстрацію.
20 %	| Дивитися аудіовізуальний матеріал.
10 %	| Читати.
5 %	| Слухати лекцію.


Виглядая ця концепція доволі переконливо і навіть шокуюче. Якщо їй вірити, то слухати лекцію в університеті в 18 разів менш еффективно ніж навчити когось іншого. 

Як правило, якщо ви бачите шокуючі цифри, це маркер того, що ця інформація потенційно недостовірна. І це дійсно так, як виявилося, ця піраміда не має наукового обґрунтування. Більше про це можна прочитати [тут](https://academia.stackexchange.com/questions/22084/are-there-any-available-studies-on-the-effectiveness-of-learning-by-teaching).

То невже, ефект протеже - це просто міф? Не зовсім. В [цій статті](https://www.growthengineering.co.uk/protege-effect/#elementor-toc__heading-anchor-1) наводяться дослідження, які підтверджують, що навчання інших може покращити ваше власне розуміння матеріалу. Зокрема:

- [Дослідження 1984 року](https://psycnet.apa.org/record/1985-29263-001) показало, що очікування навчання інших підвищує внутрішню мотивацію більше, ніж забовʼязання слкласти тест.

- [Дослідження 2009 року](https://aaalab.stanford.edu/papers/Protege_Effect_Teachable_Agents.pdf) про &quot;навчальних агентів&quot;. Навчальний агент — це цифровий персонаж, здатний до міркувань на основі отриманих знань. Учні, які взаємодіяли з цими агентами, показали значне покращення у навчанні порівняно з тими, хто цього не робив. Іншими словами, навіть симульоване навчання може активувати ефект протеже.

- [Дослідження 2014 року](https://link.springer.com/article/10.3758/s13421-014-0416-z) доводить, що &quot;очікування навчання інших&quot; підвищує &quot;ефективність навчання вдома та в класі&quot;.

- [Дослідження 2016 року](https://psycnet.apa.org/record/2015-38251-001) продемонструвало, що ті, хто навчається, готуючись до навчання, використовують на 1,3 рази більше [метакогнітивних стратегій](https://en.wikipedia.org/wiki/Metacognition), ніж ті, хто цього не робить.

- [Дослідження 2016 року](https://www.sciencedirect.com/science/article/abs/pii/S0167945716301130?via%3Dihub) виявило, що очікування навчання інших моторним навичкам покращує ці навички.

## Цікаві способи використання ефекту протеже

Є доволі очевидні (але дієві) способи використання ефекту протеже, так як:

- Створення відео або подкастів
- Написання статей
- Відповіді на питання на форумах
- Менторство.

Так само є й менш очевидні способи:

### Навчити когось з родини або друзів
Якщо ви вивчаєте нову технологію, мову програмування, чи щось інше, ви можете спробувати навчити когось, хто з вами поруч. Якщо ці люди зовсім не розбираються в цій темі - то це навіть веселіше. Вам доведеться використовувати прості терміни, доступні алегорії і приклади. Колись так мій друг хімік пояснював мені через алегорії з зупинкою автобусу колоїдний рівень в хімії. Памʼятаю тільки назву цього рівня, але тоді було доволі цікаво!


### Метод каченяти

Один з найцікавіших способів - це [метод каченяти](https://en.wikipedia.org/wiki/Rubber_duck_debugging). В основі цього методу лежить те, що ви ставите (чи уявляєте) на робочому столі іграшкове каченятко, і коли в вас виникає запитання, на яке важко дати відповідь — задаєте його іграшці, так неначе вона справді може відповісти. А правильне формулювання питання, як відомо, містить половину відповіді.

Це нагадує мені те, як один мій друг постійно запитує мене щось з приводу окремих тем в програмуванні, але перш ніж я встигну відповісти, він вже сам знаходить відповідь. 

Цю ідею можна використовувати не тільки, коли у вас виникають запитання, але і коли ви хочете поглибити своє розуміння якоїсь теми. Не знаєте кого навчити? Навчіть качку!

### Використання AI

Всі ми звикли використовувати AI як вчителя, а що як використати його як учня? Автор [цієї статті](https://www.theguardian.com/books/article/2024/sep/09/the-big-idea-how-the-protege-effect-can-help-you-learn-almost-anything) запропонував ChatGPT взяти на себе роль допитливого іспанського студента, який хотів би почути те, чому навчився автор.

## Висновок
На цьому все. Не забувайте, що навчання інших, це не тільки корисно для них, але і для вас.</content:encoded></item><item><title>Тип `never`</title><link>https://www.mykytam.com/uk/blog/never-underestimate-never/</link><guid isPermaLink="true">https://www.mykytam.com/uk/blog/never-underestimate-never/</guid><description>Повний огляд типу `never` в TypeScript</description><pubDate>Mon, 04 Nov 2024 00:00:00 GMT</pubDate><content:encoded>import { Image } from &apos;astro:assets&apos;;
import never1 from &apos;./never-1.jpg&apos;;

Давайте поговоримо про тип `never` . Коли його використовувати, як він працює, та чому це потужний інструмент в вашому арсеналі TypeScript.


Коли ви шукаєте інформацію про те, як щось працює, зокрема в TypeScript, завжди варто починати з [офіційної документації](https://www.typescriptlang.org/docs/).
Єдина проблема в тому, що в TypeScript інформація в документації може бути розкидана по різних розділах. А втім, ви завжди можете скористатися пошуком
як на прикладі нижче:
![Typescript Search Docs](./typescript-docs-search.jpg)

## Що таке `never`?

[Перша згадка](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#other-important-typescript-types) в документації каже нам про те, що `never` - це [bottom type](https://en.wikipedia.org/wiki/Bottom_type) (найнижчий тип), а `unknown` - це [top type](https://en.wikipedia.org/wiki/Top_type#:~:text=The%20top%20type%20is%20sometimes,object%20of%20the%20type%20system.) (верхній тип)

&lt;img src={never1.src} alt=&quot;Never is a bottom type&quot; class=&quot;max-h-[300px]&quot;/&gt;

### Top type
Top type (верхній тип) - це той тип, який є супертипом для всіх інших. І навпаки, всі інші типи будуть для нього підтипами.
Мені до вподоби таке пояснення: кожен тип в системі типів може бути представлений певною множиною типів - кінцевою або нескінченною.

На малюнку знизу ви можете побачити велику блакитну бульбашку - це нескінченна множина типів, які входять в `unknown`. 



![Unknown set](./unknown-set.png)

В цю бульбашку, наприклад входить бузкінечна множина типів `string`, нескінченна множина типів `number`, кінцева множина типів `boolean` (`true` і `false`), типи `null` і `undefined` та інші.

### Bottom type
Натомість, bottom type (найнижчий тип) - це тип, який не є супертипом для жодного іншого типу. Але всі інші типи - це супертипи для `never`.

Тобто `never` - це пуста множина типів. Якщо ми хотіли б представити `never` на малюнку, то це була б порожня бульбашка, чи навіть відсутність бульбашки. `never` - це нічого. В деяких мовах програмування, таких як Kotlin, Scala та Ceylon - нижній тип так і назвається - `Nothing`.

Повертаючись до попередніх тверджень і аналогії з бульбашкою:

- `never` - не є супертипом для жодного іншого типу&quot; - значить, що жоден інший тип, жодна множина типів, жодна бульбашка не може вміститися в `never`. Ми не можемо вмістити `щось` в `нічого`.


```ts
declare let neverVar: never;

let someString: string = &quot;Hello&quot;;
let someNumber: number = 42;
let someBoolean: boolean = true;

// Спробуємо присвоїти змінній типу `never` значення інших типів
// Ці рядки викличуть помилки компіляції:
neverVar = someString; // Error: Type &apos;string&apos; is not assignable to type &apos;never&apos;
neverVar = someNumber; // Error: Type &apos;number&apos; is not assignable to type &apos;never&apos;
neverVar = someBoolean; // Error: Type &apos;boolean&apos; is not assignable to type &apos;never&apos;
```
[Playground Link](https://www.typescriptlang.org/play/?#code/CYUwxgNghgTiAEEQBd4DsQDcQwGqwC50scBuAKHKVQGcB7AWxAGVkYBLNAcyJrc67wAvPABEACRAQIdURWrx6TAHIBXBgCMcRNOq0xh8ACwAmeSkWMQAITp0kUNEQ12HaQ21UgK5APS-4QEIQQH4QQAEQQD4QQEYQQGEQQBUQQB4QcPgwwA4QQEEQQCYQcMB1EEAhEBT4QHYQeMA1EEBeEDKSwE4QeHzg6PgAAwxsGEaissAGEEBxEEBWEArAeRB4csAJEBTAURBalOCSjL8AwDIQEvhQgcAWEEAuEAKMlI3AbhAU7ujcwBkQZPD4lL2t+A2Lmb2BwDEQEuyCchacfAMRJRZ+bikeD+eAAURgMDoMCIABUAJ4ABwQAHI+BxuMj4OwaOg6KgoDQaOwuGgoBokPBkHRKYiUZ8YMiPiQ8LBDH81JoyMCAuDIdD4PCkfBkbpOQysTi0Hj4ASiSSyRSqTShSLmYz6d82VZbPYQI4gSDeVDYbThS5dY5MdjcfjCcTSeSEErkKbVa1kUA)

- всі інші типи - це супертипи для `never` - значить, що будь-який інший тип, будь-яка множина типів, будь яка бульбашка вміщає в собі `never`. Тому що `never` - це пустота і нічого. Хіба не знайдеться місця для пустоти в будь-якій бульбашці? :)


```ts
let stringVar: string;
let numberVar: number;
let booleanVar: boolean;
let objectVar: object;

// Припустимо, що маємо змінну типу `never`:
declare let neverVar: never;

// Значення `never` може бути присвоєне змінним будь-якого іншого типу:
stringVar = neverVar; // Valid
numberVar = neverVar; // Valid
booleanVar = neverVar; // Valid
objectVar = neverVar; // Valid
```
[Playground Link](https://www.typescriptlang.org/play/?#code/DYUwLgBAzmBOCWA7A5gNQIawFzTk5A3AFCiSICuAtgEYiwbYQU13GkTUD2no6iDOLjxB824CJ2oArEAGMwAidLlhiRAPTqIgfBBAAiCAOEED8IIGEQQIIggIRB9gHhBAfCAAaCIEkQGxCuAGEEAqILYiB2ECuA1EEBeEEDjCEsTCAADRBAANzpIrCIAEzlgTBAIdhj4+kwcHNYiDS1AdBBAt0BxEEBWEGDAeRAowthI1xtANhBqiEBGEGNLCEMDU0AmEBsPQM6-IMDrbuNAFhBAGRAAWjrALhAbQGYQFyDACRBNl3DjJJgEFAYIAF4mOLoGAghNCAxgeGSiZlo82Evr3LuHrTPV5EIS8fiYH5Nf6PIFvSQyeTnK5QzD3GHoF7JIA)


Ці твердження також присутні в наступному [фрагменті документації TypeScript](https://www.typescriptlang.org/docs/handbook/type-compatibility.html#any-unknown-object-void-undefined-null-and-never-assignability) де розглядається таблиця субтипів, в тому числі і для`never`.

## Практичне застосування `never`

### Функції, які ніколи не повертають значення
Перше, що приходить на думку, і те, що ви можете знайти в [офіційній документації](https://www.typescriptlang.org/docs/handbook/2/functions.html#never) - це приклад, коли функції не повертає значення:


```ts
function fail(msg: string): never {
  throw new Error(msg);
}

function infiniteLoop(): never {
    while (true) {
      // Безкінечний цикл
    }
}
```
[Playground Link](https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABMAhjANgCgLYGcDmAXIrlAE4xj4CUxYApgG71mIDeAsAFCKJQAWZOAHdEDUQFEyQsjgLUA3NwC+3bqEiwEiSsEowo9ADJw4AB0y0xTFu269h-DPUSZyIetTs9eie4lUuZSA)

Тут варто звернути увагу на те, що в обох функціях явно вказано, що вони повертають `never`. Якщо прибрати явний return type, то TypeScript неявно присвоїть (infer) тип `void`:

```tsx

function fail(msg: string)/*: void*/ {
  throw new Error(msg);
}

function infiniteLoop()/*: void*/ {
  while (true) {
    // Безкінечний цикл
  }
}

```
[Playground Link](https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABMAhjANgCgLYGcDmAXIrlAE4xj4CUiA3gLABQiiUAFmXAO6JgCmvAKJkuZHAWoBuZgF9mzUJFgJElYJRhR+AGThwADplqMWibuwz9EmciH4nmrVk8Tym75lACeB6wDE0dAAlfigQMjAAFV9rAF5EUPDImL8AHh8-OGBkIIA+L1jEAEkwDTAtXX0DJIjoooTalNiM2Oy1Ms1tPUM8oA)


### Різниця між `void` та `never`
Згідно [документації](https://www.typescriptlang.org/docs/handbook/2/functions.html#void) `void` - представляє значення, що повертається функціями, які не повертають значення. Це виведений (inferred) тип кожного разу, коли функція не має `return` або не повертає жодного явного значення в `return`:
```ts
// The inferred return type is void
function noop() {
  return;
}
```

Різниця між `void` та `never` доволі тонка, 

`void` не гарантує того, що функція дійсно не повертає ніякого значення. `void` гарантує тільки те, що щоб функція не повернула, це значення буде ігноровано і не зможе бути використано в коді.


```ts
type VoidFunc = () =&gt; void;
 
const f1: VoidFunc = () =&gt; {
  return true;
};
 
const f2: VoidFunc = () =&gt; true;
 
const f3: VoidFunc = function () {
  return true;
};

//Error: Type &apos;void&apos; is not assignable to type &apos;boolean&apos;.
const a: boolean = f1()

//Error: Type &apos;void&apos; is not assignable to type &apos;boolean&apos;.
const b: boolean = f2()

//Error: Type &apos;void&apos; is not assignable to type &apos;boolean&apos;.
const b: boolean = f3()
```
[Playground link](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAag9gSwCYDECuA7AxlAvFACgEo8A+KAN0SQG4AoKOrODAZ2CgDMBGALlmrpseQiVzkA3gygAnCMDQyMUYDLQR6AX3qNmbDpwBM-eMiE58xMirUaGTFuy4BmE4MwWuH4AhaioUlCy8orKqupa9HQA9NEAojIycDL8ACrg0ADkVMiZUAisUBhwHACGrKwIAOYYpQBGADbQwHAqGVCZdXBwTaUYmQB0DvpQpfxdPRB9IjzEdDHxiclp7dnUeQVFJaMV1bWNza2gkB0Tvf1Dek51493nM4ZzCwlJKVDpJ2u5+YXFZbs1epNFRHVZnKYXYbXW6Tab4TjOYhAA)


Натомість, `never` гарантує, що функція дійсно не повертає ніякого значення. Це означає, що функція завжди кидає помилку, викликає нескінченний цикл або завершує виконання програми.
```ts
type NeverReturnsFunc = () =&gt; never
type VoidFunc = () =&gt; void

const fail: NeverReturnsFunc = () =&gt; {
  throw new Error()
}

const infiniteLoop: NeverReturnsFunc = () =&gt; {
  while (true) {
    
  }
}

//Error: Type &apos;() =&gt; void&apos; is not assignable to type &apos;NeverReturnsFunc
const voidFunc: NeverReturnsFunc = () =&gt; {
  
}

//Error: A function returning &apos;never&apos; cannot have a reachable end point.
function voidFunc2(): never {
  
}
```
[Playground link](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAchBuEBOAlCwCuSB2BnAYhtgMZQC8UAFAJTkB8U2CyAsAFCiRQBqA9gJYATQiXJVaZBvAGD27YrzzAoAMwCG-ADYAuWM1ToseEaQo16UAN7soUYAAskvAO6MIrgKJInSGuwC+cmwKSlD82Crh-MAQADK8vGC6cIgGmDgERKbiFtZsts72WtCUwEgYELR5trY2UIFsDewA9M1eProAKuDQAOTmklDSQr1huIy8ymq4uPwA5thqAEaa0MC8dj1QvSnIaOnGWfKKuMrDwlnJ+vtGmaJmEgzVUAFBre28SLoAgqpZwPxFFAkIYcOE5tsmKlRsQ1NhsJMoPY1IgoGpgRA1MRkStoBBsIIoGABNhgAA6dgqf6A7BDGQmABMNF0UOQVjqASAA)

Стосовно `void` існує ще один особливий випадок, про який слід пам&apos;ятати - коли ви явно вказуєте `void` в return type, то ця функція не повинна нічого повертати, інакше (очікувано) отримаєте помилку:

```ts
function f1(): void {
  //Error: Type &apos;boolean&apos; is not assignable to type &apos;void&apos;.
  return true;
}

const f2 = (): void =&gt;  {
  //Error: Type &apos;boolean&apos; is not assignable to type &apos;void&apos;.
  return true;
}

type VoidFunc = () =&gt; void
const f3: VoidFunc = () =&gt;  {
  //It works, but looks bad
  return true;
}
```
[Playground link](https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABMAjACgJQC5EDc4wAmiA3gLABQiiA9DQKIBOjcjOAKgJ4AOApogHIARnDgAbXgEMwAxDADOiMHCiJJ8+TADmYSUImIocQz34D8RAQDpK1RryghGSKIxC8A3JQC+lShAR5VWAAJkQAXkRMHAticIA+anIqWgZmVg5TQRFxKRk5RWVVdU0dPQMjEz5BWOtbRHtHZ0M3Tx8-CigsgDUCQgAxcAgIqIwIxNj-QOCAZhxeokHIEcxxpPq6AElVAHdWAGt5ABpEIRBVMVFD08lCesanF1avCm8gA)

### `never` в `switch` та `if`

Доволі цікавим є застосування `never` для того, щоб переконатися, що всі можливі варіанти в `switch` або `if` були враховані:

```ts
type Animal = &quot;cat&quot; | &quot;dog&quot; | &quot;bird&quot;;

function handleAnimal(animal: Animal): string {
    switch (animal) {
        case &quot;cat&quot;:
            return &quot;This is a cat&quot;;
        case &quot;dog&quot;:
            return &quot;This is a dog&quot;;
        case &quot;bird&quot;:
            return &quot;This is a bird&quot;;
        default:
            // Якщо додати інший тип до `Animal`, TypeScript покаже помилку.
            const exhaustiveCheck: never = animal;
            throw new Error(`Unknown animal: ${animal}`);
    }
}
```

У цьому прикладі ми використовуємо `never` для `exhaustiveCheck`, щоб перевірити, що всі можливі варіанти значень типу `Animal` були оброблені. 
Якщо ми додамо новий тип в `Animal` (наприклад, `&quot;fish&quot;`), TypeScript підсвітить помилку, вказуючи, що потрібно додати ще одну умову до `switch`.

Якщо розвивати цю ідею, можна створити допоміжну функцію, яка буде приймати аргумент, якого не має існувати, і викидати помилку:

```ts
function assertUnreachable(value: never): never {
  throw new Error(`Missed a case! ${value}`);
}
```

Тоді попередній приклад можна переписати наступним чином:

```ts
function handleAnimal(animal: Animal): string {
    switch (animal) {
        case &quot;cat&quot;:
            return &quot;This is a cat&quot;;
        case &quot;dog&quot;:
            return &quot;This is a dog&quot;;
        case &quot;bird&quot;:
            return &quot;This is a bird&quot;;
        default:
            return assertUnreachable(animal);
    }
}
```

А втім, якщо ви використовувєте лінтери, зокрема `typescript-eslint`, то в останньому є правило [switch-exhaustiveness-check](https://typescript-eslint.io/rules/switch-exhaustiveness-check/). 
Якщо певні перевірки можна виконати автоматично, то чому б не скористатися цим? :)

### `never` для [виняткового &apos;або&apos;](https://en.wikipedia.org/wiki/Exclusive_or) (виключна диз&apos;юнкція / exclusive or / XOR)

В Typescript &quot;або&quot; (`or`) - це інклюзивне &quot;або&quot;, тобто, якщо ви маєте об&apos;єднання типів (union) `A | B`, то це інтерпритується як `A`, `B`, або обидва.

Це добре іллюструє наступний приклад

```ts
interface TextMessage {
  text: string;
}

interface AttachmentMessage {
  attachment: string;
}

type ChatMessage = TextMessage | AttachmentMessage;

const messageOne: ChatMessage = { text: &apos;Hello, world!&apos; }; 
const messageTwo: ChatMessage = { attachment: &apos;https://example.com/image.jpg&apos; }; 
const messageThree: ChatMessage = { text: &apos;Hello!&apos;, attachment: &apos;https://example.com/image.jpg&apos; }; 
```
[Playground Link](https://www.typescriptlang.org/play/?ts=5.6.3#code/JYOwLgpgTgZghgYwgAgCoQB5gLIQM55wDmKA3gFDLKRYBcyeYUoRA3OQL7nmiSyIoAgmDCIAFgFsI4XAWJlKyOCPFTw9RsxBtO3MAE8ADigDCY5bMIlkAXjSYc+KygA+yYaISTpjuSXbkCAD2IIzIUn4QAPIgEPRmFk7ytsik1A70AOQAEhAANnlBADTIAO5BUHkAJgCEmcgcrMiBIWERzqjl8ea+zilpyp7e6siZYiKGeLQA9NOYcBKGeRAAdMES08AS8isAVoZE9Y3NwaFg4UkkqGJQEHHICb3Jdmk0YFm5BUF1JYOqPllxmBJjM5hgFktVutNtsSHsDkcmuQgA)

Всі три варіант вище будуть валідними, оскільки `ChatMessage` може бути або `TextMessage`, або `AttachmentMessage`, або й тим й тим.

Але якщо ми хочемо, щоб `ChatMessage` був або `TextMessage`, або `AttachmentMessage`, але не обидва одночасно, то ми можемо використати `never`:

```ts
interface TextMessage {
  text: string;
  attachment?: never;
}

interface AttachmentMessage {
  text?: never;
  attachment: string;
}

type ChatMessage = TextMessage | AttachmentMessage;

const messageOne: ChatMessage = { text: &apos;Hello, world!&apos; }; // Valid
const messageTwo: ChatMessage = { attachment: &apos;https://example.com/image.jpg&apos; }; // Valid
const messageThree: ChatMessage = { text: &apos;Hello!&apos;, attachment: &apos;https://example.com/image.jpg&apos; }; // Error: Type &apos;{ text: string; attachment: string; }&apos; is not assignable to type &apos;ChatMessage&apos;.
```
[Playground Link](https://www.typescriptlang.org/play/?ts=5.6.3#code/JYOwLgpgTgZghgYwgAgCoQB5gLIQM55wDmKA3gFDLKRYBcyeYUoRA3JcnGGIgBYC2EcAH56ICADdo7AL7lyoSLEQoAgtz6DwuAsTIcaYUcnFSo7Klx4IBQsPUbMQbcnPJgAngAcUAYV5cOoQkyAC8aJg4+MEoAD7I6ta22tF67OQIAPYgjMiCuiQA8uL0-oGpIeGk1JH0AOQAEhAANs2ZADTIAO6ZUM0AJgCEdcgyrMgA9BPIAGpwzcD9Gdm5+TGoPaUBUQUoVZwaNlr2yHW83F54tFOYcPxezRAAdFn8E8D8ek8AVl5EI2NJtM5gsllkcmA8hUIKheFAIBAtuVdmFkNVDPUmq1MsNOlZNHZ6ucwJdrhNbvdHi9Mm8Pl9fv9RuMpsgAKJQKC9eiobwoOro2oMJgscb4o6EoVONijEbAPAmTKQuAEYBEEBwABGj2omWovNOZR2MTqT3IQA)

Або більш універсальне рішення - допоміжний тип `XOR`:

```ts
type XOR&lt;T1, T2&gt; =
    (T1 &amp; {[k in Exclude&lt;keyof T2, keyof T1&gt;]?: never}) |
    (T2 &amp; {[k in Exclude&lt;keyof T1, keyof T2&gt;]?: never});

type ChatMessage = XOR&lt;TextMessage, AttachmentMessage&gt;;    
```
[Playground link](https://www.typescriptlang.org/play/?ts=5.6.3#code/JYOwLgpgTgZghgYwgAgCoQB5gLIQM55wDmKA3gFDLKRYBcyeYUoRA3OQL7nmiSyIoAgmDCIAFgFsI4XAWJlKyOCPFTw9RsxBtO3MAE8ADigAaAeQBKAHlQBGADRoATAD5kAXkVUAFHeQAyZFIAbQBrZFBkAFEMBAAbAFcAEwgrUIh9AHsYZ0d0rJy7FwBdAH56EAgAN2gOAEpkAB8vZF8nAKCwiJBo2MSUtIzstAdkfOHUVzKK6tq69j0jFABhMWVZQhIPZHNrdCwN+UdhUQRJaRx8TYgXbgRMkEZkKTkSM0r6VfWr+W3SakwYHoAHIABIQOJxTKOADumSgcSSAEJgcgOKxkAB6THIABqcDiwCS5HujzAzx+JFQcM+a0urxQ7iCShUZzUQOQwLEIkMeFo2MwcAkhjiEAAdPcJJjgBJ5GKAFaGIio9FYnH4wnE0lPF7XVBiKAQCC074Mv4Auic8GQzIoxzKU7ndSc7lgXn8zGC4WiiWZKUyuWK5VojHY6JQKDw+ioJac-40DmaFgYh2qC4aJjJtGo4B4ZAgTLkuAEYBEEBwABGouomWoseBX3p12BYvIQA)

Варто ж однак зазначити, що хоча такий підхід є повністню коректним, в TypeScript більш прийнято використовувти [теговані обʼєднання](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions) (tagged unions / discriminated unions)

Тег (tag/discriminant) - це додаткове поле, яке вказує на тип об&apos;єкта. Це дозволяє TypeScript визначити, який тип об&apos;єкта використовується в конкретному випадку. 



```ts
interface TextMessage {
  type: &apos;text&apos;;
  text: string;
}

interface AttachmentMessage {
  type: &apos;attachment&apos;;
  attachment: string;
}

type ChatMessage = TextMessage | AttachmentMessage;

const messageOne: ChatMessage = { type: &apos;text&apos;, text: &apos;Hello, world!&apos; };
```

В прикладі вище, тег - це поле `type`, яке вказує на тип об&apos;єкта. Якщо ми випадково вкажемо обидва поля, то TypeScript підсвітить помилку:

```ts

const messageThree: ChatMessage = { type: &apos;text&apos;, text: &apos;Hello!&apos;, attachment: &apos;https://example.com/image.jpg&apos; }; // Error: Object literal may only specify known properties, and &apos;attachment&apos; does not exist in type &apos;TextMessage&apos;.
```
[Playground link](https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgCoQB5gLIQM55wDmKA3gFDLJgCeADhAFzIDkkWLA3JdZmM3jBRQRbgF9y5UJFiIUAQTBhEACwC2EcLgLEyPWg2Ys4S1RvBceJ5QnWb+yQcJCjyE8gZQBhFSe2ESZABeND5-XWQAH2RFGzstfACIbnIEAHsQQWQNHRIAeRAmZB8-RIiQ0mp6IrY+FgAaXiwjAAkIABt2tMaAdzSodoATAEIG5GszeyMVJTo8RgB6Bcw4NTp2iAA6dLUF4DVdTYArOiIWZDFOIA)

Ба більше, з таким підходом працювати з типом далі набагато зручніше, порівняйте самі:

Припустимо, що в нас є функція, яка обробляє повідомлення. Якщо це tagged union підхід, то це виглядає наступним чином:

```ts
function handleMessage(message: ChatMessage) {
  switch (message.type) {
    case &apos;text&apos;:
      console.log(message.text);
      break;
    case &apos;attachment&apos;:
      console.log(message.attachment);
      break;
    default:
      assertUnreachable(message);
  }
}
```

А якщо ми використовуємо XOR підхід, то це виглядає так:

```ts
function handleMessage(message: XOR&lt;TextMessage, AttachmentMessage&gt;) {
  if (&apos;text&apos; in message) {
    console.log(message.text);
  } else {
    console.log(message.attachment);
  }
}
```

Як на мене, перший варіант виглядає більш читабельним і зрозумілим. Особливо, різниця буде помітна при маштабуванні коду.


### `never` в умовних типах (conditional types)

Де особливо я доволі часто стикався з `never` - так це - при написанні допоміжних типів (utility types). Доречі, є гарний репозиторій - [Type Challanges](https://github.com/type-challenges/type-challenges?tab=readme-ov-file), де ви можете попрактикуватися в написанні таких типів.

Цей допоміжний тип виключає з типу значення `null` та `undefined`, повертаючи never для будь-якого типу, що містить їх.
```ts
type NonNullable&lt;T&gt; = T extends null | undefined ? never : T;

// Приклади використання:
type A = NonNullable&lt;boolean&gt;;            // boolean
type B = NonNullable&lt;number | null&gt;;      // number
type C = NonNullable&lt;string | undefined&gt;; // string

```

Це - копія вбудованного допоміжного типу `Exclude` - він видаляє з типу всі підтипи, які відповідають заданому підтипу.
```ts
type Exclude&lt;T, U&gt; = T extends U ? never : T;

// Приклади використання:
type Union = string | number | boolean;
type ExcludeString = Exclude&lt;Union, string&gt;; // number | boolean
type ExcludeNumber = Exclude&lt;Union, number&gt;; // string | boolean

```

Цей utility тип витягує значення з `Promise`, використовуючи `never` для всіх типів, які не є Promise.
```ts
type PromiseValue&lt;T&gt; = T extends Promise&lt;infer R&gt; ? R : never;

// Приклади використання:
type Value1 = PromiseValue&lt;Promise&lt;string&gt;&gt;; // string
type Value2 = PromiseValue&lt;Promise&lt;number&gt;&gt;; // number
type Value3 = PromiseValue&lt;number&gt;;          // never (не є Promise)
```


### Розподіл над обʼєднаннями ([distribution over unions](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types)) - особливість `never`
Працюючи з умовними типами, можна наткнутися на один з підводних каменів `never` - це те як він розподіляється над об&apos;єднаннями.


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

```ts
type BeOrNotToBe&lt;T&gt; = T extends &quot;Something is rotten in the state of Denmark&quot; ? &quot;Be&quot; : &quot;Not to be&quot;;
```

Якщо ми передамо в цей тип строку `&quot;Something is rotten in the state of Denmark&quot;`, то він поверне `&quot;Be&quot;`, в іншому випадку - `&quot;Not to be&quot;`. 

```ts
type GuesWhat = BeOrNotToBe&lt;&apos;Gues what&apos;&gt;
//   ^? type GuesWhat = &quot;Not to be&quot;
```

Якщо ми передамо в цей тип union, то TypeScript розподілить тип над кожним елементом об&apos;єднання. І приклад нижче поверне об&apos;єднання типів `&quot;Not to be&quot; | &quot;Be&quot;`:

```ts
type MaybeBoth = BeOrNotToBe&lt;&apos;Gues what&apos; | &quot;Something is rotten in the state of Denmark&quot;&gt;
//  type MaybeBoth = BeOrNotToBe&lt;&quot;Gues what&apos;&quot;&gt; | BeOrNotToBe&lt;&quot;Something is rotten in the state of Denmark&quot;&gt;
//  type MaybeBoth = &quot;Not to be&quot; | &quot;Be&quot;
```

Але що буде, якщо ми передамо в цей тип `never`?

```ts
type AndNow = BeOrNotToBe&lt;never&gt;;
// type AndNow = never
```
[Playground link](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAQhDyAnAcge2AFVXAPBgfFALxQZQQAewEAdgCYDOUARAMqoC2EwAFgJY0A5lD5NE6ajRFTe0BsACG1KKgBmUACK0OCxAGtmUAPws4hgFws0wKMFRQARhGYBuAFBvQkKAHEArhAMAOo8SsSwCCjoWLgA5P6BUADuocCx+G4A9JlQuQB6Jl7QCcGp4czWtvZOzJ7g0ACyCiBOMOg84XBI1jEQOPEBTClKsVAAPizsXLwCwqJQ4sCS0rY8corKapraugYZ2blQBbb1voMhYSQV6FWOznXeAIL0aEmdkT3YfTQQAG4QiHw7gO+UKp2edFe4R+-0QQA)

Ми би могли очікувати, що `AndNow` буде `&quot;Not to be&quot;`, але ні.

Чому це так відбувається як цьому запобігти?

Відбувається це тому, що TypeScript розглядає тип `never` як порожнє об&apos;єднання. І якщо немає чого розподіляти, то ви отримаєте порожність назад.

Щоб уникнути неявного розподілу, можна використати `[]`:

```ts
type BeOrNotToBe&lt;T&gt; = [T] extends [&quot;Something is rotten in the state of Denmark&quot;] ? &quot;Be&quot; : &quot;Not to be&quot;;

type AndNow = BeOrNotToBe&lt;never&gt;;
```

[Playground link](https://www.typescriptlang.org/play/?#code/C4TwDgpgBAQhDyAnAcge2AFVXAPBgfFALxQDaGAulBAB7AQB2AJgM5kBEAyqgLYTAALAJYMA5lCFtE6egwlzB0FsACG9KKgBmUACKMeKxAGt2VAPxR2cdlABcltMCjBUUAEYR2AbgBQP0JBQAILMaADuxLAIKOhYuAwQAG4QiPheQA)

Як гадаєте, що буде в `AndNow`? Першою думкою може бути, що це буде `&quot;Not to be&quot;`, але ні. Це буде `Be`. Чому? 

Памʼятаєте, на початку ми визначили, що всі існуючі типи - це супертипи для `never`, тому, коли ми перевіряємо, чи `never` є підтипом для `&quot;Something is rotten in the state of Denmark&quot;`, то виявляється, що це так, `never` є підтипом для будь-якого типу.


Якщо ви не зрозуміли цього з першого разу, не переймайтесь, це досить складний момент, який варто розглянути кілька разів, розуміючи [як працює розподіл над об&apos;єднаннями](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types) (distribution over unions) і як TypeScript працює з `never`.


## Фінал
Вітаю, ви дійшли до кінця цієї статті. Ми з вами розглянули що таке `never` і як його використовувати.  Сподіваюсь, що ця стаття була корисною для вас. Якщо у вас є якісь питання або зауваження, звертайтесь на будь-який із зазначених контактів . Дякую за увагу!
</content:encoded></item><item><title>Type vs Interface. Що обрати?</title><link>https://www.mykytam.com/uk/blog/type-vs-interface/</link><guid isPermaLink="true">https://www.mykytam.com/uk/blog/type-vs-interface/</guid><description>Ключові відмінності і приклади використання</description><pubDate>Thu, 31 Oct 2024 00:00:00 GMT</pubDate><content:encoded>
Якщо ви працюєте з TypeScript, то, ймовірно, вже стикалися з питанням: що краще використовувати для оголошення типів — `type` чи `interface`? Сьогодні ми в цьому розберомось

## Тут тільки `type`

По-перше, варто зазначити, що питання вибору між type і interface не актуальне, коли мова йде про типи, де використання `type` є нашою єдиною опцією. 


### Types

Ключове слово `type` використовується для створення псевдонімів типів (Type aliases). Це означає, що ми можемо створити псевдоніми для будь-якого типу, включаючи:


#### Літеральні типи:

```ts
type LuckyNumber = 13;
type True = true;
type HeWhoMustNotBeNamed = &apos;Voldemort&apos;;
```

#### Примітивні типи:

```ts
type Name = string;
type Choice = boolean;
```

#### Кортежі (Tuples)

```ts
type MagicalCreatureSighting = [creatureName: string, location: string, isDangerous: boolean, numberSeen: number];
```

#### Об&apos;єднання типів (Union types):

```ts
type PowerfulWizard = &quot;Albus Dumbledore&quot; | HeWhoMustNotBeNamed;
```

#### Mapped Types:

```ts
type MyPartial&lt;T&gt; = { [P in keyof T]?: T[P]; };
```

#### Функції:

```ts
type CastSpell = (spell: &quot;Expelliarmus&quot; | &quot;Lumos&quot; | &quot;Alohomora&quot;, target: string) =&gt; void;
```

#### Обʼєкти

```ts
type Wizard = {
  name: string;
  house: &apos;Gryffindor&apos; | &apos;Hufflepuff&apos; | &apos;Ravenclaw&apos; | &apos;Slytherin&apos;;
  age: number;
  knownSpells: string[];
  pet?: &apos;Owl&apos; | &apos;Cat&apos; | &apos;Toad&apos;;
}
```


Тобто за допомогою type ми можемо оголосити будь який типи - на те вони і псевдоніми для типів.

### Interfaces

Натомість, Інтерфейси (interfaces) у TypeScript призначені виключно для визначення структури об&apos;єктів і функцій. 

Тому питання вибору між `type` і `interface` виникає лише тоді, коли ми працюємо з *об&apos;єктами* або *функціями*. В усіх інших випадках, коли мова йде про інші типи, наша єдина опція — це `type`.


## Функції

Що ж стосується функцій і об&apos;єктів? У випадку з функціями питання вибору, як правило, не виникає, оскільки більшість розробників надають перевагу використанню псевдонімів типів (type aliases) через їх більш явний і зрозумілий синтаксис.

Для порівняння:

```ts
type CastSpell = (spell: &quot;Expelliarmus&quot; | &quot;Lumos&quot; | &quot;Alohomora&quot;, target: string) =&gt; void;

interface CastSpell { 
   (spell: &quot;Expelliarmus&quot; | &quot;Lumos&quot; | &quot;Alohomora&quot;, target: string): void;
}
```

Як бачите, синтаксис `type` для функцій виглядає простіше і зрозуміліше.


## Об&apos;єкти
А ось для обʼєктів думки можуть різнитися і крім синтаксису є і більш вагомі відмінності, але почнемо ми саме з нього


### Синтаксис


1. Для `type` -  після назви ми ставимо знак рівності, для `interface` - не ставимо

```ts
type Animal = {
     name: string
}

interface Animal {
    name: string
}
```

2. Для розширення `type` ми вокристовуємо знак амперсанду (в Typescript він назвається `intersection`) - іншими словами, створюємо перетин типів (intersection type). А для розширення `interface` ми використовуємо звичне для ООП ключове слово `extends`

```ts
type Goat = Animal &amp; {
    isSmart: true
}

interface Sheep extends Animal {
  isSmart: false
  isCute: true
}
```


### Швидкість

Розібравшись синтаксисі, давайте подивимось на більш вагомі відмінності, і почнемо з тих, про які можно прямо [прочитати](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces) в *документації Typescript &gt; Handbook &gt;  Everyday Types*

Почнемо з швидкості:

&gt; Using interfaces with extends [can often be more performant for the compiler](https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections) than type aliases with intersections 

&gt; Використання інтерфейсів з extends [часто може бути продуктивнішим для компілятора]((https://github.com/microsoft/TypeScript/wiki/Performance#preferring-interfaces-over-intersections)), ніж type aliases із перетинами (intersections).


І в посиланні на те як оптимізувати швидкість в Typescript - першою порадою ми бачимо використовувати *interface extends*  замість *type intersections*.

То ж, що значить на практиці ця різниця в швидкості і коли вона може бути відчутна?

Поперше, треба розуміти, що це не той performance, який впливає на клієнтський досвід користування вашим продуктом - додатком, вебсайтом, тощо.


Це швидкість того, як компілюється ваш TS код. Тож негативний вплив на performance в данному випадку - це збільшення часу, за який буде проходити type check і будуть створюватися артефакти збірки (файли *.js* і *.d.ts* ). 

Це performance, який може впливати на ваш досвід яки розробника (developer experience/DX) .І найбільшу різницю можна буде відчути наприклад тоді, коли у вас великий проект, який треба регулярно збирати (наприклад, як частину CI процессу), що в свою чергу потребує компіляції всього TS коду. 

Наприклад в цьому репозиторії https://github.com/edishu/intersect порівнюється час на компіляцію 10 тисяч інтерфейсів, які рекурсивно використовують *extends* і 10 тисяч *type intersection* -  як результат варіант з інтерфейсами займає **1 min 26 sec 629 ms**, варіант з *intersections* - **2 min 33 sec 117 ms**. 




### Declaration merging

Наступна цікава відмінність між `types` і `interfaces` - це те що назвається Declaration merging - який властивий **тільки інтерфейсам** і в базовому прикладі працює так - якщо ви оголосите інтерфейси з одним імʼям декілька разів, TypeScript автоматично об&apos;єднає їх в один інтерфейс.

```ts
interface Animal {
  species: string;
  sound: string;
}

interface Animal {
  favoriteFood: string;
  canFly: boolean;
}

const cat: Animal = {
  species: &apos;Cat&apos;,
  sound: &apos;Meow&apos;,
  favoriteFood: &apos;Fish&apos;,
  canFly: false // Not yet, but we&apos;re working on it!
};  // OK
```

Ми не будемо тут детально розглядати для чого потрібна така незвична фіча і особливості її використання, зробимо це в окремій статті, натомість зазначимо, що для більш менш повсякденних задач *declaration merging* це радше те, що може призводити до небажаних помилок, наприклад, якщо ви не помітили існуючий інтерфейс, і випадково продублювали його десь нижче в файлі, що приведе до того, що в цьому інтерфейсі будуть властивості із двох оголошенних інтерфейсів. 

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

### Помилки 

Наступна різниця - це те що *інтерфейс з extends* краще обробляють помилки ніж *type з intersection*. Про це теж можна [прочитати](https://www.typescriptlang.org/docs/handbook/2/objects.html#interface-extension-vs-intersection) в *документаціі Typescript &gt; Handbook &gt; Objects* 

Давайте розглянемо наступний приклад

[Playground](https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgArQM4HsTIN4BQyyIcAthAFzIZhSgDmA3EcnA1TXYywL4EEwATwAOKACroo2XAF40mHMgBk+Nh2ogArmQBG0Jsl6HkAelMksyaFCxQANMi3aMcXQBsUwsQIAmEBHc4KBQEHFpkMCkZaklFEAEo+IA6dggBUEhYRBQASWilCAAPSBBfDAVpJUJic2J65AA-ZpbG5FzwaHgkZAByfPje5FAwqBCEMHchaxKIMorMrpy+gpBe5NY6hu2d8VEICqwYZBFbMShhPrSh4JQRrDIRODBgDwgN2osd74a9sT7tHpoENgBUQFgwGwMBhgAxSG9IlZvCherR6CAGOtWGlNDp9FA+AQgA)

Припустимо у нас є `interface Person`


```ts
interface Person {
  name: string;
  age: string;
}
```

Якщо ми спробуємо розширити інтерфейс *Person*, але при цьому перепишимо тип будь-якої властивості на несумісний тип, то ми отримаємо ось таку помилку

```ts
interface IPerson extends Person {
  //      ~~~~~~~ Interface &apos;IPerson&apos; incorrectly extends interface &apos;Person&apos;.
  //                Types of property &apos;age&apos; are incompatible.
  //                  Type &apos;number&apos; is not assignable to type &apos;string&apos;.
  age: number;
}
```

Якщо аналогічну дію проробити з *types i intersection* - то помилки в нас не буде, натомість властивість `age` отримає тип `never`.

```ts
type TPerson = Person &amp; { age: number; };  // no error, unusable type
```


І тут більш бажаною поведінкою буде те, як працює `interface`, тому що ми швидше зможемо виявити помилку



### Index signature

Наступна невелика відмінність - це те що type alias-es мають неявну індексну сигнатуру (implicit index signature),  а інтерфейси - ні. Про що йде мова, краще показати на прикладі

[Playground](https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgIImAWzgG2QbwChlkw4BzALmQHI4NscaAaY5EOTCamgZwHsuyellw1CAX0KEAJhAQ44UFADclwhrmrpROANzSE-ELzDIAFvRk4IAJXn8oM5AF5kACn4AjAFaV7Rk4APKZQoOTMyACuIADWIPwA7iAAfACUrikEyFKEAPR5yADCliDkKAAGoJCwiBAVpPzIFWAAngAO9RrO0FCOyInAOHheKMoCOCoQMoRGJmbjUThmbqXWdg5O7iKMGQXIvY5AA)


```ts
interface Animal {
  name: &apos;some animal&apos;
}

declare const animal: Animal;

const handleRecord = (obj:Record&lt;string, string&gt;) =&gt; { }

const result = handleRecord(animal)
//                          ~~~~~~~ 
//    Argument of type &apos;Animal&apos; is not assignable to parameter of type &apos;Record&lt;string, string&gt;&apos;.
//      index signature for type &apos;string&apos; is missing in type &apos;Animal&apos;.(2345)
```

В данноми прикладі в `handleRecord(animal)` ми отримаємо помилку

Помилка тут зʼявляється через те, що інтерфейси можуть бути розширені за допомогою declaration merging  і не факт, що нові властивості *Animal* будуть входити в рамки `Record&lt;string,string&gt;`

Щоб це виправити, нам треба або явно  додати індексну сигнатуру в `Animal`, на кшталт

```ts
interface Animal {
  name: &apos;some animal&apos;
  [key: string]: string
}
```

або ж просто заміити `interface` на `type`

```ts
type Animal = {
  name: &apos;some animal&apos;
}
```


### Імʼя в помилках

Ще є супер маленька відмінность в тому, як показуються помилки, але ця відмінність настільки незначна, що не факт, що ви побачити різницю з першого погляду

[Playground](https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgLJwLYbgG2QbwChkTkRMIAuZAZzClAHNCBfQwsATwAcUBBEMGx4AvAWKlyGKrXpNW7GAFcQCMMAD2IZBAQALDeiy4AFBmpHhASnGlkCLTQ04IAOhwbGZ11IhWFhLoGlqb4ZBTUAIwATADMACyxyCz+iipqmtpBGgJCpubIuda2pA4gTi7unt6+-myB+jmCwiZhvlFx8QCsyVZAA)

## Подібність

Це все по відмінностям, в усьому ж іншому, все що стосується обʼектів - `interface` і `type` - поводяться однаково, наприклад

- Вони можуть бути дженеріками, 
- `interface` може наслідувати `type`
- класс може реалізувпти як `interface` так і `type`.

## Висновки

Cеред великих відмінностей між інтерфейсами і псевдонімами для типів є два вирішальні фактори, на яки б я звернув би увагу:

1. Інтерфейси переважають в performance. 
2. З іншого боку в них є механізм *delcaration merging*, що може приводити до несподіваних помилок. 

Всі ж інші відмінності на мй власний розсуд не такі суттєві.

Офіційна документація рекомендує використовувати interface до тієї пори, поки вам в конкретному випадку не знадобляться ті функції, які є у *type alias*, і яких бракує інтерфейсам, наприклад *mapped types* або *union types*. 

&gt; For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs something to be the other kind of declaration. If you would like a heuristic, use `interface` until you need to use features from `type`.

Багато статей від різних авторів на просторах інтернету рекомендують усюди використовувати `type`, щоб бути послідовним і не наражати себе на небезпеку випадкового розширення інтерфейсів за допомогою *declaration merging*.


Я б порекомендував робити наступним чином

- Якщо у вас legacy проект - використовуйте те, що прийнято писати на цьому проекті.

- Якщо це новий проект - використовуйте те, що вам більше до вподоби, тому що навіть ті “суттєві” відмінності не такі вже і суттєві. 
  - Якщо у вас нема вподобань, і це буде більш-менш великий проект, для якого буде важливим час компіляції Typescript-a, то віддайте перевагу interface,в іншому ж впадку використовуйте type :)

Дякую за увагу!
</content:encoded></item></channel></rss>