自戒と備忘録として。typescript 3.4.5 で以下は確認しています。
typescript 3系で unknown 型が導入され、オッ外部からのデータを受け付ける箇所から any が撲滅できるのではないか、みたいなことを思ったことがあるわけです。
ただ、
type User = {
name: string;
age: number;
};
const data: unknown =
JSON.parse(`{ "user": { "name": "user", "age": 18 } }`);
こういう unknown なデータがあった時に、なんとなく以下のように書けそうに思うけどうまくいかない。
if(data && typeof data === 'object'){
if("user" in data){
const user = data.user; // Error, data["user"] でもいっしょ
if (
user &&
typeof user === "object" &&
typeof user.name === "string" &&
typeof user.age === "number"
) {
doSomethingFor(user)
}
}
}
プロパティ 'user' は型 'object' に存在しません。ts(2339)
とそっけなく言われてしまう。それはそうかもしれないけど、if("user" in data)
してるんだからそこは推論してほしい気持ちはある。一度 any とか {}
にキャストしてしまうのも一つの方法だが、率直に意図が読みづらいコードになるのは間違いない。
ここはタイプガード関数を通すとよいみたい。
const hasUser = (d: any): d is { user: User } => {
if (!d) return false;
if (d.user && typeof d.user === "object") {
if (
typeof d.user.name === "string" &&
typeof d.user.age === "number"
) {
return true;
}
}
return false;
};
// 以下は通る。
if (hasUser(data)) {
const user: User = data.user;
}
あれ、 any が出現しているぞ・・・? でもこの hasUser
が公開する受け入れ可能な引数の型が any なのは自然なのだ。unknown しか受け付けない関数とか、(外から見たら)意味がわからない。
というわけで、
- unknown は any を返す関数の戻り値を受け取るときに使う
- unknown で受けたあとは明示的に型チェックを行うタイプガードを通す
- プリミティブでないことが期待されるデータを unknown で受けたら、どこかで any が混入することにはそういうものです
というところに落ち着いたがまだ釈然としていない。
個人的には if("user" in data && typeof data.user)
は許してほしかったところだけど。でも、 unknown
の問題というより typescript の object
型の扱いに問題があるような気がしてきた。
関連 Issue については引き続き眺めていきたいところ。