編集:私は十分に明確ではなかったので、次の例にジャンプしたいと思うかもしれません。
ジェネリクスの制約に関する問題があり、自分が間違っていることを理解できません。
基本的に、私はこれを試しています:
enum Categories {
FIRST = 'first',
SECOND = 'second',
}
type ItemsMap = {
[key in Categories]: Item<key>;
}
class Item<
T extends keyof M,
M extends {[key in T]: Item<key, M>} = ItemsMap,
> {
category: T;
items: M;
}
目標は、enum/typeの「マップ」を渡すことです(アイテムは後で「type」を使用する必要があります)。
type ItemsMap = {
first: Item<Categories.FIRST, ItemsMap>;
second: Item<Categories.SECOND, ItemsMap>;
}
それでも、MジェネリックのデフォルトでTSエラーが発生しました。
Type 'ItemsMap' does not satisfy the constraint '{ [key in T]: Item<key, ItemsMap>; }'
なぜ制約を満たさないのですか?
このマップをサブクラスで使用しようとすると、別の問題が発生します。
class Foo<M extends {[key in keyof M]: Item<key, M>}> {} // OK
class Bar<M extends ItemsMap = ItemsMap> extends Foo<M> {} // Not OK
class Baz extends Foo<ItemsMap> {} // OK, but why?
TSはバーでエラーを生成します:
Type 'M' does not satisfy the constraint '{ [key in keyof M]: Item<key, M>; }'.
Type 'ItemsMap' is not assignable to type '{ [key in keyof M]: Item<key, M>; }'.
しかし、私にはその理由がわかりません。 TSからより多くの情報を入手する方法はありますか?
別の例を使用してみましょう。私の問題を理解する方が良いでしょう。
イベントに参加しましょう:
interface EventInterface {
target: EventTargetInterface;
type: string;
}
これまでのところ、イベントはタイプとターゲット(イベントを発行したオブジェクト)を持っています。後者は次のように説明されます。
interface EventTargetInterface {
addEventListener(type: string, listener: (event: EventInterface) => void): void;
dispatchEvent(event: EventInterface): boolean;
removeEventListener(type: string, listener: (event: EventInterface) => void): void;
}
たとえば、addEventListener
メソッドは、文字列と、EventInterface
のような関数を使用して呼び出すことができます。
その時点から、いくつかの理由でいくつかの制約を追加したいと思います。
そのためには、マップされたタイプを開発者に定義してもらいます。
type EventsMap = {
first: NiceEvent;
second: AwesomeEvent;
}
このマップされたタイプは、「イベントタイプ 'first'の場合、 'NiceEvent'をディスパッチします」と言います。これはタイプのみで、コードを生成しないでください。
それで、私はEventTargetInterface
を次のように変更しました:
interface EventTargetInterface<
M extends {[key in keyof M]: EventInterface},
> {
addEventListener<T extends keyof M>(type: T, listener: (event: M[T]) => void): void;
dispatchEvent<T extends M[keyof M]>(event: T): boolean;
removeEventListener<T extends keyof M>(type: T, listener: (event: M[T]) => void): void;
}
これまでのところ、これで良いので、これによりタイプが「指定された」マップのキーのみに制限され、リスナーがそれに関連付けられます。しかし、今ではEventTargetInterface
はジェネリックを受け取るため、EventInterface
も変更する必要があります。
interface EventInterface<
M extends {[key in keyof M]: EventInterface<M>},
> {
target: EventTargetInterface<M>;
type: keyof M;
}
よさそうです。ここでベース実装を追加しましょう:
abstract class EventBase<
M extends {[key in keyof M]: EventInterface<M>},
> implements EventInterface<M> {
target: EventTargetInterface<M>;
type: keyof M;
constructor(type: keyof M) {
this.type = type;
}
}
abstract class EventTargetBase<
M extends {[key in keyof M]: EventInterface<M>},
> implements EventTargetInterface<M> {
addEventListener<T extends keyof M>(type: T, listener: (event: M[T]) => void): void {}
dispatchEvent<T extends M[keyof M]>(event: T): boolean { return false; }
removeEventListener<T extends keyof M>(type: T, listener: (event: M[T]) => void): void {}
}
そして今、最初の具体的な実装:
enum MyEvents {
FIRST = 'first',
SECOND = 'second',
}
type MyEventsMap = {
[key in MyEvents]: MyEvent;
}
class MyEvent<
M extends MyEventsMap = MyEventsMap,
> extends EventBase<M> {}
class MyEventTarget<
M extends MyEventsMap = MyEventsMap,
> extends EventTargetBase<M> {}
そして、これは私が問題を抱えていることです(extends EventBase<M>
とEventTargetBase<M>
に関して):
Type 'M' does not satisfy the constraint '{ [key in keyof M]: EventInterface<M>; }'.
Type 'MyEventsMap' is not assignable to type '{ [key in keyof M]: EventInterface<M>; }'.ts(2344)
したがって、TypeScriptの場合、MyEventsMap
を拡張するものはM extends {[key in keyof M]: EventInterface<M>}
に準拠しません。 MyEvent
はEventBase
を実装するEventInterface
を拡張するので、わかりません!
さらに混乱して、以下を使用します:
class MyEvent extends EventBase<MyEventsMap> {}
TypeScriptの場合はこれで問題ないので、ジェネリックを使用しても問題が発生しません。 (クラスを拡張可能にしたいので、ジェネリックを維持する必要がありますが、それは別のトピックです)
TypeScript Playground にアクセスしたい場合は、それにアクセスできます。
最終目標を完全に理解しているとは思いません(おそらく、さらにいくつかの使用例が役立つでしょう)。しかし、私が現在タスクを解釈する方法...これはそれを解決しますか?
class Item<
T extends string,
K extends string = Categories,
> {
category: T;
items: Record<K, Item<T, K>>;
}