【TypeScript】空のオブジェクトにプロパティを後付けするときの型指定
JavaScriptというか動的型付けの言語だと、オブジェクトを作るときに
function getObj() {
const obj = {}
obj.foo = 'a'
obj.bar = 'b'
return obj
}
といった感じに(ただし実際にはもう少し複雑な場合に)、とりあえず空のオブジェクトを作ってプロパティを後付けしたくなるときがあるが、それTypeScriptだとどうするのがいいんだろうと疑問に思った。
とりあえず、返り値として想定しているのは次のような型
type FooBar = {
foo: string
bar: string
}
なので、関数の返り値の型注釈としては
function getObj(): FooBar {
const obj = {}
obj.foo = 'a'
obj.bar = 'b'
return obj
}
としたいところなのだが、obj
を宣言した時点で型が{}
と推論されるので、次の2つの問題がある。
-
foo
やbar
を追加できない -
return
時にも{}
型判定なことは変わらず、FooBar
型を返していることにはならない
foo
やbar
を追加できない
1. JSからTSに移行するときは一旦無視して、単純に一からTSで動的にプロパティをつけたいケースであれば、これは書き方を少し変えれば回避はできる。obj.(プロパティ)
の形で存在しないプロパティにアクセスしているから怒られるのであって、次のような書き方であれば問題はない。
function getObj(): FooBar {
let obj = {}
obj = { ...obj, foo: 'a' }
obj = { ...obj, bar: 'b' }
return obj
}
{}
型のobj
に{ foo: 'a' }
のようなより詳細な型(※構造的部分型の考え方で互換性があるもの)を代入するのは問題ない。すごくざっくりしたことを言うとAnimal
型の変数にDog
型のオブジェクトを代入しても問題ないのと一緒。逆は当然ダメ。
ただし、この場合にもreturn
時までずっとobj
は{}
型の判定であることは変わらず2の問題は残っている。
2. return時にも{}型判定なことは変わらず、FooBar型を返していることにはならない
これは結局、帰り値で返しているものが{}
型であるという部分が問題なので、方針としては
- 型アサーションを使って
obj
の型を手動で変える - そもそも
obj
を使わない
の2パターンが思いつく。
前者の場合だと次のような形になる。
function getObj(): FooBar {
const obj = {} as FooBar
obj.foo = 'a'
obj.bar = 'b'
return obj
}
この場合、obj
の宣言時点でFooBar
型の扱いになるので、1の問題も解決される。一番最初にあげたコードから型を付けたくらいで、処理は変わっていないのでJSからの移行時であれば最も手軽なような気がする。ただ、obj
を宣言した時点でFooBar
扱いなので、obj
をreturn
する時点で必要なプロパティが揃っていることは保証されない(例えばobj.bar = 'b'
の行がなかったとしてもエラーにはならない)という欠点がある。
後者の場合だと次のような形になる。
function getObj(): FooBar {
const foo = 'a'
const bar = 'b'
return { foo, bar }
}
趣旨がずれている気もするが、結局foo
やbar
の値を取得するまでに、もう少し実際は複雑な段階があって、それをobj
に各段階で追加していきたいというよう目的であれば、その目的自体は達成できている。当然obj
がないので1の問題も発生しない。
結論
以下のようにするのが一番自然な気がする。
- TSで一からコーディングするなら、
{}
のオブジェクトをとりあえず作っておくような書き方はせず、必要な情報が出揃ってからオブジェクトを作る。 - JSからの移行なら型アサーションを使う。
疑問...
もう少し空気を読んで型推論を更新してくれたりする方法はないのだろうか。
何も考えてないがこんな感じ
function getObj(): FooBar {
const obj = {}
obj.foo = 'a' // { foo: string } と型推論が更新される
obj.bar = 'b' // { foo: string, bar: string } と型推論が更新される
return obj
}
ただ、勝手に型推論が更新されると困るので、特定の注釈がある場合に更新するとか?ただ、型アサーションを各段階でフルで書くのはそれは面倒臭いのです。
良い方法とか知っている人いたら気軽にコメントください。