ジェネリック型を次のように定義しました
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;
}
上記の作業のいずれでもありません。
数値は自動的に文字列に変換されるため、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"
ご覧のとおり、文字列と数値のインデックスは交換可能です。
Javascriptでは、オブジェクトのキーは文字列にしかできません(es6
シンボルにも)。
数値を渡すと、文字列に変換されます。
let o = {};
o[3] = "three";
console.log(Object.keys(o)); // ["3"]
ご覧のとおり、常に{ [key: string]: TValue; }
を取得します。
TypeScriptを使用すると、number
sをキーとしてマップを定義できます。
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つの異なるエントリがあります。
オブジェクトキーは常に内部の文字列であり、文字列としてインデクサーを入力すると数値がカバーされますが、渡されるオブジェクトのキーを関数に認識させたい場合があります。 Array.map
のように機能するがオブジェクトを使用するこのマッピング関数を考えてみましょう。
function map<T>(obj: Object, callback: (key: string, value: any) => T): T[] {
// ...
}
key
はstring
に制限されており、値は完全に型指定されていません。おそらく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 S
とS[keyof S]
は定数型に解決されます。重複したプロパティを持つオブジェクトを渡す場合、keyof S
はプロパティ名に制限され、S[keyof S]
はプロパティ値の型に制限されます。