Содержание

Type Script

Це суперсет над JavaScript. Це означає, що будь-який дійсний код JavaScript також є дійсним кодом TypeScript. Він додає типизацію до JavaScript.

Встановлення

  1. Встановити nodejs
  2. Вставноити VSCode
  3. Встановити зборщик Vite
  4. В зборщику Vite створюємо новий проект «Vanila TypeScript»
  5. За необхыдныстю налаштувати компілятор за офіційною документацією або за курсом

Приклад базових налаштувань

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,


    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,


    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src"]
}

Рефакторінг проекту з React на TypeScript + React

Міграція проєкту на TypeScript

Типизація

Базова

обєект

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

let user: User = {
  name: 'Tom',
  age: 30,
};

let userJack: User = {
  name: 'Jack',
  age: 25,
};

export {};

Масив

let arrNumber: number[];
let mixed: (number | string)[] = [1, 'two'];
let arrAny: any[];

Any

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

let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;
notSure = {};

Unknown

function fetchUserData() {
  return 'Tom';
}

let userData: unknown = fetchUserData(); // fetchUserData повертає невідомі дані
if (typeof userData === 'string') {
  console.log(userData.toUpperCase()); // OK, тепер ми знаємо, що це рядок
}

Tuple (кортедж, фиксированній массив)

Попри те, що кортежі фіксують число та типи елементів, метод push може бути використаний для додавання елементів до кортежу.

let tupleType: [string, boolean];
tupleType = ['hello', true]; // OK
tupleType = [true, 'hello']; // Error. Неправильні типи
tupleType = ['hello', true, true]; // Error. Більше значень ніж у tuple

Enum

Enum являє собою набір констант, що робить код більш зрозумілим. Як ми бачили у минулому прикладі, значеннями enum зазвичай є числа, проте ми можемо задати свої значення.

enum Role {
 ADMIN,
 USER,
}

console.log(Role.ADMIN); // 0
console.log(Role[Role.ADMIN]); // "ADMIN"
enum UserStatus {
 Active = 'ACTIVE',
 Inactive = 'INACTIVE',
 Banned = 'BANNED',
}
let status: UserStatus = UserStatus.Active;

Існує ще така конструкція, як const enum. На відміну від звичайного enum, const enum видаляється під час транспіляції та не створює додаткового об'єкта в JavaScript. Значення const enum вставляють у місце використання у вигляді літералів. Це може допомогти покращити продуктивність.

const enum HttpCodes {
  OK = 200,
  BadRequest = 400,
  Unauthorized = 401,
}

const status = HttpCodes.OK;

Union Type

Union Type у TypeScript дозволяє вказати, що значенням може бути один із кількох типів. Це дуже зручно, коли хочемо визначити змінну, яка може приймати різні типи даних. Типи перераховуються через вертикальну риску |

let mixedType: string | number | boolean;

mixedType = 'string'; // OK
mixedType = 10; // OK
mixedType = true; // OK
mixedType = {}; // Error: Type '{}' is not assignable to type 'string | number | boolean'.

Intersection Type

Intersection type є способом об'єднання декількох типів в один. Це дозволяє створювати складні типи, комбінуючи прості. У TypeScript можна використовувати символ & для створення типу intersection.

type Employee = {
  name: string;
  id: number;
};

type Manager = {
  employees: Employee[];
};

type CEO = Employee & Manager;

const ceo: CEO = {
  name: 'Alice',
  id: 1,
  employees: [
    {
      name: 'Bob',
      id: 2,
    },
  ],
};

export {};

Literal Type

Literal Type — це тип, що набуває конкретного значення. З ним ви можете визначити тип змінної так, щоб він набував лише певних значень. Тобто може містити лише одне з можливих рядкових значень.

type OneOrTwo = 1 | 2;
let value: OneOrTwo;
value = 1; // OK
value = 2; // OK
value = 3; // Error: Type '3' is not assignable to type 'OneOrTwo'.

type YesOrNo = 'yes' | 'no';
let answer: YesOrNo;
answer = 'yes'; // OK
answer = 'no'; // OK
answer = 'maybe'; // Error: Type '"maybe"' is not assignable to type 'YesOrNo'.

export {};

Return Type

Return type — це тип даних, який функція повертає під час її виклику. TypeScript дозволяє вказувати тип значення, що повертається для функцій, що допомагає зробити ваш код більш зрозумілим і безпечним.

function greet(): string {
  return 100; // Error: Type 'number' is not assignable to type 'string'
}

let result = greet();
const greet = (): string => {
  return "Hello, world!";
}

let result = greet();

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

function greet() {
  return 'Hello, world!';
}

let result: string = greet();

export {};

Void

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

function logMessage(message: string): void {
  console.log(message);
}

logMessage('Hello, world!');

export {};

Never

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

// Функція, яка завжди викидає помилку
function throwError(message: string): never {
  throw new Error(message);
}

// Функція з нескінченним циклом
function infiniteLoop(): never {
  while (true) {}
}

export {};

Function Type

let myFunc: (firstArg: string, secondArg: number) => void;

myFunc = (first: string, second: number) => {
  console.log(`First: ${first}, Second: ${second}`);
};

myFunc('Hello', 42); // Висновок: "First: Hello, Second: 42"

export {};

Інтерфейси

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

У TypeScript інтерфейси відіграють ключову роль статичної типізації. Вони допомагають забезпечити узгодженість та чіткість структури об'єктів чи класів.

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

interface Person {
  firstName: string;
  lastName: string;
  age?: number; // Необов'язкове поле
}

function greet(person: Person) {
  console.log(`Hello, ${person.firstName} ${person.lastName}`);
}

const john: Person = {
  firstName: 'John',
  lastName: 'Doe',
};

greet(john); // Виведе: "Hello, John Doe"

Використання сторонніх бібліотек

Іноді ви можете зіткнутися з бібліотекою, що не підтримує TypeScript з коробки. У цьому випадку вам потрібно встановити окремі визначення типів для цієї бібліотеки.\

DefinitelyTyped

це репозиторій на GitHub, у якому спільнота TypeScript підтримує визначення типів для тисяч JavaScript-бібліотек. Наприклад, для бібліотеки react-router-dom команда виглядатиме так:

npm install --save-dev @types/react-router-dom

Хуки, які зазвичай не потрібно типізувати

useEffect

useEffect очікує, що функція, що передається, буде повертати void або функцію очищення, яка теж повертає void. Усі ці типи ми можемо не вказувати, і просто писати так:

useEffect(() => {
    let isActive = true;

    return (): void => {
      isActive = false;
    };
  }, []);

useMemo

У цьому прикладі ми використовуємо хук useMemo для оптимізації продуктивності. Ми створюємо мемоізоване значення selectedUser, яке перераховується лише за зміни users або selectedUserId.

import React, { useMemo } from 'react';

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

type Props = {
  users: User[];
  selectedUserId: number;
};

export function UserList({ users, selectedUserId }: Props) {
  const selectedUser = useMemo(() => {
    return users.find(user => user.id === selectedUserId);
  }, [users, selectedUserId]);

  return (
    <div>
      {selectedUser && <p>Selected user is {selectedUser.name}</p>}
      {users.map(user => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  );
}