Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to match types in Typescript?

In F#, you can do this:

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

type UndeliveredOrderData =
  {
    OrderId: int;
  }

type Order = 
  | Delivered of DeliveredOrderData
  | Undelivered of UndeliveredOrderData

and then I can create functions that return depending on the state:

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

I get how to create types in TypeScript, but how can I do the same as above?

const putOrderOnTruck = (order: UndeliveredOrder) => {
  // how can I make sure order is really UndeliveredOrder?
}
like image 887
kibe Avatar asked Feb 26 '26 00:02

kibe


2 Answers

The two main typescript concepts at play here are Discriminating Unions and Type Guards.

We have two different types of order data. The difference is that one has a DateDelivered and the other doesn't. We can make that very explicit by saying that UndeliveredOrderData can never have a DateDelivered (ie. this property must not exist, or be set to undefined).

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

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

type Order = DeliveredOrderData | UndeliveredOrderData

If we have an Order which could be either of the two types, we can tell which type it is by seeing if there is a DateDelivered or not. We put that logic into a user-defined type guard which tells typescript to narrow the type based on the result.

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

Let's say we have a function that requires an UndeliveredOrderData

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

But at runtime, we aren't sure if an Order is delivered or not. We can use our type guard inside an if statement and act differently in the two branches.

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

Typescript Playground Link

like image 81
Linda Paiste Avatar answered Feb 28 '26 17:02

Linda Paiste


TypeScript's types can only be used for static type checking, so in order to do that you should check the object's properties.

Here is an example

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');
    }
}
like image 40
Teneff Avatar answered Feb 28 '26 17:02

Teneff



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!