web-dev-qa-db-ja.com

Typescriptのオブジェクトインデックスキータイプ

ジェネリック型を次のように定義しました

interface IDictionary<TValue> {
    [key: string|number]: TValue;
}

しかし、TSLintは不平を言っています。どちらかをキーとして持つことができるオブジェクトインデックスタイプを定義するにはどうすればよいですか?私もこれらを試してみましたが、運はありませんでした。

interface IDictionary<TKey, TValue> {
    [key: TKey]: TValue;
}

interface IDictionary<TKey extends string|number, TValue> {
    [key: TKey]: TValue;
}

type IndexKey = string | number;

interface IDictionary<TValue> {
    [key: IndexKey]: TValue;
}

interface IDictionary<TKey extends IndexKey, TValue> {
    [key: TKey]: TValue;
}

上記の作業のいずれでもありません。

それではどうですか?

23
Robert Koritnik

数値は自動的に文字列に変換されるため、IDictionary<TValue> { [key: string]: TValue }を使用するだけでこれを実現できます。

以下に使用例を示します。

interface IDictionary<TValue> {
    [id: string]: TValue;
}

class Test {
    private dictionary: IDictionary<string>;

    constructor() {
       this.dictionary = {}
       this.dictionary[9] = "numeric-index";
       this.dictionary["10"] = "string-index"

       console.log(this.dictionary["9"], this.dictionary[10]);
    }
}
// result => "numeric-index string-index"

ご覧のとおり、文字列と数値のインデックスは交換可能です。

26
Nickeat

Javascriptでは、オブジェクトのキーは文字列にしかできません(es6シンボルにも)。
数値を渡すと、文字列に変換されます。

let o = {};
o[3] = "three";
console.log(Object.keys(o)); // ["3"]

ご覧のとおり、常に{ [key: string]: TValue; }を取得します。

TypeScriptを使用すると、numbersをキーとしてマップを定義できます。

type Dict = { [key: number]: string };

また、コンパイラは、値を割り当てるときに常にキーとして数値を渡すことをチェックしますが、実行時にはオブジェクトのキーは文字列になります。

したがって、次の理由により、{ [key: number]: string }または{ [key: string]: string }のいずれかを使用できますが、string | numberの和集合は使用できません。

let d = {} as IDictionary<string>;
d[3] = "1st three";
d["3"] = "2nd three";

dには2つの異なるエントリがありますが、実際には1つしかありません。

できることは、Mapを使用することです。

let m = new Map<number|string, string>();
m.set(3, "1st three");
m.set("3", "2nd three");

ここには、2つの異なるエントリがあります。

19
Nitzan Tomer

オブジェクトキーは常に内部の文字列であり、文字列としてインデクサーを入力すると数値がカバーされますが、渡されるオブジェクトのキーを関数に認識させたい場合があります。 Array.mapのように機能するがオブジェクトを使用するこのマッピング関数を考えてみましょう。

function map<T>(obj: Object, callback: (key: string, value: any) => T): T[] {
    // ...
}

keystringに制限されており、値は完全に型指定されていません。おそらく10回のうち9回は問題ありませんが、もっとうまくやることができます。このような愚かなことをしたいとしましょう:

const obj: {[key: number]: string} = { 1: "hello", 2: "world", 3: "foo", 4: "bar" };
map(obj, (key, value) => `${key / 2} ${value}`);
// error: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type.

最初に数値にキャストしないと、キーに対して算術演算を実行できません(JSでは"3" / 2が有効であり、numberに解決されることに注意してください)。マップ関数で少しトリッキーな入力を行うことで、これを回避できます。

function map<S, T>(obj: S, callback: (key: keyof S, value: S[keyof S]) => T): T[] {
    return Object.keys(obj).map(key => callback(key as any, (obj as any)[key]));
}

ここでは、ジェネリックSを使用してオブジェクトを入力し、そこからキーと値のタイプを直接検索します。オブジェクトがジェネリックインデクサーと値を使用して入力されている場合、keyof SS[keyof S]は定数型に解決されます。重複したプロパティを持つオブジェクトを渡す場合、keyof Sはプロパティ名に制限され、S[keyof S]はプロパティ値の型に制限されます。

0
Sandy Gifford