Frontend [TypeScript] 1

Тут

Type vs Interface

Як інструменти типізації у багатьох випадках різниця буде непомітною.

Все окрім “implements" - можна реалізувати через “type";

type User = { name: string; age: number; };

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

Багато людей використовує наступні правила:

  1. Для об’єктів - інтерфейси

  2. Для всього іншого - типи


interface IUser {
  name: string;
  age: number;
  status: "registered" | "blocked";
}

Такий код добре читається, але якщо замінити “interface" на “type" то великої різниці не буде, професіонали називають це - “вкусовщина".

type Status = "registered" | "blocked";

interface IUser {
  name: string;
  age: number;
  status: Status;
}

Опираючись на правила які описані вище саме так потрібно використовувати взаємодію “type" та “interface"


Type Merging

interface IUser {
  name: string;
}

interface IUser = {
  age: number;
}

const user: IUser = {
  name: 'Jhon',
  age: 27,
} // OK

тепер типи

type User = { name: string };
type User = { age: number };

// уже на цій стадії буде помилка (Duplicate identifier 'User'.)

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

Tuples

interface ITuple extends Array<string | number> {
 0: string; 
 1: string; 
 2: number;
}

Виглядає кріпово і незрозуміло - не робіть так.

Варіант для нормальних людей.

type Tuple = [firstName: string; lastName: string; id: number];

При порівнянні 2й варіант виглядає значно краще і читабильніше, звісно можна написати просто:

type Tuple = [string; string; number];

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

Я не буду розповідати всі комбінації можливих реалізацій через type чи interface - це тема окремої статті. Тут лише хочу донести головні ідеї які вплинуть на читабельність та швидкість писання коду.


keyof | typeof | in

keyof

Порівнюючи з JavaScript у якому є метод об’єкту Object.keys() у TypeScript маємо keyof який повертає набір [“union"] не індексованих значень об’єкту або масиву (у JS теж є об’єктом).

type EntityType = {
  name: string;
  age: number;
  id: number;
}

type EntityTypeKeys = keyof EntityType; // 'name' | 'age' | 'id';

Тепер можна використовувати EntityTypeKeys як окремий набір значень.

type CapitalizedType = {
  [k in EntityTypeKeys as `${Capitalize<k>}`]: EntityType[k];
}
// { Name: string; Age: number; Id: number; }

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

typeof

якщо вам потрібно перетворити уже створений об’єкт у тип, можна використовувати typeof

const user = { name: 'Json', age: 27 };

type UserType = typeof user;

// { name: string; age: number }

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

Якщо поле об’єкту може бути двох типів то через оператор typeof ми отримаємо не коректний тип.

const user = {
  name: 'Jhon',
  age: 27,
  status: 'registered' // can be number (1, 2, 3)
}

type UserType = typeof user; 
// { name: string; age: number, status: string }

type CorrectUserType = Omit<UserType, 'status'> & { 
  status: string | number;
};

// { name: string; age: number, status: string | number }

in

Для перевірки чи дійсно є поле у об’єкту, можна використати декілька варіантів.

type Employee = {
  name?: string;
  department?: string;
  country?: string;
};

const emp: Employee = {};

// 👇️ (property) department?: string | undefined
emp.department

if (emp.department !== undefined) {
  console.log(emp.department.toLowerCase());
}

if (emp.hasOwnProperty('department')) {
  console.log(emp.department.toLowerCase());
}

// ---

if('department' in emp) {
  console.log(emp.department.toLowerCase());
}

Перший варіант не коректний, він не перевіряє чи є таке поле у об’єкті, насправді він перевіряє чи значення цього поля дорівнює undefined, що не правильно.

Другий варіант правильніший для перевірки існування поля у об’єкті, навіть якщо значення undefined.

Третій варіант краще використовувати для швидкої перевірки існування певного поля у об’єкті, але він не дуже надійний.

const entity = { name: 'Jhon' };

'constructor' in entity    // true
'__proto__' in entity      // true
'hasOwnProperty' in entity // true

// У той час як hasOwnProperty

entity.hasOwnProperty('constructor');    // false
entity.hasOwnProperty('__proto__');      // false
entity.hasOwnProperty('hasOwnProperty'); // false
Потрібно пам’ятати при роботі з подібними назвами полів, для себе я порівнюю роботу “hasOwnProperty” та “in" як різницю між “for of" та “for in" відповідно.“in” варто використовувати якщо ви впевнена у собі людина.

Оператори

& (Intersection)

& - поєднання типів. Щось типу впевненої у собі кон’юнкції.

type A = B & C;
B, C - можуть бути як типами так і інтерфейсами, але однакової структури.

Якщо структура відрізняється - отримаєте помилку.

extends

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

interface A extends B {
  field: string
}

// --

interface A extends B, C {};

Також можна наслідуватись від об’єктних типів, але не від Mapped Types

Type A = { field: string };
Type B = { count: number };

interface C extends A, B {}

const entity: C = {} // { field: string; count: number; }

| (Union)

Оператор - union, використовується для декларації можливих варіантів типів.

type A = B | C;

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

-? та +?

Поля об’єкту можуть бути обов’язковими або опціональними, для таких полів можна використати наступні типи.

type Entity = { name: string };

type OptionalEntity = Partial<Entity>; // всі поля стали не обов'язковими

type RequiredEntity = Required<OptionalEntity>; // всі поля стали обов'язковими

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

type CustomPartial<D> = {
 [k in keyof D]+?: D[k];
}

// або просто

type CustomPartial<D> = {
 [k in keyof D]?: D[k];
}

і навпаки

type CustomRequired<D> = {
 [k in keyof D]-?: D[k];
}

Інколи потрібно створити вкладені типи, у таких випадках це буде корисним.

Підсумки

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

Для того щоб називати себе професіоналом певної галузі, вам варто розуміти всі її аспекти, навіть якщо це щось не явне або абстрактне.

У наступних статтях будемо розбирати складніші та набагато цікавіші речі, про які уже не так багато інформації у документації.

UPD: не було натхнення писати про “implements" тож допишу як з’явиться 🙃

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Valentyn Tupota
Valentyn Tupota@valentyn_tupota

Software Engineer

1KПрочитань
3Автори
33Читачі
На Друкарні з 11 жовтня

Більше від автора

  • Neural Network [guide] 1

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

    Теми цього довгочиту:

    It
  • Frontend [TypeScript] 1

    TypeScript - Як писати код швидше та надійніше.

    Теми цього довгочиту:

    It
  • [Frontend] Універсальні рішення

    Потряпляючи у пастки антипатернів велику кількість разів ви навчитесь писати гарні “універсальні рішення".

    Теми цього довгочиту:

    It

Вам також сподобається

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

Підтримайте автора першим.
Напишіть коментар!

Вам також сподобається