概要
TypeScriptについて学んでいて、インデックスシグネチャに関して感覚的には挙動が理解できなかったので、調べたことを備忘として記録しておくもの。
コード例は基本的にTypeScript Handbookから引用し、一部は自分で書いています。
参考資料
Udemy-Understanding TypeScript 日本語版
TypeScript Handbook
インデックスシグネチャとは
TSで、プロパティの具体名は指定せず、型だけを指定して定義を行う記法。
stringかnumber型のみ指定できる。以下はnumberの例
interface StringArray {
[index: number]: string;
}
// ↑数値の明示はせず、number型であることのみを定義している
const myArray: StringArray = getStringArray();
const secondItem = myArray[1];
ここからが感覚的には理解できなかった挙動になります
インデックスシグネチャでstringを指定しても、numberを使うことができる。
interface Player {
[index: string]: string;
}
const yusukeChiba: Player = {
1: 'vocal',
}
// ↑1を指定してるけど、エラーにはならない
TypeScript Handbook内にこれを根拠付ける記述がありました。
This is because when indexing with a number, JavaScript will actually convert that to a string before indexing into an object
JSではobjectのindexを理解する際に、numberで記載されていた場合はstringにコンバートしてから解釈されるとのこと。
なので上記のコードの1も、実際には'1'というstringと解釈されるため問題にならない(=定義に反していない)とのこと。
interface Player {
[index: string]: string;
}
const yusukeChiba: Player = {
part: 'vocal',
}
// ↑1を指定してるけど、エラーにはならない
これは調べてないんですが、stringで定義した後に、上記の様にシンボルでpartと書いて書きエラーが出ないことにも全く違和感を覚えてなかったんですが、これもちゃんと'part'とstringとして解釈してもらっているからかな?と思うなどしました(真偽はわかってません)
number、string両方のインデックスシグネチャを指定した場合、numberに対するvalueはstringに対するvalueのsubtypeでなくてはならない
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
// 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.というエラーが発生する
interface Okay {
[x: string]: Animal;
[y: number]: Dog;
}
// これはDogがAnimalのsubtypeなのでok
これも原理はさきほどの例と同じ。
こちらとしてはyとしてnumberを指定指定したとしても、JSの挙動でstringにconvertされて解釈されるということは、同時にx側(stringと定義した側)として読み取られる。
なので、number側で指定したvalueが、string側のvalueとしても成立しなければならない。
この様な状態が成立するのは、number側のvalueがstring側のvalueのsubtypeである時なのでタイトルの通りとなる。
インデックスシグネチャを指定した型に他のpropertyを指定することはできるが、valueは同じ型でなければならない
interface NumberDictionary {
[index: string]: number;
length: number;
name: string;
}
// Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
これもTypeScript Handbookに説明があったので引用します
This is because a string index declares that obj.property is also available as obj["property"]~
要はこれもnameプロパティを指定したとして、インデックスシグネチャ側の定義に沿ったものとして解釈されうるから、ということなんでしょうか。
interface NumberDictionary {
[index: string]: number;
length: number;
name: number;
}
const sample: NumberDictionary = {
name: 2,
length: 5,
}
console.log(sample['name']); // => 2
確かに上記の様に書くことができるので、インデックスシグネチャ側の定義にそって解釈されるとvalueがnumberでないとダメ、というのは納得感があります。
感想
TS、安全性が高くありがたいな〜といつも書いてて思いますが、コンパイラの挙動だったりJSの挙動だったりを理解してないと理解できない挙動もたまにあるので、そういう所の理解は掘り下げが必要だなあ、と思いました。