typescript
GitHub
  • README
  • Давайте Почнемо
    • Why TypeScript
  • JavaScript
    • Equality
    • References
    • Null vs. Undefined
    • this
    • Closure
    • Number
    • Truthy
  • Future JavaScript Now
    • Classes
      • Classes Emit
    • Arrow Functions
    • Rest Parameters
    • let
    • const
    • Destructuring
    • Spread Operator
    • for...of
    • Iterators
    • Template Strings
    • Promise
    • Generators
    • Async Await
  • Проект / Project
    • Контекст компіляції / Compilation Context
      • tsconfig.json
      • Які файли / Which Files?
    • Простори Оголошень / Declaration Spaces
    • Модулі / Modules
      • File Module Details
      • global.d.ts
    • Namespaces
    • Dynamic Import Expressions
  • Node.js QuickStart
  • Browser QuickStart
  • Library QuickStart
  • TypeScript's Type System
    • JS Migration Guide
    • @types
    • Ambient Declarations
      • Declaration Files
      • Variables
    • Interfaces
    • Enums
    • lib.d.ts
    • Functions
    • Callable
    • Type Assertion
    • Freshness
    • Type Guard
    • Literal Types
    • Readonly
    • Generics
    • Type Inference
    • Type Compatibility
    • Never Type
    • Discriminated Unions
    • Index Signatures
    • Moving Types
    • Exception Handling
    • Mixins
  • JSX
    • React
    • Non React JSX
  • Options
    • noImplicitAny
    • strictNullChecks
  • Errors in TypeScript
    • Interpreting Errors
    • Common Errors
  • NPM
  • Testing
    • Jest
    • Cypress
  • Tools
    • Prettier
    • Husky
    • ESLint
    • Changelog
  • TIPs
    • String Based Enums
    • Nominal Typing
    • Stateful Functions
    • Currying
    • Type Instantiation
    • Lazy Object Literal Initialization
    • Classes are Useful
    • Avoid Export Default
    • Limit Property Setters
    • outFile caution
    • JQuery tips
    • static constructors
    • singleton pattern
    • Function parameters
    • Build Toggles
    • Barrel
    • Create Arrays
    • Typesafe Event Emitter
  • StyleGuide
  • TypeScript Compiler Internals
    • Program
    • AST
      • TIP: Visit Children
      • TIP: SyntaxKind enum
      • Trivia
    • Scanner
    • Parser
      • Parser Functions
    • Binder
      • Binder Functions
      • Binder Declarations
      • Binder Container
      • Binder SymbolTable
      • Binder Error Reporting
    • Checker
      • Checker Diagnostics
      • Checker Error Reporting
    • Emitter
      • Emitter Functions
      • Emitter SourceMaps
    • Contributing
Powered by GitBook
On this page
  • Nominal Typing
  • Using literal types
  • Using Enums
  • Using Interfaces
Edit on GitHub
  1. TIPs

Nominal Typing

PreviousString Based EnumsNextStateful Functions

Last updated 1 year ago

Nominal Typing

Система типів TypeScript є структурною . Однак існують випадки реального використання системи, де потрібно, щоб дві змінні були розрізнені, оскільки вони мають різні назви типу, навіть якщо вони мають однакову структуру. Дуже поширеним випадком використання є структури ідентичності (які, як правило, являють собою просто рядки з семантикою, пов’язаною з їх ім’ям у таких мовах, як C#/Java).

Є кілька моделей, які виникли в спільноті. Я розглядаю їх у порядку зменшення особистих переваг:

Using literal types

У цьому шаблоні використовуються узагальнені та літеральні типи:

/** Generic Id type */
type Id<T extends string> = {
  type: T,
  value: string,
}

/** Specific Id types */
type FooId = Id<'foo'>;
type BarId = Id<'bar'>;

/** Optional: constructors functions */
const createFoo = (value: string): FooId => ({ type: 'foo', value });
const createBar = (value: string): BarId => ({ type: 'bar', value });

let foo = createFoo('sample')
let bar = createBar('sample');

foo = bar; // Error
foo = foo; // Okay
  • Переваги

    • Немає потреби в твердженнях будь-якого типу

  • Недолік

    • Структура {type,value} може бути небажаною та потребувати підтримки серверної серіалізації

Using Enums

Обхідний шлях передбачає:

  • Створення переліку brand.

  • Створення типу як перетину (&) переліку бренду + фактичної структури.

Це показано нижче, де структура типів є лише рядком:

// FOO
enum FooIdBrand { _ = "" };
type FooId = FooIdBrand & string;

// BAR
enum BarIdBrand  { _ = "" };
type BarId = BarIdBrand & string;

/**
 * Usage Demo
 */
var fooId: FooId;
var barId: BarId;

// Safety!
fooId = barId; // error
barId = fooId; // error

// Newing up
fooId = 'foo' as FooId;
barId = 'bar' as BarId;

// Both types are compatible with the base
var str: string;
str = fooId;
str = barId;

Зверніть увагу, що переліки брендів FooIdBrand і BarIdBrand вище мають один елемент (_), який відповідає порожньому рядку, як зазначено в _ = "". Це змушує TypeScript зробити висновок, що це переліки на основі рядків зі значеннями типу string, а не переліки зі значеннями типу number. Це необхідно, оскільки TypeScript робить висновок, що порожнє перелічення ({}) є числовим переліком, а з TypeScript 3.6.2 перетин числового enum і string є never .

Using Interfaces

Оскільки numbers є типом сумісним з enum, попередня техніка не може бути використана для них. Замість цього ми можемо використовувати інтерфейси, щоб порушити структурну сумісність. Цей метод досі використовується командою компіляторів TypeScript, тому варто згадати. Використання префікса _ і суфікса Brand є угодою, яку я настійно рекомендую (і [той, якої дотримується команда TypeScript](https://github.com/Microsoft/TypeScript/blob/7b48a182c05ea4dea81bab73ecbbe9e013a79e99/src/compiler/types .ts#L693-L698)).

Обхідний шлях передбачає наступне:

  • додавання невикористаної властивості до типу для порушення структурної сумісності.

  • використання твердження типу, коли потрібно оновити або ліквідувати.

Це показано нижче:

// FOO
interface FooId extends String {
    _fooIdBrand: string; // To prevent type errors
}

// BAR
interface BarId extends String {
    _barIdBrand: string; // To prevent type errors
}

/**
 * Usage Demo
 */
var fooId: FooId;
var barId: BarId;

// Safety!
fooId = barId; // error
barId = fooId; // error
fooId = <FooId>barId; // error
barId = <BarId>fooId; // error

// Newing up
fooId = 'foo' as any;
barId = 'bar' as any;

// If you need the base string
var str: string;
str = fooId as any;
str = barId as any;

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

і це одна з головних мотиваційних переваг
Enums у TypeScript