Галоўная / Артыкулы / Асаліны TypeScript: ад першай анатазыі да гэнерыкаў і строгага режыму

Асаліны TypeScript: ад першай анатазыі да гэнерыкаў і строгага режыму

Структураваны апавядкі гэтасяброўства типаў у TypeScript — ад прымітываў і ўзнікнення типаў да разліковых аб’еднанняў, гэнерыкаў, типаў-засобаў і строгага tsconfig.

3750 слоў

Кожны разработчык JavaScript знае гэты шаблон: код працуе локальна, аплывае на сервер, і чэрез два дні прыходзіць адказ пра баг, таму што функцыя прабрала об’ект заместа строкі, або ў вычысленні патрапіла значэнне undefined. JavaScript не заважае вам пісаць такі код; ён проста выконваецца некоректна, зазвычай у режыме роботы, таму што код запускаецца ў продакшэне. TypeScript пераносіць гэтую проблему на момент, калі вы пісаеце адпаведныя рэквізіты коду. Шырокі практычны карцэг прыводзіць вас ад першага запісу let x: number через механізмы звужэння типаў, дискримінаваных аб’юній, гэнерыкаў і типаў-выконавіцоў да налашоўкаў кампайляра, якія вядомаюць, насколькі захоплення вы насправды атрымуеце, тады як вы зможаце з адной упэўненасцю чытаць і пісаць код на TypeScript для продакшэна.

Што такое TypeScript і чаго ён не ўсё

TypeScript — это мова з адзвярнутым кодам ад Microsoft, якая дадае JavaScript статычную систему типаў. Пад «статычным» тут маесяцца на увазе, што кампайляр перакантрацуе типы ўсё раней чым запускае програму, у вашам рэдагары или пад час кампайлявання, а не пасля ўжывання. Тры факты вялікай меры адпраўляюць усё іншае:

  • Цяперашняя мова ёсць супернабор JavaScript. Будзь-які сынтаксычна правільны код на JavaScript таксама є правільным сынтаксычна на TypeScript, таму вы проста расширяеце тое, што вядома ўжо, а не пачынаеце з нуля. Кампайляр усё ж можа паведаміць пра аблыканні ў таком кодзе, і гэта самэ прычына.
  • Ён кампайляецца у звычны JavaScript. Браузеры і Node.js запускаюць JavaScript, таму кампайляр tsc адключае всі анотацыі і стварае звычныя файлы .js.
  • Тыпы зникаюць пад час выканання. Яны існуюць для захавання вас пад час разработкі. На адзін з Java, дзе інфармацыя пра тыпы застаёцца ў скомпіляванам байткодзе, пасля запуску програмы нічога пра тып TypeScript не застаёцца.
  • Разлік у адной функцыі

    Ёсць звычная функцыя JavaScript, якая дадзеўнае два значэння:

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

    Калі яе вызваць з рэчым і цяжарам, яна не выклікае памылак. Яна з’едначае іх, і вы заўважыце гэта толькі тады, калі сума на рахунку-фактуре будзе незвычайная:

    add("10", 20); // "1020" — silently wrong
    

    Указванне тыпа параметра і рэзультата чыста адзначае умовы викорыстання функцыі:

    function add(a: number, b: number): number {
      return a + b;
    }
    

    Тепер той самы вызов будзе адхілён, калі вы яшчэ рэдагуеце код, пры чым памылка будзе паказана адразу пад некоректным аргументам:

    add("10", 20);
    // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
    

    Стварэнне проекта

    Пасля установкі Node.js вы можете установіць кампайлер глобальна і пераканацца, што ён працюе:

    npm install -g typescript
    tsc --version
    

    Для рэальных проектаў установіце TypeScript як локальную залежнасць для разработкі, каб усі учаснікі тыму і сервер CI выкарыстоўвалі аднаковую версію, пасля чаго створыце файл налаштавання:

    npm install typescript --save-dev
    npx tsc --init
    

    Створанный файл tsconfig.json яўляецца панеллю керування, якая выбірае, насколькі строгім і савременным должна быць кампайляр. Няўзабавныя версіі tsc --init вже актываюць режым strict, а TypeScript 6.0 зробіў строгі режым стандартным, але старэйшыя проекты часта працуюць з менш строгімі налаштаваннямі, якія пазней допраўляюцца. У роздзеле налаштавання нижэй гаворыцца пра гэтым.

    Мінімальны першы файл адзначае зменную з типам і выводзіць яе:

    // app.ts
    let message: string = "Hello, TypeScript";
    console.log(message);
    

    Скомпілюйце яго, а пасля запустыце створаны JavaScript:

    tsc app.ts     # produces app.js
    node app.js    # Hello, TypeScript
    

    У практыцы большэйшыя проекты ухілляюцца ад такога двухэтапнага падходу і запускаюць файлы за допаможчынай ts-node або tsx, альбо выкарыстоўваюць інструменты для збірання коду, такія як Vite, esbuild чытае webpack, які компілюе код у рэальны час. Іноды цяпер жа зробіць гэта вручную мае сенс, адколі гэта дапамага закрепіць правильную канцэпцыю: увайшае TypeScript, а выходзіць JavaScript.

    Базовыя типы і ўзнакомленне

    Прімітывы і масівы

    Анотацыі прімітываў — це string, number і boolean:

    let username: string = "sanajit";
    let age: number = 26;
    let isActive: boolean = true;
    

    Масівы пішучыся з типам элемента, які следуе за квадратнымі скобкамі:

    let scores: number[] = [90, 85, 78];
    let tags: string[] = ["typescript", "javascript"];
    

    Генерычны формат Array<number> означае абсолюйна тое ж самае; выберыце адны стыль і настойчыва яго выкарыстоўвайце:

    // equivalent generic syntax
    let ids: Array<number> = [1, 2, 3];
    

    Дазвольце ўзнакомленню выканаць звычную роботу

    Рэдкасць, калі трэба адзначаць заснаваныя зменны. TypeScript выважвае тып на адказе і пасля чаго прыменяе яго:

    let city = "Ahmedabad"; // inferred as string
    city = 42;              // Error: Type 'number' is not assignable to type 'string'
    

    Запіс let city: string = „Ahmedabad“ не ўскладнена, проста зайвая. Хорашыя правілы для всіго языка: паважаць выважванне, калі адказ робіць тып явным, і пісаць явныя тыпы там, дзе адказ є неодназначным або калі вы визначаеце контракт, такі як параметры функцый, тыпы адказаў, порожнія масівы і сігнатуры калебакаў.

    any, unknown, never і void

    Этыя чатыры спецыяльныя тыпы ствараюць проблемы практычна кожнаму пачатківцу:

    • any выключае перакананне тыпу для адказа. Це спосаб выйсця з проблемы, а не справжні тып, і частае ўжыванне яго — гэта найпашырэнейшы спосаб, калі кодавая база ператвараецца на JavaScript з дадзенай синтаксысам.
  • unknown — гэта безпечны аналаг. Яму можа быть прызначана будзь-яка значэнне, але яго нельга выкорыстоўваць, пакуль яго не будзе адганяча перапыткай. Гэта правы выбар для дадзенняў, форма якіх ўсё ще не вядома, напрыклад, для адпаведзення API перад паўнай верыфікацыёй.
  • never — гэта значэнне, якое не можа існаваць, напрыклад, рэзультат функцыі, якая завжды выклекае або ніколи не вяртае значэнне. Яго самая практычная наявнасць — у перапытках выключнай ўсе можлівасці, калі компілятор павінен пераканацца, што кожны варыянт switch адобразжаны.
  • void — гэта пазначэнне для функцыі, якая не вяртае нічога корыстнага, напрыклад, для обробні адбуванняяў чы пакрыцца для логавання.
  • За дапамогою unknown перапытка typeof адкрывае метадзеі для строк толькі ў захаванай галузі:

    function process(value: unknown) {
      if (typeof value === "string") {
        console.log(value.toUpperCase()); // safe — narrowed to string
      }
    }
    

    Функцыя, яка завжоды выклікае аберанцію, мае тип never, тады як функцыя, яка проста выконвае побачны эфект, вяртае void:

    function fail(message: string): never {
      throw new Error(message);
    }function logAction(action: string): void {
      console.log(`Action: ${action}`);
    }
    

    Па конкрэтныя способы замены any у распашчытых ситуаціях, адзірніце шасць спосабоў безпечнай замены any.

    Апісаванне об’ектаў за дапамойкай інтэрфейсаў та псевдонімаў типаў

    Форму об’екта можна запісаць унутрошняя:

    const employee: { id: number; name: string; active: boolean } = {
      id: 1,
      name: "Amit",
      active: true,
    };
    

    Это працюе аднойчы, але павтарэнне той самай унутрошняй формы ў всіх месцах быстра становіцца непатрэбным шумам. Назваўшы форму interface або type, можна гэта рашыць.

    Інтэрфейсы

    Інтэрфейс называе форму об’екта, ўжо каб можна было яго перысваяць:

    interface Employee {
      id: number;
      name: string;
      department: string;
    }
    

    Кожны об’ект, анотаваны ім, должен падходзіць да заявленых полей:

    const employee1: Employee = { id: 1, name: "John", department: "IT" };
    const employee2: Employee = { id: 2, name: "Sara", department: "HR" };
    

    Знак запытання пазначаець атрыбут, які можа не існаваць:

    interface User {
      id: number;
      name: string;
      phone?: string; // may or may not be present
    }
    

    Атрыбуты readonly можна прызначаць пад час стварэння об’екта, але не пасля таго:

    interface Product {
      readonly id: number;
      name: string;
    }
    

    Прабаўка перапрызначыць такі атрыбут вызывае памылку часу кампайлявання:

    const laptop: Product = { id: 100, name: "MacBook" };
    laptop.id = 200; // Error: Cannot assign to 'id' because it is a read-only property
    

    Інтэрфейсы таксама можу распрастарвацца адна ў аднаго, што дазволяе дзеліцца спяльнымі полямі без ўсунення іх. Пачніце з базовага шэйпу:

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

    Потым створыце болей спецыфічны шэйп на його аднойчыне:

    interface Employee extends Person {
      employeeId: number;
      department: string;
    }
    

    Аліясы типаў

    Аліяс type таксама можа даваць назву формам об’ектаў, але не ўскладнены толькі імі. Юніі, прымітывы і туплы таксама можна назваць такім спосабам:

    type ID = string | number;
    type Point = { x: number; y: number };
    type Status = "pending" | "shipped" | "delivered";
    

    Выбор межа інтэрфейсам і типам

    Для звычных форм об’ектаў гэтыя два элементы ў большай частцы можна заменіць адным на другі. Рэальныя разлікі заключаюцца ў:

    • Расшырэнне: інтарфейсы выкарыстоўваюць extends; алясы типа спаўнююць формы за дапамою аператара перасекання &.
    • Аднасці і прымітывы: іх можна выразіць толькі за дапамою алясаў типа, як у type A = string | number.
    • Злучэнне декларацый: два інтарфейсы з адной назвай злучаюцца ў адны; алясы типа не можна перадеклараваць.
    • Стандарт: інтарфейсы звычайна викорыстоўваюцца для форм публічных API і контрактаў класаў; алясы типа — для аднасцей, туплікаў і карэльваваных або умовных типаў.

    Правіло, якое прыменяе большасць команд: выбірайце interface, калі форма, верагатна, будзе расшыроўвана, напрыклад, для прапсаў React, модэляў API і контрактаў класаў, а type — для аднасцей, туплікаў і всіх не-об’ектных типаў.

    Аднасці, звужэнне і перасекання

    Тыпы аднасці

    Союз можа мець значэнне, якое ўзімае аднойчы ад кальколькох типаў:

    function printId(id: string | number) {
      console.log(`Your ID is ${id}`);
    }
    

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

    printId(101);
    printId("A-204");
    

    Спяканне

    Калі значэнне прызначаецца як союз, TypeScript дазволяе толькі тыя аперацыі, якія ўзімаюцься паўнай меры для кожнага члена, пакуль не будзе падтверджана які самэ член выкарыстоўваецца. Гэта падтверджэнне называецца спяканнем, і кампайлер следуе за яным у всіх ветках програмы:

    function formatValue(value: string | number) {
      if (typeof value === "string") {
        return value.toUpperCase(); // TypeScript knows it's a string here
      }
      return value.toFixed(2); // and here, it knows it's a number
    }
    

    Па-разоў з typeof, распашчытныя засобы спякання – это Array.isArray(), instanceof, апэратар in і пераказы роўнасці з null або undefined. Самэ так TypeScript за звычайных адзначэнняў застаецца мовчазным, але ўсё ж выяўляе нестандартныя ситуацыі.

    Дискримінаваныя союзы для стану

    Самыя частая памятка, якую не спрыгваюць розробнікі среднага рангу, — это стварэнне спяльнага літэральнага поля „tag“ для кожнай варыянтной формы аб’юнкціі. Спачатку неабходна отдзельная дэфініцыя кожнага стану:

    type LoadingState = { status: "loading" };
    type SuccessState = { status: "success"; data: string[] };
    type ErrorState = { status: "error"; message: string };
    

    Потым іх неабходна саюзаваць і выбіраць стан па значэнні tag:

    type FetchState = LoadingState | SuccessState | ErrorState;function render(state: FetchState) {
      switch (state.status) {
        case "loading":
          return "Loading...";
        case "success":
          return `Loaded ${state.data.length} items`;
        case "error":
          return `Failed: ${state.message}`;
      }
    }
    

    У кожнам case значэнне state сузваецца да падходячага варіанта, таму state.data ўзможна выкарыстоўваць толькі у галузі "success", а state.message — толькі у галузі "error". Немагчымыя комбінацыі, такія як наявнасць дадзеных і памылкі адночасна, проста не можуць быць представлены. эта мера пазбегае цэлай категоріі збоев типу "аспект не вызначаны" у коде UI і API. Дадаць галузь default, якая прызначае state зменнай never, ператварае гэта на перакананне аб усепакрытасі: якщо пазней дадаць чэрговы статус, кампайлер пакажае кожны элемент switch, які яго не урахаваў.

    Тыпы перасекання

    Дзе альянс означае "гэта або тое", перасеканне означае "гэта і тое". Пачніце з двух маленькіх форм:

    type Timestamped = { createdAt: Date };
    type Named = { name: string };
    

    Для стварэння перасекання об’екту неабяжнае, каб у яму былі поля з обох:

    type Record = Timestamped & Named;const item: Record = { name: "Invoice", createdAt: new Date() };
    

    Адзін застерэжны момент па гэтым прыкладу: Record таксама ўжо ёсць назва вбудованага типу-інструмента. Адзначэнне савастой псевдоніма з гэтай назвай засланяе глобальны тип у тым файле, што ў найкращым случае вызывае плутанне, таму ў рэальным кодзе краща выбіраць болей спецыфічную назву.

    Функцыі

    Анотацыі параметраў і рэзультатаў ёсць адной з основных частак кантракту функцыі:

    function multiply(a: number, b: number): number {
      return a * b;
    }
    

    Неабяжныя параметры пазначаюцца знакам ?; значэння за значчынай робяць параметр неабяжным і визначаюць яго тип, а параметры-рэст збіраюць будзь-сколькі аргументаў у масэвы типаваны массив:

    // optional parameter
    function greet(name: string, title?: string): string {
      return title ? `${title} ${name}` : name;
    }// default parameter
    function createOrder(item: string, quantity: number = 1) {
      return { item, quantity };
    }// rest parameters
    function sum(...numbers: number[]): number {
      return numbers.reduce((total, n) => total + n, 0);
    }
    

    Тыпы функцый

    Можна таксама апісаць форму самай функцыі, што ўжо корыстна для калебакаў і об’ектаў-стратэгій:

    type MathOperation = (a: number, b: number) => number;
    

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

    const subtract: MathOperation = (a, b) => a - b;
    

    Класы і модифікаторы доступу

    Класы TypeScript — это класы JavaScript з типаванымі атрыбутамі і модифікаторамі доступу. Клас нижэйша спачатку абяраецца прыватным значэнням балансу і атрыбутам owner, які можа быць чытаны толькі:

    class Account {
      private balance: number;
      readonly owner: string;
    

    У рэште класа гэтыя атрыбуты задаюцца ў канстрактары, а таксама выклікаюцца методы для змены і чытання балансу; доступ да прыватнага атрыбута зза меж класа заблокаваны:

      constructor(owner: string, initialBalance: number) {
        this.owner = owner;
        this.balance = initialBalance;
      }  deposit(amount: number): void {
        this.balance += amount;
      }  getBalance(): number {
        return this.balance;
      }
    }const acc = new Account("Priya", 1000);
    acc.deposit(500);
    console.log(acc.getBalance()); // 1500
    acc.balance; // Error: Property 'balance' is private
    

    Модифікаторы значаць:

    • public, які ўзяты за звычку, доступны паўсюду.
    • private доступны толькі ўнутрь класа.
    • protected доступны ўнутрь класа і яго падкласаў.
  • readonly лямды прымаюць значэнне ў канстрактары і не дазволяюць пазнейшае перазначэнне.
  • Памятайце, што private рэалізуецца толькі за дапамою кампайляра; пад час выканання атрыбут ёсць звычайной лямдой. Якщо вам патрэбна справжня прыватная адзインформацыя пад час выканання, для этага існуюць # лямды ў JavaScript, пра якія йдзе мова ў TypeScript private fields versus # syntax.

    Інтэрфейсы як кантракты класаў

    Інтэрфейс можа визначыць, што клас павінен запростаць:

    interface Shape {
      area(): number;
    }
    

    За дапамою implements кампайляр пераканальваецца, што клас выпалняе кантракт, і пра відсутнюй метод паведамляецца да таго, як код запускаецца. Канстрактар таксама выкорыстоўвае атрыбут-параметр private radius, які адгласоўвае і прызначае лямду ў аднам кроку:

    class Circle implements Shape {
      constructor(private radius: number) {}  area(): number {
        return Math.PI * this.radius ** 2;
      }
    }
    

    Гэнерыкі: код, які можна викорыстоўваць знову і які захоўвае сваія типы

    Гэнерыкі — гэта тады, калі TypeScript перестае адчувацца як проста анотаваны JavaScript і стае сэрцам іншага інструмента.

    Проблема, якую яны рашаюць

    Памочную функцыю, якая вяртае першы элемент будзь-каго масіву, можна напісаць з викорыстаннем any:

    function firstElement(arr: any[]) {
      return arr[0];
    }
    

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

    const num = firstElement([1, 2, 3]);   // typed as `any` — no help from the compiler
    const str = firstElement(["a", "b"]);  // also `any`
    

    Параметр типу зафіксавае тип элемента вхідных дадзеных і перыяктвае яго для значэння, яке вяртаецца:

    function firstElement<T>(arr: T[]): T {
      return arr[0];
    }
    

    Тепер кожны вызов дае точны тип рэзультата, які выважваецца на адной з аргументаў:

    const num = firstElement([1, 2, 3]);   // inferred as number
    const str = firstElement(["a", "b"]);  // inferred as string
    

    T — гэта зменная типу, якую TypeScript запоўняе на адповіднасць таму, што вы прасілі. Вы застаецеся з гнучкасцю any, але атрымуеце безпеку конкрэтнага типу. Заўважыце, што з порожнім масавым элементам arr[0] на час выконання фактычна ёсць undefined; увімкненне noUncheckedIndexedAccess змушвае кампайлер практычна выразіць гэта як T | undefined.

    Генерычныя інтерфейсы

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

    interface ApiResponse<T> {
      success: boolean;
      data: T;
    }
    

    Тып паданых дадзенняў задаецца там, дзе викорыстоўваецца обгорак:

    const userResponse: ApiResponse<{ id: number; name: string }> = {
      success: true,
      data: { id: 1, name: "Sara" },
    };
    

    Самэ так багато прыкладных програм пішуць свой шар API: адна зменная ApiResponse<T>, якая пераўтвараецца для корыстувачаў, продуктаў, замовленняў і ўсьго іншага, што вяртае бэкенд.

    Обмежэнне параметра типу

    Інодзе T павінен гарантаваць певныя якосці. Апісацыю выкалекі трэба практыкаваць у вачынку-інтерфейсе:

    interface HasLength {
      length: number;
    }
    

    Пасля чаго параметр трэба обмежыць за дапамою extends, ўнаследаваючы толькі тыпы, якія маюць атрыбут length:

    function logLength<T extends HasLength>(item: T): void {
      console.log(item.length);
    }logLength("hello");       // OK — strings have .length
    logLength([1, 2, 3]);     // OK — arrays have .length
    logLength(42);             // Error: number doesn't have .length
    

    Тыпы-дапаможнікі

    TypeScript прывозыць гэнерычныя тыпы-дапаможнікі, якія ствараюць новыя тыпы на адной з існуючых, заменяючы вельмі большую частку ручна напісанага шаблонавага коду. Дадзім модель корыстніка:

    interface User {
      id: number;
      name: string;
      email: string;
      isAdmin: boolean;
    }
    

    Можна ствараць варіанты заместо таго, каб перазаявляць іх: Partial робіць кожную якосць неабяжнаю, Readonly — усі якосці толькі для чытання, Pick заставае выбраныя ключы, Omit ўсуне іх, а Record стварае тып-слоўнік:

    // Every property becomes optional — perfect for "update" functions
    type UserUpdate = Partial<User>;// Every property becomes read-only
    type ImmutableUser = Readonly<User>;// Pick only the fields you need
    type UserPreview = Pick<User, "id" | "name">;// Everything except the fields you list
    type PublicUser = Omit<User, "email" | "isAdmin">;// A dictionary shape: keys of one type, values of another
    type UsersById = Record<number, User>;
    

    Тыповым прыкладам ўжыцця являецца функцыя апдэйта, якая прыме нальотнае падмножыну полей:

    function updateUser(id: number, changes: Partial<User>): void {
      // merge `changes` into the stored user
    }
    

    Апыляючыя функцыю пасылаюць толькі тое, што зменілася:

    updateUser(1, { name: "New Name" }); // no need to pass email, isAdmin, etc.
    

    Адаптаванне блога па тэму вбудованых функцыянальных типаў TypeScript дэталізавае ўсю ўпорку такіх типаў.

    Enum-ы і літэральныя типы

    Enum-ы

    Enum задае іменаваны набор сталярых значэнняў:

    enum OrderStatus {
      Pending,
      Shipped,
      Delivered,
    }
    

    Значэнні пасля чаго апыляюцца чераз enum:

    let status: OrderStatus = OrderStatus.Shipped;
    

    Па значэнню члены нумеруюцца ад 0. Асабліве калі прызначаюцца строкавыя значэнні, логі і даны, якія пераходзяць праз сеть, становяцца значна простыяя для дыбагавання:

    enum Direction {
      Up = "UP",
      Down = "DOWN",
      Left = "LEFT",
      Right = "RIGHT",
    }
    

    Аднасоюзы строкавых літэраляў часта ёсць простыяя

    Многія команды сэрцава выбіраюць аднасоюз строкавых літэраляў, таму што ён не стварае дапынковага JavaScript-кода і працуе прыведамлівей:

    type OrderStatus = "pending" | "shipped" | "delivered";
    

    Кампайляр такім чынам адхоўвае будзь-якія значэнні, якія не належаць да заданага набору:

    function updateStatus(status: OrderStatus) {
      // ...
    }updateStatus("shipped");  // OK
    updateStatus("cancelled"); // Error: not assignable to type 'OrderStatus'
    

    Існуе ўтолькі іншы практычны адказ на выбор уніянаў: энумы генеруюць код пад час выканання, таму інструменты, якія адключаюць толькі типы, такія як вбудованая падтрымка TypeScript у Node.js, не прыймаюць іх.

    Модулі і tsconfig.json

    Модулі

    TypeScript выкарыстоўвае стандартную сынтаксі ES-модуляў. Адна файл выэкспортуе функцыю:

    // math.ts
    export function add(a: number, b: number): number {
      return a + b;
    }
    

    Іншы файл яе імпортуе:

    // app.ts
    import { add } from "./math";
    

    Найважлівейшыя настройкі

    У генераванай конфігурацыі є дзесяткі опцый, але толькі калька з іх вплывае на большую частку вашага досвіду работы:

    {
      "compilerOptions": {
        "target": "ES2020",
        "module": "ESNext",
        "strict": true,
        "noImplicitAny": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "outDir": "./dist"
      }
    }
    
    • strict: true актывае весь комплекс строгіх пераканальненняў, укладаючы strictNullChecks, які вымагае частковага адносу да null і undefined. Апытныя разработчыкі вважаюць гэта непрыемлемым, таму што без гэтага TypeScript выявляе значна менш справжніх багоў.
  • noImplicitAny адваруе пра абэранцыі там, дзе значэнне іншым чынам перайшла бы на any без вашага запиту. Ён уже ўключаны ў режым strict, таму яго отдзельныя паказанні маюць значэнне толькі для лёгкасупрацоўнасці.
  • target адначасова вяршыць выбіранне версіі JavaScript, якая будзе выканана, і вяршыць тое, насколькі савэтнай синтаксі трэба перапісваць для старэйшых средаваў.
  • Калі вы спадчынаеце кодавую базу з выключаным режымом strict, правільны падход — ўвёсці яго і поступова усунуць абэранцыі, працюя з кожным файлам окрема, а не проста додаць any, пакуль не знікнуць адзначэнні пра абэранцыі.

    Пашчутныя працынкі на кожным рэвэле

    Пачатківцы

    • Адзначэнне значэнняў, якія TypeScript і так мог бы вывучыць сам.
    • Вжыванне any як толькі компайлер падае адзначэнні пра абэранцыю, замест таго каб звузіць рэальны тип.
  • Занедбаўце падчас перагляду коду ігноравація памылак кампайляра, якія на самае працо є справжнімі памылкамі.
  • Середній ранг

    • Акларацыя interface або type для екземпляра, які могаў бы адразу обработаць інферэнс, што прыносіць толькі зайвую формальнасць без рэальной парады.
    • Указванне масоў як зменных і ўтварэнне змян у іх за дапамогою push або splice, калі ўсё ж трэба было викорыстоўваць ReadonlyArray<T> і здзейснюваць змяны толькі без змены структуры.
    • Забыванне пра тое, што аб’еднанне (union) павінна быць спачатку сузана, прычымо да таго — спробы борцавання з кампайлярам уместо чытання його паведамленняў.

    Прасунуты ранг

    • Стварэнне генерыкаў без жадных абмежэнняў, якія могу быць чым завалям, што таямніча паслабляе сэнс викорыстоўвання генерыкаў у функціях.
    • Выключэнне режыму strict для всіго проекта, каб спрыяць адхоўленню кальколькі памылак, уместо таго, каб іх выправіць аб адгуклаць у певныя рамкі.
    • Спрытліванне такога тыповага асэрцыі, як value as Type, так, нібыта ёна паўнастае якія-небудзь данні. Асэрцыя нічога не пераканальвае пад час выконання; яна проста кажа компілятору даваць вам доверле. Данні з зяўніх адносна вашага програмы, укладаючы ў сябе адпаведзі AI, данні, якія вводзі корыстувальнікі, і localStorage, патрабуюць рэальнай пераканальвання.

    Галоўныя выводы

    • TypeScript дадае кампіляцыйны пераканальвач да JavaScript; тыпы зникаюць, калі код запускаецца, таму межы пад час выконання все рава патрабуюць пераканальвання.
    • Анотавайце контракты, такія як падписы функцый і порожнія колекцыі, і дазвольце механізму аўтадыдакшні займацца рэштаю.
    • Ўжывайце interface для об’ектаў, якія можна расшырваць, і type для з’еднанняў, туплі і необ’ектных тыпаў.
    • Як можно раней выучыце концэпцію дискримінаваных з’еднанняў; гэта самы спосаб, які найбольш безпасна запобегае багам у кодзе з великай колькасцю станоў.
    • Універсальныя тыпы і функцыйнальныя типы, такія як Partial, Pick, Omit і Record, якраз і ператвараюць розрозненыя анотацыі на справжняю систему тыпоў.
    • Заставайце параметр strict: true увімкнутым. Галоўная перавага TypeScript — гледзець на помылкі яшчэ на пачатку, калі ўсё легка выправіць, а не пазней, калі це будзе коштаць дорожа, і гэта можліва толькі тады, калі перакрытчык можа вядома выплявляць свою роботу.

    Спадні матэрыялы