yuu_1st
@yuu_1st

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

ジェネリクス型をリテラル型として取り出したい

Q&A

Closed

解決したいこと

typescriptにて、クラスによって異なる引数を取るといったコードを記述したいです。
言語化するのが少し難しいので、コードを先に提示します。

// 各クラスの実装省略
abstract class BaseClass {}
class Custom1class extends BaseClass {}
class Custom2class extends BaseClass {}
class Custom3class extends BaseClass {}

abstract class CustomUseBaseClass<T extends BaseClass> {
  readonly classType: unknown; // ここの型を指定したい

  abstract main(instance: T): void;
}

上記のように、BaseClassを拡張したいくつかのカスタムクラスがあります。
それらのクラスを使用するクラスとして、ベースでCustomUseBaseClassがあります。
ここから新たなクラスCustomUse1Classを作成するとして、このクラスのmain関数は引数としてcustom1classのインスタンスを使用したいです。
実装としてはおおよそ以下の形になります。

class CustomUse1Class extends CustomUseBaseClass<custom1class> {
  readonly classType: 'custom1class' = 'custom1class'; // 表題の箇所の具体例

  main(instance: custom1class) {
    // 略
  }
}

このように、mainの引数はcustom1classを取ります。

ここで、CustomUse1Classが、引数としてcustom1classを取る、ということを見分ける1ために、classTypeとして文字列のcustom1classを入れようと思いました。

ここからが本題です。

上記のコードのように、Tにそのまま与えられた文字列をリテラル型として扱う手段はありますか?

  1. requireで動的読み込みを考えているため、型推論が一度anyを通ります。そのため、見分けるためのものが必要になりました。

0

2Answer

Comments

  1. @yuu_1st

    Questioner

    今のところ想定しているのが、CustomUse1Class等が1クラス1ファイルあり、それらを動的読み込みで取得・mainの引数に指定できる型ごとに配列を作成し、その配列をループして呼び出す、といった形です。
    ```ts
    const use1 : CustomUseBaseClass<custom1class>[] = [];
    const use2 : CustomUseBaseClass<custom2class>[] = [];
    // requireで読み込み、use1、use2等に振り分ける
    ```

    ジェネリクスでクラスの種類を分ける都合上、instanceofを使用しても`CustomUseBaseClass`であることに変わりがないため、分岐が出来ないんです。
    なので手法がないのであれば、ジェネリクスではなく間にもう一つベースクラスを用いてinstanceofにしようとは思っているのですが、その分クラスを用意しなくてはいけないので、若干手間だなと思い、、、

    ```ts
    abstract class CustomUseBaseClass {
    abstract main(instance: BaseClass): void;
    }

    abstract class CustomBase1Class extends CustomUseBaseClass {
    abstract main(instance: custom1class) : void;
    }

    class CustomUse1Class extends CustomBase1Class{ // これだと、 instanceof CustomBase1Class で判別できる。
    main(instance: custom1class) {
    // 略
    }
    }
    ```
  2. うーん、すみませんやっぱりいまいちやりたいことのイメージが湧かないのでもう少し質問文読んで何か思いついたらコメントします

質問のタイトルとだいぶかけ離れてしまうのですが…
この実装で参考になるでしょうか?

TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript

上記の例では、BaseUseClassの配列をもとに、CustomClass1と2にフィルターする機能を実装しています。

もしご質問の意図と違いましたら、CustomUseBaseClassやCustomUse1Classを使って何をしたいか(クラス実装に求める要件)を明確にしていただけると回答できるかもしれません。。。

0Like

Comments

  1. @yuu_1st

    Questioner

    回答ありがとうございます
    実装したい内容としてはおおよそそのような解釈で合っています。
    ただ、今回の質問の内容としては、ここまでできた上で、CustomUseClass1やCustomUseClass2の`classType`に宣言できる文字をリテラル型で固定したい、という内容です。
    Playground内では、BaseUseClassのclassTypeがstringで定義されている以上、例えば`extends BaseUseClass<CustomClass1>`のクラス内で`classType = "1";`や`classType = "2";`など、任意の値にセットすることが出来ます。もちろん、コード作成者が気をつければそれで済むのですが、型が使えるtypescriptを使用しているので、そこをコードで厳格に設定したいと思っています。
    そこで、`extends BaseUseClass<CustomClass1>`では`classType = "1";`しか設定できない、`extends BaseUseClass<CustomClass2>`では`classType = "2";`しか設定できない、といった型の設定をしたいと思っています。
    それで今思いついていたのが、本タイトルの通り、`classType`に`CustomClass1`などのクラス名を文字としてそのまま使う、ことが楽だと思ってこのような質問になっている、といった感じです。。
  2. ご返答、ありがとうございます。
    なんとなくですが、質問意図は理解できたような気がします。。。
    「CustomUseBaseClassを適切に実装することで、各CustomUseClassXのclassTypeに、適切なリテラル型が設定されて型チェックされるようにしたい」と理解しました。

    ただし、「`extends BaseUseClass<CustomClass1>`では`classType = "1";`しか設定できない」という実装は難しい気がします。
    ハードコーディング無しでこれを実装しようと思うと、クラスの名前をリテラル型として取得する必要があると思うのですが、現在のTypeScriptにその機能は無いのではと思います。
    (<https://stackoverflow.com/questions/64072908/get-class-name-as-type-in-typescript>)

    結局どこかでハードコーディングして指定することに変わりはないのですが、こういう実装でよろしければリテラル型を設定できると思います。

    - 案1: CustomUseBaseClass側にリテラル型の型定義を持たせる
    - <https://www.typescriptlang.org/play?#code/PTAEiHlRzR0VQZBiGRohkPUMhLhkJ0MAoEpBomoC4TmAlDIGsMygVwyB2DCqoFYM5g5gyCADIFeBgYEp2C+moLIMgu0qDSRoF+KgU3NqgEQZAFgyA7uUBZ2oCAGdAEMARgGcALgCd5AY1WgtAG3nLloAEJGApgGFDx0AG9QAX3QGjJqwFc1AewC2Nu4AjKAWAB6qFgB2ACYm5srWtiaOLm52Xr4ByQBMoRHRcWaWgXap6JhgAOLRFuoAllrKdFCAmgxIaJWggCAqmar+pcoAGoDWDIA7+oAaDIBmDAx9-gCqiYNDgDIMgBragBTq7XJKapo6esnFiYtJ7gA8ACr5kbHxJUcAvMdnxgA0oIOXAJ4ADhY3QomPb1KIAc1AzxB4IAfA50KBQLsNNpdOoLPIYj4ovpvod3D9-gAuT7JQkWADcCL02L2nh0PnUAApfp5FPpGqB6jESdCwQBKBzOUBYQA4loAeKMAFK7kACSABF0C5MAAqZWfbz9PynQZBAB0qhMyuArlpunSynJIWeAHJzZbrVTzeqstrkiFwrcigkLK6LnNssEPqo-hYfAAzfHGS1w+zU9GY7G4yMWkOQ5OWqmIrDxrE4vF21M2r4hoIOkVgQAA5oBcJRWgHJNQDhpoB1bVodEAFQzIQA-DHJEWp5KpOfVlEzVCTvb7jOc+TD+STdIPnZrx8oQrHEYj0apPOooqBVLqC-9IY9ngeLEFM85FRVgKqFwslm79YbjVpTemQ3kbaecg7XEd-UueQekCLxLuc-qDDkQYhuG77-DkMZxhiuZJqeabfhevb9loXJDiOoEPhcU4zruuF3lqhHGHkq5rhuW47nuaHHieZIfheLguEAA>
    - ※結局、元々の定義が数行上の型定義に移っただけですので、ミスはすると思います。。。
    - 案2: 元のBaseClass側にリテラル型の型定義を持たせる
    - <https://www.typescriptlang.org/play?#code/PTAEiHlRzR0VQZBiGRohkPUMhLhkJ0MAoAhgIwM4BcAnTAY31BIBtNddQAhGgUwGFraAeF0JgD3yYA7ACZ0ChAJaCA5gD5QAb3ShQOcaXKEmmYQHtBlAJ4V2uACqGADkwBcoFgG50AX3RUadFgFcCugLZsHgCMPPxCogzMgZwA5O60FtZBMfJKKlo6+kYmHolMoAC8oABE8eZWTEHFTq5l9j74-tG4AEyhAiJ0jLisphxxpnktKYrKoBl6BsZleYUlMxUt1S7o6CCgAOJCTJIkuIDmDFCAmgxIaGtggCAq3r4BpgAagNYMgDv6gBoMgGYMgIAM141+AKo9zTugBkGQAa2oAKdROgCAGLB4IgaHK0SI9f69DwcMztcJdKJ9FiyAA09kxnVA4ik0jmZJkqTGajhZHG2km2QW1jsjjGJH04i8ZF0hAAFJYvNhKBISKAJMI7FTpABKRSgVy1Uz1G4o5ohPgdCLdJga3ENJqmII09JMrLTQYVOYDXIVZJOFQETD4cWS3AC-B2PUGtF65ocWWyWRyuzkCSeI1-AEm0Yqc34LyEQSgfAAOlZ+QKOdAdoSDpiTqVKzq338ftobW1WKR+tjaPLt2CZsZmSmCPK1ltWeGxZdbolka9PuYldwHADfWDofDHrVP3HbTSCa0SZTacz1u7OaK+a7TD7Y2VQA>

    あまりオススメはできないのですが、クラス名をハードコーディングさせて型パズルで遊ぶという手もあります。

    <https://www.typescriptlang.org/play?#code/PTAEiHlRzR0VQZBiGRohkPUMhLhkJ0MAoEpBomoC4TmAlDIGsMygVwyB2DCqoFYM5g5gyCADIFeBgYEp2C+moLIMgu0qDSRoF+KgU3NqgEQZAFgyA7uUBZ2oCAGdAEMARgGcALgCd5AY1WgtAG3nLloAEJGApgGFDx0AG9QAX3QGjJqwFc1AewC2Nu4AjKAWAB6qFgB2ACYm5srWtiaOLm52Xr4ByQBMoRHRcWaWgXap6JhgAOLRFuoAllrKdFCAmgxIaJWggCAqmar+pcoAGoDWDIA7+oAaDIBmDAx9-gCqiYNDgDIMgBragBTq7XKqAJ4ADhaggwAqBxYAPIMAcvJ+R+GRsSZqDVEA5gB8oAC8x8m3e75J5FAAGcz8iyS7gAJPZ6lEAGZ1UAABScoNAAH5QKD0sozoc4ejMQAuUBRCwANzqAG4FCoNNpdPjiokoYMLidgYV4iVkr82dDjAAaf7uQEPArPUCvBHvQXg7z9SFLZJwuUfDHfezoUCgJSvZmgdQWeQxHxRfS7PTJQkWcmnc5XAF3Cyfen6rSW16eHQ+dQACn2nkU+kaoHqMXJmveAEoHM5QFhADiWgB4owAUruQAJIAEXQLkwACoi8dlQs1cEAHSqExF4CuAUQjnJEKPXlClvuK7l7LBMUAcmbleMQQHOr1JrNFqtNvx9sFA-n5zHntlqnkqgj9WUgdU5ISFi7xjj5N0O7LWWPyhCuv1+tNqk86iioFUVeXh1+Pz+S7tK4HNcXELYAS0vFVryCGs6wbVlh2FZQ8nbGVD2vHsskGHJB3gzDxwcSdTXNS1rVtdwF1-T8LByQDJzUTdt13fdOxHZQLljT5TzfSMPF7a88jve9H2fV930o78KP-Q5qKAgt0CAA>

    いろいろ示しましたが、こういうことやるくらいなら最初の実装(元の回答のPlayground実装で、ノーチェック)が私的には一番かと思います…
    私ではこれが限界です。。。
  3. @yuu_1st

    Questioner

    > クラスの名前をリテラル型として取得する必要があると思うのですが、現在のTypeScriptにその機能は無いのではと思います。
    やはり無いのですね。。参考サイトありがとうございます。

    自分自身で思いついている型チェックの方法として、もう一方の解答欄にも書かせていただいたのですが、BaseUseClassとCustomUseClassXの間にもう一つabstractクラスをそれぞれ作成して、classTypeを直接リテラル型で指定、もしくはclassTypeの代わりにクラス名(instanceof)で確認する、といった形で、厳格にするならこれが良いのかなと思っています。。

Your answer might help someone💌