typescript - 1. parametreye dayanarak İkinci parametrenin Genel Tipini çıkar (T<A,B> const/tip listesi, A'yı B'ye eşleyen Genel Tip)


Bir soyut sınıfın (olay işlemcisi) uygulamalarına sahibim, iki genel parametre sağlıyor - bir enum değeri (olay adı) ve bir tür (olay yükü):

enum EventNames {
  EVENT_NAME_1='event_name_1',
  EVENT_NAME_2='event_name_2',
  EVENT_NAME_3='event_name_3',
  EVENT_NAME_4='event_name_4',
}

type Event1Payload = {
  somePayload1: string
}

type Event2Payload = {
  somePayload2: string
}

abstract class EventProcessor<TEventName extends EventNames, TEventPayload> {
  abstract process(payload: TEventPayload): void;
}
  1. Enum değerini ve bir türü kabul eden ve türü enum'a göre doğrulayan bir işlev istiyorum - tür, o enum değerine sahip sınıfın ikinci jenerik türüyle eşleşmelidir.

Fonksiyon şudur:

function publish<TEventName extends EventNames>(EventName: TEventName, payload: GetPayloadTypeForEventName<TEventName>): void {
  // ...
}

Yani şunu iki uygulamaya sahip olmayı varsayarsak:

class Event1Processor extends EventProcessor<EventNames.EVENT_NAME_1, Event1Payload> {
  process(payload: Event1Payload): void {
 // ...
 }
}

class Event2Processor extends EventProcessor<EventNames.EVENT_NAME_2, Event2Payload> {
  process(payload: Event2Payload): void {
 // ...
 }
}

Uygun yükleme türü olmadan bir etkinlik adını çağırdığınızda, TypeScript hatası alırsınız, örneğin:

publish(EventNames.EVENT_NAME_1, {});

çalışmamalıdır ve eşleşen Event1Payload tipi ile çağrı yapmak çalışmalıdır:

publish(EventNames.EVENT_NAME_1, {
  somePayload1: 'some string'
});

Yani türleri almak için uygulamaları içeren en az bir tür/const olmalı:

const eventProcessors = [Event1Processor, Event2Processor] as const

veya

type eventProcessors = Event1Processor | Event2Processor

ancak ondan enum değeri->yük haritasını çıkaran türü nasıl oluşturacağımı bilmiyorum.

Türlerden generikleri alan türler oluşturabilirim:

type getTypeOfFirstGenericParameter<T> = T extends EventProcessor<infer U, any> ? U : never
type getTypeOfSecondGenericParameter<T> = T extends EventProcessor<any, infer U> ? U : never

ve sabit diziden türleri çıkarmaya çalıştım ancak Tip örneklemesi aşırı derecede derin ve muhtemelen sonsuz hatası alıyorum:

type GetPayloadTypeForEventName<N extends JobAction<any>[], Acc extends string[] = []> =
  N['length'] extends Acc['length'] ?
    Acc :
    GetPayloadTypeForEventName<N, [...Acc, getTypeOfFirstGenericParameter<N[Acc['length']]>]>;

  1. eventProcessors türünden/const dizisinden generatedEventNamesType türünü alabilmeyi deniyorum:
const eventProcessors = [Event1Processor, Event2Processor] as const

veya

type eventProcessors = Event1Processor | Event2Processor

Türlerin dizisini/“veya”sını eşleyen ve bunların birinci genel argümanını alan, olası türler arasında “veya” ilişkisi ile yeni bir tür oluşturan bir tür, aşağıdaki gibi:

type GetEventNamesFromEventProcessors<TEventProcessor[]> = ...

// Type:
GetEventNamesFromEventProcessors<eventProcessors>
// Should be equivalent to:
type generatedEventNamesType = JobTypes.EVENT_NAME_2 | EventNames.EVENT_NAME_1;

Cevap #1

Eğer bir tür argümanı başka bir tür argümandan türetmek istiyorsanız, muhtemelen aslında yalnızca bir tür argümanı kabul etmeniz ve iki tür argümanı kabul etmemeniz gerekir. Kodunuzun bir yerinde TypeScript’e hangi EventNames üyesinin hangi payload ile eşleştiğini söylemeniz gerekir. Bu bilgiyi EventProcessor alt sınıfları arasında dağıtmaya çalışıyormuşsunuz gibi görünüyor. Ancak EventProcessor<K, T>’niz K ile hiçbir yapısal bağımlılık içermediğinden (yani K ile ilgili herhangi bir üyesi olmadığından), EventProcessor<K1, T> gibi bir alt sınıf ile EventProcessor<K2, T> gerçekte birbirinden ayırt edilemez ve dolayısıyla çıkarım teşebbüslerinin başarısız olma olasılığı oldukça yüksektir. Bkz. TypeScript SSS: Bu arayüzde tip çıkarımı neden çalışmıyor: interface Foo<T> { }?

Buradaki en kolay yaklaşım, EventNamesi açıkça ve tek bir yerde yüklerle bağlayan bir eşleme arayüzü oluşturmaktır:

interface EventPayloadMapping {
  [EventNames.EVENT_NAME_1]: Event1Payload
  [EventNames.EVENT_NAME_2]: Event2Payload
  // presumably others
}

Bunu elde ettikten sonra, geri kalan tüm kodunuzu sadece K extends keyof EventPayloadMapping (veya tüm olaylar için payload tipleriniz varsa K extends EventNames) olarak genel hâle getirebilirsiniz. Olay adına atıfta bulunmak isterseniz, bu K olur. Payload tipine atıfta bulunmak isterseniz, EventPayloadMapping içine indeksleme yaparak bulabilirsiniz: bu da EventPayloadMapping[K] olur.

Yani diğer kodunuz şöyle oluyor:

abstract class EventProcessor<K extends keyof EventPayloadMapping> {
  abstract process(payload: EventPayloadMapping[K]): void;
}

class Event1Processor extends EventProcessor<EventNames.EVENT_NAME_1> {
  process(payload: Event1Payload): void {
    // ...
  }
}

class Event2Processor extends EventProcessor<EventNames.EVENT_NAME_2> {
  process(payload: Event2Payload): void {
    // ...
  }
}

function publish<K extends keyof EventPayloadMapping>(
  EventName: K, payload: EventPayloadMapping[K]
): void {
  // ...
}

İki genel tür parametresi yerine, yalnızca tek bir parametreye sahip olduğunuzu görebilirsiniz. Ve şimdi istediğiniz gibi davranıyor:

publish(EventNames.EVENT_NAME_1, {}); // error!

publish(EventNames.EVENT_NAME_1, {
  somePayload1: 'some string'
}); // okay

Koda yönelik oyun alanı bağlantısı