Typescriptインターフェースのキーを文字列の配列として取得する


102

私はたくさんのテーブルを持っています Lovefieldにあり、それぞれのインターフェースには、それらが持つ列があります。
例:

export interface IMyTable {
  id: number;
  title: string;
  createdAt: Date;
  isDeleted: boolean;
}

このインターフェイスのプロパティ名をこのような

const IMyTable = ["id", "title", "createdAt", "isDeleted"];

配列にしIMyTableたいのですが、インターフェイスのオブジェクト/アレイを直接作成することはできません。これは、テーブルのインターフェイス名を動的に取得するためです。したがって、インターフェイスでこれらのプロパティを反復処理して、配列を取得する必要があります。

どうすればこの結果を達成できますか?

回答:


52

以下のよう活字体2.3(または私は言うべき2.4のように、2.3この機能が含まれているバグで修正されましたtypescript@2.4-devを)、あなたは何をしたい達成するために、カスタムトランスを作成することができます。

実際、私はすでにそのようなカスタムトランスを作成しており、次のことを可能にしています。

https://github.com/kimamula/ts-transformer-keys

import { keys } from 'ts-transformer-keys';

interface Props {
  id: string;
  name: string;
  age: number;
}
const keysOfProps = keys<Props>();

console.log(keysOfProps); // ['id', 'name', 'age']

残念ながら、現在カスタムトランスはそれほど簡単に使用できません。tscコマンドを実行する代わりに、TypeScript変換APIでこれらを使用する必要があります。カスタムトランスフォーマーのプラグインサポートを要求する問題があります。


お返事ありがとうございます。昨日はすでにこのカスタムトランスフォーマーを確認してインストールしましたが、これはtypescript 2.4を使用しているため、現時点では役に立たないものです。
Tushar Shukla

16
こんにちは、このライブラリはまさに私の要件にも対応ts_transformer_keys_1.keys is not a functionしていますが、ドキュメントの正確な手順に従っているときに取得しています。これに回避策はありますか?
Hasitha Shan 2017

きちんと!動的型パラメーター(readmeの注2)を取るように拡張できると思いますか?
ケンシン2017

@HasithaShanはドキュメントを詳しく調べます-パッケージを機能させるにはTypeScriptコンパイラAPIを使用する必要があります
Yaroslav Bai

2
残念ながら、私がいつも得ていることは何でもパッケージが壊れていますts_transformer_keys_1.keys is not a function
fr1sk

17

以下では、自分でキーを一覧表示する必要がありますが、少なくともTypeScriptが適用され、まったく同じキーIUserProfileIUserProfileKeys持ちます(Required<T>TypeScript 2.8で追加されました)。

export interface IUserProfile  {
  id: string;
  name: string;
};
type KeysEnum<T> = { [P in keyof Required<T>]: true };
const IUserProfileKeys: KeysEnum<IUserProfile> = {
  id: true,
  name: true,
};

かなりクールなトリック。これで、のすべてのキーの実装を強制するIUserProfileことが簡単になり、constからそれらを簡単に抽出できますIUserProfileKeys。これはまさに私が探していたものです。すべてのインターフェースをクラスに変換する必要はありません。
Anddo

13

同様の問題があり、インターフェースとオブジェクトの両方を使用したいプロパティの巨大なリストがありました。

注:プロパティを2回(キーボードで入力)書きたくありませんでした。 ただ乾燥。


ここで注意すべき点の1つは、インターフェイスはコンパイル時に型が強制されるのに対し、オブジェクトはほとんどがランタイムであるということです。(出典

@derekが別の回答で述べたように、インターフェイスオブジェクトの共通の特徴は、値の両方を提供するクラスにすることができます

したがって、TL; DR、次のコードはニーズを満たす必要があります。

class MyTableClass {
    // list the propeties here, ONLY WRITTEN ONCE
    id = "";
    title = "";
    isDeleted = false;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// This is the pure interface version, to be used/exported
interface IMyTable extends MyTableClass { };

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Props type as an array, to be exported
type MyTablePropsArray = Array<keyof IMyTable>;

// Props array itself!
const propsArray: MyTablePropsArray =
    Object.keys(new MyTableClass()) as MyTablePropsArray;

console.log(propsArray); // prints out  ["id", "title", "isDeleted"]


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Example of creating a pure instance as an object
const tableInstance: MyTableClass = { // works properly!
    id: "3",
    title: "hi",
    isDeleted: false,
};

ここでは活字体遊び場で上記のコードはもっとプレーすることです)

PS。クラスのプロパティに初期値を割り当てたくない場合は、型を維持したまま、コンストラクターのトリックを実行できます。

class MyTableClass {
    // list the propeties here, ONLY WRITTEN ONCE
    constructor(
        readonly id?: string,
        readonly title?: string,
        readonly isDeleted?: boolean,
    ) {}
}

console.log(Object.keys(new MyTableClass()));  // prints out  ["id", "title", "isDeleted"] 

TypeScript Playgroundのコンストラクタトリック


propsArrayあなたがが鍵を初期化したときにのみアクセス可能です。
denkquer

「初期化された」@denkquerの意味がわかりません。最初の例でpropsArrayは、tableInstanceifの前にが利用可能です。つまり、インスタンスの初期化の前です。ただし、で割り当てられた偽の値を参照している場合MyTableClass、それらはプロパティの「タイプ」をほのめかすためにそこにあるだけです。それらが必要ない場合は、PSの例のコンストラクタートリックを使用できます。
アイディン

1
私の理解では、値は何らかの値を持つときに初期化されます。初期化されていない変数と型は実行時に削除されるため、単にをMyTableClass後者で置き換えることができず、でキーを受け取ることができないため、「コンストラクターのトリック」は誤解を招くものですpropsArray。常に何らかの種類のデフォルト値を提供する必要があります。で初期化するのundefinedが最善の方法であることがわかりました。
denkquer

申し訳ありません@denkquer、私のコンストラクタのトリックが欠落していたreadonly?paramteresに。更新しました。ご指摘ありがとうございます。今、私はそれがあなたが言った方法で誤解を招くように機能し、機能できないと思います。:)
アイディン

@Aidinは解決策をありがとう。また、パラメーターの初期化を回避できるかどうかも考えています。コンストラクタトリックを使用すると、MyTableClassを拡張するインターフェイスを作成できなくなります。typescriptプレイグラウンドリンクのコンストラクタトリックは空です
Flion

10

これはうまくいくはずです

var IMyTable: Array<keyof IMyTable> = ["id", "title", "createdAt", "isDeleted"];

または

var IMyTable: (keyof IMyTable)[] = ["id", "title", "createdAt", "isDeleted"];

10
それが間違っているというわけではありませんが、ここで明確にするためには、単に「配列の値を強制する」だけです。開発者は、それらを手動で2回書き留める必要があります。
Aidin

アイディンが言ったことは真実かもしれないが、いくつかのケースでは、これは私の場合、まさに私が探していたものでした。ありがとうございました。
ダニエル

4
これは、キーの重複または欠落したキーを防止しません。いいねvar IMyTable: Array<keyof IMyTable> = ["id", "createdAt", "id"];
ford04

私にとっては、オプションでキーを受け入れるだけで、インターフェースで定義されたキー以外のものを受け入れたくないため、それも私が探していたものでした。上記のコードでは、これがデフォルトであるとは思っていませんでした。そのための共通のTS方法がまだ必要だと思います。上記のコードをありがとうございます!
nicoes

8

遅すぎるかもしれませんが、typescriptのバージョン2.1では次のkey ofように使用できます。

interface Person {
    name: string;
    age: number;
    location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string

ドキュメント:https : //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types


回答ありがとうございます。静的に作成されたタイプのインターフェイスを使用するのに役立つかどうかはわかりません。私見、ほとんどの場合、インターフェース/タイプを交換可能に使用できます。さらに、複数のインターフェースのタイプを手動で作成する必要があります。ただし、誰かがインターフェイスから型を取得する必要があるだけの場合、ソリューションは適切に見えます。
Tushar Shukla

6

IMyTableインターフェイスとして定義する代わりに、クラスとして定義してみてください。typescriptでは、インターフェースのようなクラスを使用できます。

したがって、例として、次のようにクラスを定義/生成します。

export class IMyTable {
    constructor(
        public id = '',
        public title = '',
        public createdAt: Date = null,
        public isDeleted = false
    )
}

インターフェースとして使用してください:

export class SomeTable implements IMyTable {
    ...
}

キーを取得:

const keys = Object.keys(new IMyTable());

5

インターフェイスを実装するクラスを作成してインスタンス化しObject.keys(yourObject)、プロパティを取得するために使用する必要があります。

export class YourClass implements IMyTable {
    ...
}

その後

let yourObject:YourClass = new YourClass();
Object.keys(yourObject).forEach((...) => { ... });

私の場合は機能しません、インターフェイスのプロパティをリストする必要がありますが、それは私が望んでいることではありませんか?インターフェイスの名前が動的になり、そのプロパティを決定します
Tushar Shukla

これによりエラーが発生します(v2.8.3)。Cannot extend an interface […]. Did you mean 'implements'?ただし、implements代わりにを使用すると、インターフェースを手動でコピーする必要があります。これは、まさに私が望まないことです。
ジェイコブ2018年

@ジェイコブ申し訳ありませんが、それはあったはずでありimplements、私は私の答えを更新しました。@basaratが述べたように、インターフェイスは実行時に存在しないので、唯一の方法はそれをクラスとして実装することです。
Dan Def

あなたはインターフェースの代わりにクラスを使うということですか?残念ながら、インターフェースはサードパーティ(@types/react)からのものであるため、私はできません。手動でコピーしましたが、将来の保証はほとんどありません😪非ライフサイクルメソッド(既にバインドされている)を動的にバインドしようとしていますが、React.Component(クラス)で宣言されていません。
ジェイコブ2018年

いいえ、つまり、サードパーティのインターフェースを実装するクラスを作成し、実行時にそのクラスのプロパティを取得します。
Dan Def

4

できません。インターフェースは実行時に存在しません。

回避策

タイプの変数を作成して使用Object.keysしますuse


1
このような意味ですか:var abc: IMyTable = {}; Object.keys(abc).forEach((key) => {console.log(key)});
Tushar Shukla

4
いいえ、そのオブジェクトにはキーがないためです。インターフェースはTypeScriptが使用するものですが、JavaScriptで蒸発するため、「リフレクション」または「インタースペクション」を通知する情報は残っていません。JavaScriptが知っているのは、空のオブジェクトリテラルがあることだけです。TypeScriptに、インターフェイス内のすべてのキーを含む配列またはオブジェクトをソースコードに生成する方法が含まれるのを待つ(または要求する)ことが唯一の希望です。または、ダン・デフが..あなたは、クラスを使用することができれば、あなたはすべてのインスタンスのプロパティの形で定義されたキーを持つことになります、と言うように
ジェスパー

1
また、この行ではTypeScriptによってエラーが発生var abc: IMyTable = {}します。空のオブジェクトリテラルがそのインターフェイスの形状に準拠していないためです。
Jesper、2017年

16
これが機能しない場合、なぜこの回答に賛成票があるのですか?
dawez

1
反対投票の理由:null許容値では機能しないことについての言及はありません
TamusJRoyce '31 / 10/19

4

インターフェイスからキー配列を作成する簡単な方法はありません。型は実行時に消去され、オブジェクト型(順序なし、名前付き)は、なんらかのハックなしにタプル型(順序付き、名前なし)に変換できません。


オプション1:手動アプローチ

// Record type ensures, we have no double or missing keys, values can be neglected
function createKeys(keyRecord: Record<keyof IMyTable, any>): (keyof IMyTable)[] {
  return Object.keys(keyRecord) as any
}

const keys = createKeys({ isDeleted: 1, createdAt: 1, title: 1, id: 1 })
// const keys: ("id" | "title" | "createdAt" | "isDeleted")[]

(+)単純な(-)配列の戻り値型、タプルなし(+-)自動補完による手動書き込み

拡張機能:凝ったデザインにして、再帰型を使用してタプルを生成できます。これは、パフォーマンスが大幅に低下するまで、いくつかの小道具(〜5,6)でしか機能しませんでした。深くネストされた再帰型もTSでは正式にサポートされていません。完全を期すために、ここではこの例を挙げます。


オプション2:TSコンパイラAPIに基づくコードジェネレーター(ts-morph

// ./src/mybuildstep.ts
import {Project, VariableDeclarationKind, InterfaceDeclaration } from "ts-morph";

const project = new Project();
// source file with IMyTable interface
const sourceFile = project.addSourceFileAtPath("./src/IMyTable.ts"); 
// target file to write the keys string array to
const destFile = project.createSourceFile("./src/generated/IMyTable-keys.ts", "", {
  overwrite: true // overwrite if exists
}); 

function createKeys(node: InterfaceDeclaration) {
  const allKeys = node.getProperties().map(p => p.getName());
  destFile.addVariableStatement({
    declarationKind: VariableDeclarationKind.Const,
    declarations: [{
        name: "keys",
        initializer: writer =>
          writer.write(`${JSON.stringify(allKeys)} as const`)
    }]
  });
}

createKeys(sourceFile.getInterface("IMyTable")!);
destFile.saveSync(); // flush all changes and write to disk

このファイルをでコンパイルして実行すると、次の内容のtsc && node dist/mybuildstep.jsファイル./src/generated/IMyTable-keys.tsが生成されます。

// ./src/generated/IMyTable-keys.ts
const keys = ["id","title","createdAt","isDeleted"] as const;

(+)自動ソリューション(+)正確なタプルタイプ(-)にはビルドステップが必要


PS:を選択ts-morphしました。これは、元のTSコンパイラAPIの単純な代替手段であるためです。


-1

できません。インターフェースは実行時に存在しません(@basaratが言ったように)。

今、私は以下で作業しています:

const IMyTable_id = 'id';
const IMyTable_title = 'title';
const IMyTable_createdAt = 'createdAt';
const IMyTable_isDeleted = 'isDeleted';

export const IMyTable_keys = [
  IMyTable_id,
  IMyTable_title,
  IMyTable_createdAt,
  IMyTable_isDeleted,
];

export interface IMyTable {
  [IMyTable_id]: number;
  [IMyTable_title]: string;
  [IMyTable_createdAt]: Date;
  [IMyTable_isDeleted]: boolean;
}

たくさんのモデルがあると想像して、みんなのためにそれをしてください...それはとても高価な時間です。
William Cuervo

-6
// declarations.d.ts
export interface IMyTable {
      id: number;
      title: string;
      createdAt: Date;
      isDeleted: boolean
}
declare var Tes: IMyTable;
// call in annother page
console.log(Tes.id);

1
typescript構文は実行時に使用できないため、このコードは機能しません。typescript playgroundでこのコードをチェックすると、JavaScriptにコンパイルさconsole.log(Tes.id)れるのはもちろん、エラー「Uncaught ReferenceError:Tes is not defined」
Tushar Shukla
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.