はじめに
JavaScript→TypeScriptへ移行する際に、オブジェクトに対するアクセスで型チェックエラーが出て困っていました。
const obj = {
key1: "hoge",
key2: "fuga",
key3 : ""
}
// →
// const obj: {
// key1: string;
// key2: string;
// key3: string;
// }
// が型推論される
const objArray: string[] = [];
// 型チェックエラーが発生する
for (let i = 1; i <= 3; i++) {
if (obj[`key${i}`]) {
objArray.push(obj[`key${i}`]);
}
}
console.log(objArray);
objにはkey1, key2, key3のキーしか存在しないため、obj[`key${i}`]
という形でアクセスしようとすると、コンパイルエラーになります。
error TS7053: Element implicitly has an 'any' type because expression of type 'key${number}
' can't be used to index type '{ key1: string; key2: string; key3: string; }'.
上記の場合では for文中でiは1, 2, 3のいずれかに限られますが、TypeScriptではそこまで反映しての型チェックはできないです。
今回に限らず、TypeScriptの型チェックは必ずしも完璧ではないことに留意しましょう。
今回も、存在しないプロパティにアクセスする可能性があるためにエラーが発生しています。
対策1
type Obj = {
[key: `key${number}`]: string;
}
const obj: Obj = {
key1: "hoge",
key2: "fuga",
key3 : ""
}
const objArray: string[] = [];
for (let i = 0; i <= 3; i++) {
if (obj[`key${i}`]) {
objArray.push(obj[`key${i}`]);
}
}
console.log(objArray);
[key: `key${number}`]: string;
といったインデックス型を用いて、型定義してあげればいいのではないかと思い、上記コードを書きました。
型チェックは問題なく突破できるものの、
- 型定義をしなければいけないのでコード量が増えてしまう
- 実際には存在しないキー(key4など)へのアクセスも型定義上できてしまう
// TypeScriptでの型チェックは通るがランタイムエラーが発生する
console.log(obj.key4);
といったデメリットがあり、ベストな回答とは言えない気がします。
対策2
悩んだ結果、下記コードに辿り着きました。
Object.keys()やObject.values()を用いることで、オブジェクトに対する安全な動的アクセスが可能です。
型チェックに引っ掛かることも、型安全性が破壊されランタイムエラーが発生することもないです。
const obj = {
key1: "hoge",
key2: "fuga",
key3 : ""
}
const objArray = Object.values(obj).filter(value => value);
console.log(objArray);
参考文献