Как сопоставить типы в Typescript?

#typescript #pattern-matching

#typescript #сопоставление с образцом

Вопрос:

В F # вы можете сделать это:

 type DeliveredOrderData =
  {
    OrderId: int;
    DateDelivered: DateTime;
  }

type UndeliveredOrderData =
  {
    OrderId: int;
  }

type Order = 
  | Delivered of DeliveredOrderData
  | Undelivered of UndeliveredOrderData
  

и тогда я могу создавать функции, которые возвращаются в зависимости от состояния:

 let putOnTruck order = 
  match order with
    | Undelivered {OrderId=id} ->
      OutForDelivery {OrderId=id}
    | Delivered _ ->
      failwith "package already delivered"
  

Я понимаю, как создавать типы в TypeScript, но как я могу сделать то же самое, что и выше?

 const putOrderOnTruck = (order: UndeliveredOrder) => {
  // how can I make sure order is really UndeliveredOrder?
}
  

Комментарии:

1. Вы хотите создать функцию, которая принимает в качестве аргумента идентификатор порядка и возвращает новую функцию в зависимости от этого идентификатора порядка?

2. Помогает ли это? TS docs по защите типов . Также найдите «дискриминируемые объединения»

Ответ №1:

Здесь используются две основные концепции typescript: различающие объединения и защита типов.

У нас есть два разных типа данных заказа. Разница в том, что у одного есть a DateDelivered , а у другого нет. Мы можем сделать это очень явным, сказав, что UndeliveredOrderData может never иметь DateDelivered (т.е.. это свойство не должно существовать или иметь значение undefined ).

 type DeliveredOrderData = {
    OrderId: number;
    DateDelivered: number;
  }

type UndeliveredOrderData = {
    OrderId: number;
    DateDelivered?: never;
}

type Order = DeliveredOrderData | UndeliveredOrderData
  

Если у нас есть an Order , который может быть любого из двух типов, мы можем определить, какой это тип, посмотрев, есть ли a DateDelivered или нет. Мы помещаем эту логику в определяемый пользователем тип guard, который сообщает typescript сузить тип на основе результата.

 const isDelivered = (order: Order): order is DeliveredOrderData => {
    return !! order.DateDelivered;
}
  

Допустим, у нас есть функция, которая требует UndeliveredOrderData

 const doPutOnTruck = (order: UndeliveredOrderData) => {
}
  

Но во время выполнения мы не уверены, доставляется ли an Order или нет. Мы можем использовать нашу защиту типов внутри if инструкции и действовать по-разному в двух ветвях.

 const maybePutOnTruck = (order: Order) => {
  if ( isDelivered( order ) ) {
      throw new Error("package already delivered");
  }
  // type of `order` is now `UndeliveredOrderData`
  doPutOnTruck(order);
}
  

Ссылка на игровую площадку Typescript

Ответ №2:

Типы TypeScript можно использовать только для статической проверки типов, поэтому для этого вам следует проверить свойства объекта.

Вот пример

 enum OrderType {
    Delivered,
    Undelivered
}

type DeliveredOrderData = {
    type: OrderType
    OrderId: number;
    DateDelivered: Date
}

type UndeliveredOrderData = {
    type: OrderType
    OrderId: number;
}

function OutForDelivery(order: UndeliveredOrderData) {
}

let putOnTruck = (orderData: DeliveredOrderData | UndeliveredOrderData) => {
    switch(orderData.type) {
        case OrderType.Undelivered: 
            return OutForDelivery(orderData);
        case OrderType.Delivered:
            throw new Error('package already delivered');
    }
}
  

Ответ №3:

Сопоставление с образцом не является неотъемлемой частью Typescript, но вы можете заменить его пакетом ts-pattern .

 import { match, P } from "ts-pattern";

type Order =
  | { status: 'delivered'; orderId: number; dateDelivered: string }
  | { status: 'undelivered'; orderId: number; };

const putOnTruck = (order: Order) => {
  match(order)
    .with({status: 'delivered', dateDelivered: P.select()}, (dateDelivered, order) =>
       console.log(`Order ${order.orderId} has been delivered at ${dateDelivered}`))
    .with({status: 'undelivered'}, (order) =>
       console.log(`Order ${order.orderId} has not been delivered yet`))
    .exhaustive();
}