Typescriptオブジェクトのインデックス付きメンバーの型を強制しますか?


289

文字列->文字列のマッピングをTypescriptオブジェクトに保存し、すべてのキーが文字列にマッピングされるようにします。例えば:

var stuff = {};
stuff["a"] = "foo";   // okay
stuff["b"] = "bar";   // okay
stuff["c"] = false;   // ERROR!  bool != string

値が文字列(または任意のタイプ..)でなければならないことを強制する方法はありますか?

回答:


518
var stuff: { [key: string]: string; } = {};
stuff['a'] = ''; // ok
stuff['a'] = 4;  // error

// ... or, if you're using this a lot and don't want to type so much ...
interface StringMap { [key: string]: string; }
var stuff2: StringMap = { };
// same as above

13
この構文の興味深い点は、キーの「文字列」以外のタイプが実際に間違っていることです。JSマップが文字列によって明示的にキー付けされていることを考えると、完全に理にかなっていますが、構文が多少冗長になります。値のタイプを単に指定する '{}:string'のようなものは、生成されたコードの一部としてキーの自動強制を許可するために何らかの方法で追加しない限り、より単純に見えます。
アーメンテージ

42
numberインデックスタイプとしても使用可能
Ryan Cavanaugh

3
注目に値する:コンパイラーは値のタイプのみを強制し、キーのタイプは強制しません。あなたは何かを行うことができます[15] = '何でも'そしてそれは文句を言いません
amwinter 2015年

3
いいえ、キーのタイプを強制します。myObjectにtoString()が適切に実装されていても、stuff [myObject] = 'whatever'は実行できません。
AlexG

7
@RyanCavanaugh a type Keys = 'Acceptable' | 'String' | 'Keys'をインデックス(キー)タイプとして使用することは可能ですか(または可能ですか)?
calebboyd

130
interface AgeMap {
    [name: string]: number
}

const friendsAges: AgeMap = {
    "Sandy": 34,
    "Joe": 28,
    "Sarah": 30,
    "Michelle": "fifty", // ERROR! Type 'string' is not assignable to type 'number'.
};

ここで、インターフェイスはAgeMapキーを文字列として、値を数値として適用します。キーワードnameは任意の識別子にすることができ、インターフェイス/タイプの構文を提案するために使用する必要があります。

同様の構文を使用して、オブジェクトが共用体タイプのすべてのエントリのキーを持つように強制できます。

type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";

type ChoresMap = { [day in DayOfTheWeek]: string };

const chores: ChoresMap = { // ERROR! Property 'saturday' is missing in type '...'
    "sunday": "do the dishes",
    "monday": "walk the dog",
    "tuesday": "water the plants",
    "wednesday": "take out the trash",
    "thursday": "clean your room",
    "friday": "mow the lawn",
};

もちろん、これをジェネリック型にすることもできます!

type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";

type DayOfTheWeekMap<T> = { [day in DayOfTheWeek]: T };

const chores: DayOfTheWeekMap<string> = {
    "sunday": "do the dishes",
    "monday": "walk the dog",
    "tuesday": "water the plants",
    "wednesday": "take out the trash",
    "thursday": "clean your room",
    "friday": "mow the lawn",
    "saturday": "relax",
};

const workDays: DayOfTheWeekMap<boolean> = {
    "sunday": false,
    "monday": true,
    "tuesday": true,
    "wednesday": true,
    "thursday": true,
    "friday": true,
    "saturday": false,
};

10.10.2018の更新:以下の@dracstaxiの回答を確認してください-これのRecordほとんどを行う組み込み型があります。

1.2.2020の更新: 作成済みのマッピングインターフェイスを回答から完全に削除しました。@dracstaxiの答えは、それらを完全に無関係にします。引き続き使用する場合は、編集履歴を確認してください。


1
{[キー:番号]:T; 内部ではオブジェクトのキーは常に文字列であるため、}はタイプセーフではありません。詳細については、@ tepによる質問へのコメントを参照してください。たとえばx = {}; x[1] = 2;、Chromeで実行すると、Object.keys(x)["1"] JSON.stringify(x)が返され、 '{"1":2}'が返されます。typeof Number(例:Infinity、NaN、1e300、999999999999999999999など)のコーナーケースは、文字列キーに変換されます。また、のような文字列キーの他のコーナーケースには注意してくださいx[''] = 'empty string';x['000'] = 'threezeros'; x[undefined] = 'foo'
robocat

@robocatこれは本当であり、私はしばらくの間、この回答から数字のキーの付いたインターフェイスを削除するために編集を行ったり来たりしてきました。結局、TypeScriptが技術的かつ具体的にはキーとしての数字を許可するようになったので、それらを保持することにしました。そうは言っても、数字で索引付けされたオブジェクトを使用することに決めた人には誰でもあなたのコメントを見てもらえれば幸いです。
Sandy Gifford、

75

簡単な更新:Typescript 2.1以降Record<T, K>、辞書のように機能する組み込み型があります。

ドキュメントの例:

// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>

const names = { foo: "hello", bar: "world", baz: "bye" };
const lengths = mapObject(names, s => s.length);  // { foo: number, bar: number, baz: number }

TypeScript 2.1ドキュメント Record<T, K>

これを使用する上{[key: T]: K}で私が目にする唯一の欠点は、「キー」の代わりに使用しているキーの種類に関する有用な情報をエンコードできることです。たとえば、オブジェクトに素数キーしかない場合は、次のようにヒントを与えることができます{[prime: number]: yourType}

これらの変換を支援するために私が書いた正規表現は次のとおりです。これにより、ラベルが「キー」であるケースのみが変換されます。他のラベルを変換するには、最初のキャプチャグループを変更するだけです。

検索: \{\s*\[(key)\s*(+\s*:\s*(\w+)\s*\]\s*:\s*([^\}]+?)\s*;?\s*\}

交換: Record<$2, $3>


3
これはより多くの賛成票を獲得するはずです-それは本質的に私の回答の新しいネイティブバージョンです。
Sandy Gifford 2018年

レコードはにコンパイルされ{}ますか?
Douglas Gaskell

@DouglasGaskell型は、コンパイルされたコードには存在しません。 Records(たとえば、Javascript Maps とは異なり)は、オブジェクトリテラルの内容を強制する方法を提供するだけです。あなたがすることはできませんnew Record...const blah: Record<string, string>;にコンパイルされますconst blah;
Sandy Gifford

この回答がどれほど役に立ったか想像もできません。どうもありがとうございました:)
フェデリコグランディ

文字列ユニオンがRecordsでも機能することを言及する価値がある: const isBroken: Record<"hat" | "teapot" | "cup", boolean> = { hat: false, cup: false, teapot: true };
Sandy Gifford

10

@Ryan Cavanaughの回答はまったく問題なく、有効です。それでも、Fall'16の時点で、ES6が大多数のプラットフォームでサポートされていると主張できる場合は、データに何らかのキーを関連付ける必要がある場合は常にMapを使用する方がよいことを付け加えておきます。

書くときはlet a: { [s: string]: string; }、typescriptがコンパイルされた後、型データのようなものはなく、それはコンパイルにのみ使用されることを覚えておく必要があります。そして{[s:string]:string; }は単に{}にコンパイルされます。

そうは言っても、次のようなものを書いたとしても:

class TrickyKey  {}

let dict: {[key:TrickyKey]: string} = {}

でも、これは単に(コンパイルされませんtarget es6、あなたが買ってあげますerror TS1023: An index signature parameter type must be 'string' or 'number'.

したがって、実際には潜在的なキーとして文字列または数値に制限されているため、ここではタイプチェックを強制するという意味はそれほどありません。特に、jsが数値でキーにアクセスしようとすると、文字列に変換されることに注意してください。

したがって、キーが文字列であってもMapを使用するのがベストプラクティスであると想定するのは非常に安全であるため、次のようにします。

let staff: Map<string, string> = new Map();

4
この回答が投稿されたときにこれが可能であったかどうかはわかりませんが、今日はこれを行うことができます:let dict: {[key in TrickyKey]: string} = {}- TrickyKeyは文字列リテラルタイプです(例:)"Foo" | "Bar"
Roman Starkov

個人的には、ネイティブのtypescript形式が好きですが、標準を使用するのが最善です。それは私に本当に使えないキー「名前」を文書化する方法を提供しますが、私は「patientId」のようなものと呼ばれるキーを作成することができます:)
コーディング

この答えは絶対的に有効であり、非常に良い点を示しますが、ネイティブMapオブジェクトに固執する方がほとんど常に良いという考えには同意しません。マップには追加のメモリオーバーヘッドが伴うため、(さらに重要なことに)JSON文字列として保存されているデータから手動でインスタンス化する必要があります。それらはしばしば非常に便利ですが、純粋に型を取得するためではありません。
サンディギフォード

7

インターフェースを定義する

interface Settings {
  lang: 'en' | 'da';
  welcome: boolean;
}

キーを設定インターフェースの特定のキーにする

private setSettings(key: keyof Settings, value: any) {
   // Update settings key
}

3

@shabuncの答えに基づいて、これにより、キーまたは値(あるいはその両方)を強制したいものに強制することができます。

type IdentifierKeys = 'my.valid.key.1' | 'my.valid.key.2';
type IdentifierValues = 'my.valid.value.1' | 'my.valid.value.2';

let stuff = new Map<IdentifierKeys, IdentifierValues>();

定義のenum代わりに使用することもできtypeます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.