Typescript jenerik arayüzler, özellik değerine göre eşleşir


Hala TypeScript öğreniyorum ve sahip olduğu tüm özellikleri. Bunlardan biri de generikleri kısıtlama. Eğer bu yaygın olarak sorulan bir soruysa özür dilerim, eğer bu konuda bana yardımcı olabilecek herhangi bir kaynağınız (dokümanların ötesinde) varsa, lütfen bir yorum olarak bağlantı paylaşın.

Yapmaya çalıştığım şey, DeliveryObject içindeki type özelliklerinin deliveryItems içindeki tüm nesnelerle eşleşmesini sağlamak.

İşte derlenen ama benim aradığım nihai çözüm olmayan bir kod örneği.

type DeliveryMethod = 'notification' | 'text' | 'email'

type DeliveryActions = INotificationAction | ITextMessageAction | IEmailAction

interface IDelivery {
  type: DeliveryMethod
}

interface INotificationAction extends IDelivery {
  type: 'notification'
  deviceId: string
  message: string
}

interface ITextMessageAction extends IDelivery {
  type: 'text'
  message: string
}

interface IEmailAction extends IDelivery {
  type: 'email'
  to: string
  subject: string
  body: string
}

// I know I need to do something additional here...
interface IDeliveryObject<T extends DeliveryMethod, U extends DeliveryActions> {
  type: T
  deliveryItems: Array<U>
}

function sendDelivery<K extends DeliveryMethod, Z extends DeliveryActions>(state: IDeliveryObject<K, Z>) {
  console.log(state)
}

sendDelivery({
  type: 'notification', // <--- needs to match or error out
  deliveryItems: [
    {
      type: 'email',  // <--- needs to match or error out
      to: '[email ',
      subject: '1235-67890',
      body: 'Here is a notification'
    }
  ]
})

Cevap #1

Buna, teslimat yöntemi ile ilişkili eylem nesnesini bir araya getirmek için bir "tip arama haritası" kullanarak yaklaşırdım. Yani buna benzer başka bir tip eklerdim:

type DeliveryActionTypes = {
    "notification": INotificationAction;
    "text": ITextMessageAction;
    "email": IEmailAction;
}

Bu tür sadece doğru yöntem adını onun eylem nesne türüne eşler. Daha sonra DeliveryMethod ve DeliveryActions için beyanları şunlarla değiştirebilirsiniz:

type DeliveryMethod = keyof DeliveryActionTypes;
type DeliveryActions = DeliveryActionTypes[keyof DeliveryActionTypes];

Bu, yöntem adını biliyorsanız doğru işlemi kolayca bulmanıza olanak tanır. Bunu, iki türün birbirine karşılık geldiğinden emin olmak için IDeliveryObject içinde kullanabilirsiniz:

interface IDeliveryObject<T extends DeliveryMethod> {
    type: T;

    // This is the type lookup, note the `DeliveryActionTypes[T]` part.
    deliveryItems: Array<DeliveryActionTypes[T]>;
}

Artık sendDelivery fonksiyonunun imzasını basitleştirebilirsiniz, çünkü artık tek ihtiyacı olan yöntem adı:

function sendDelivery<K extends DeliveryMethod>(state: IDeliveryObject<K>) {
  console.log(state)
}

Tüm bunlarla birlikte, türler eşleşmezse bir hata alırsınız:

sendDelivery({
  type: 'notification',
  deliveryItems: [
    {
      type: 'email', 
      to: '[email ', // <-- error on this line, see below
      subject: '1235-67890',
      body: 'Here is a notification'
    }
  ]
})
Type '{ type: "email"; to: string; subject: string; body: string; }'
is not assignable to type 'INotificationAction'.

Gördüğünüz gibi, Typescript dizideki öğelerin INotificationAction tipi olması gerektiğini doğru şekilde çıkarır ve olmadıklarında bir hata üretir.