継承元classで継承先classの型を使う
code:example.ts
class Base {
public addListener(listener: (store: this) => void) { this.listeners.push(listener); }
private listeners: ((store: this) => void)[] = [];
}
class Derived extends Base {
public data: number[] = [];
}
const instance = new Derived();
instance.addListener((store) => console.log(store.data));
takker.icon 次のstoreの型をBaseStoreではなくBaseStoreを継承したクラスにしたいのですが、そのようなtemplateの指定方法は存在しますか?
`
export type BaseStoreListenerInit = { store: BaseStore; event: string };
export type BaseStoreListener = (init: BaseStoreListenerInit) => void;
export declare class BaseStore {
constructor();
public addChangeListener(listener: BaseStoreListener): void;
public removeChangeListener(listener: BaseStoreListener): void;
public emitChange(event: string): void;
public get listenersCount(): number;
private _onChangeListeners: BaseStoreListener[];
}
`
chatgpt.icon
BaseStoreを継承した新しいクラスを作成するには、次のようにクラスの宣言を行います。typescriptCopy codeexport class MyStore extends BaseStore {
constructor() {
super();
// コンストラクターの実装
}
// オーバーライドや新しいメソッドの追加
}
このように、新しいクラスをBaseStoreのサブクラスとして宣言することで、BaseStoreクラスのメソッドやプロパティを継承することができます。ただし、継承元の型としてBaseStore型を指定している箇所は、新しいクラスの型に置き換える必要があります。例えば、BaseStoreListenerInit型のstoreプロパティの型を、MyStoreクラスに置き換える場合は、次のように型定義を修正します。typescriptCopy codeexport type MyStoreListenerInit = { store: MyStore; event: string };
export type MyStoreListener = (init: MyStoreListenerInit) => void;
export class MyStore extends BaseStore {
// ...
}
このように、BaseStoreクラスを継承
takker.icon 続き
chatgpt.icon
した新しいクラスを定義することで、新しい型を定義することができます。ただし、MyStoreクラスで継承元のBaseStoreクラスのメソッドやプロパティをオーバーライドした場合や、新しいメソッドを追加した場合は、その型定義も適宜修正する必要があります。例えば、MyStoreクラスで_onChangeListenersプロパティをオーバーライドしている場合は、次のように型定義を修正します。typescriptCopy codeexport class MyStore extends BaseStore {
private _onChangeListeners: MyStoreListener[] = [];
// ...
}
このように、新しいクラスを定義することで、BaseStoreクラスの機能を拡張することができます。ただし、新しいクラスを定義する前に、BaseStoreクラスが十分に把握できていることを確認してから、実装することをお勧めします。
takker.icon それだと継承するたびに型定義を書き換えなければ行けません。templateを使って任意の継承先クラスに対応できないでしょうか?
chatgpt.icon
継承するたびに型定義を書き換える必要がないように、ジェネリックスを使用して実現することができます。ジェネリックスを使用すると、BaseStoreクラスを継承する任意のクラスで、storeプロパティの型を正しく指定することができます。以下は、ジェネリックスを使用してBaseStoreクラスを定義した例です。typescriptCopy codeexport type BaseStoreListenerInit<T extends BaseStore> = { store: T; event: string };
export type BaseStoreListener<T extends BaseStore> = (init: BaseStoreListenerInit<T>) => void;
export class BaseStore {
constructor() {
// ...
}
public addChangeListener(listener: BaseStoreListener<this>): void {
// ...
}
public removeChangeListener(listener: BaseStoreListener<this>): void {
// ...
}
public emitChange(event: string): void {
// ...
}
public get listenersCount(): number {
return this._onChangeListeners.length;
}
private _onChangeListeners: BaseStoreListener<this>[] = [];
}
BaseStoreListenerInitとBaseStoreListenerの型引数Tを使用することで、継承先のクラスを指定できるようになります。また、addChangeListenerやremoveChangeListener、_onChangeListenersプロパティの型も、this型を使用することで、継承先のクラスで正しく動作するようになります。MyStoreクラスでこのBaseStoreクラスを継承する場合は、次のようにジェネリックスを使用します。typescriptCopy codeexport class MyStore extends BaseStore {
// ...
}
const myStore = new MyStore();
myStore.addChangeListener((init) => {
// init.store は MyStore のインスタンスになる
});
BaseStoreListenerInitの型引数TをMyStoreに指定することで、storeプロパティの型がMyStoreになります。継承先のクラスを指定することで、型定義の汎用性が高くなり、拡張性のあるクラス設計ができるようになります。
takker.icon ありがとうございます。求めていたコードです。
ジェネリクスの型引数にthisって入れられたんですね……知りませんでした。このあたりについて解説していただけませんか?
chatgpt.icon
2 / 2ジェネリックスの型引数にthisを使用することで、継承先のクラスを指定することができます。thisは実行時に呼び出し元オブジェクトの型を示す特殊な型であり、静的な型チェックの段階では推論できませんが、実行時には正確な型を示します。BaseStoreListener型はBaseStoreListenerInit型の引数に使われる関数型です。BaseStoreListenerInit型でstoreプロパティの型を指定する必要があるため、BaseStoreListener型でも同じ型を指定する必要があります。しかし、BaseStoreListener型はBaseStoreクラスを継承した任意のクラスで使用されるため、継承先のクラスによってstoreプロパティの型が異なる可能性があります。ここで、ジェネリックスの型引数にthisを使用することで、継承先のクラスを指定することができます。thisは呼び出し元オブジェクトの型を示すため、BaseStoreListener<this>とすることで、storeプロパティの型を継承先のクラスに指定できます。これにより、BaseStoreListener型を使用する関数で、継承先のクラスに適した型のstoreプロパティを受け取ることができます。また、addChangeListenerやremoveChangeListenerメソッド、_onChangeListenersプロパティにおいても、ジェネリックスの型引数にthisを使用することで、継承先のクラスに適した型の引数やプロパティを使用することができます。ただし、thisを使用する場合は、ジェネリックスの型引数を省略することができなくなるため、継承先のクラスでジェネリックスを明示的に指定する必要があります。以上が、thisを使用したジェネリックスの説明になります。