項目18 推論可能な型でコードを乱雑にしない
型推論の活用
TypeScriptでは型推論が効くため、多くの型アノテーションは不要。すべての変数に型を宣言するのは非効率で、悪いスタイルとしてみなされる code:ts
interface Product {id: number; name: string; price: number;}
function logProduct(product: Product) {
const {id, name, price} = product;
console.log(id, name, price);
}
上記のProductのidをstring型に変更した場合でも、型推論が行われ、変更によるエラーが発生したかどうか気づきやすい
また、型推論を活かして変更時の影響が最小限になるようなコードを書くことが大切
下記のコードは型を明示的に書きすぎて、リファクタリングが容易でなくなっている
code:ts
function logProduct(product: Product) {
const id: number = product.id; // product.idの型が変わったら都度型を調整する必要がある
const name: string = product.name;
const price: number = product.price;
console.log(id, name, price);
}
TypeScriptが型を決定するのに材料不足の場合は、型アノテーションが必要になる
関数のパラメータ型を指定する
デフォルト値が存在する場合は、型アノテーションを省略できる
変数の型は、一般的に最初に登場したときに決定される
理想的なコードは、関数やメソッドのシグネチャには型アノテーションがつくが、その中で作成されるローカル変数にはつかないような状態
関数が肩宣言を持つライブラリのコールバックとして利用される場合でも、通常パラメータの型は推論される
code:ts
// 悪い書き方
app.get('/health', (request: express.Request, response: express.Response) => {
response.send('OK');
});
// 良い書き方
app.get('/health', (request, response) => { // requestとresponseは型推論される
response.send('OK');
});
型推論される場合でも、型を指定したい状況
オブジェクトリテラルを使う場合
code:ts
const elmo: Product = {
name: 'Tickle Me Elmo',
id: '048188 627152',
price: 28.99,
};
オブジェクトの定義に誤りがあった場合、使用している場所で型エラーが発生してくれる
大規模なコードベースでは特に有効
外部APIをコールする関数を実装する場合
関数の戻り値の型を明示的にPromise<T>で指定することで、関数実装時に型推論によって意図しない型が返されるケースを防ぐことができる
すべてのreturnが返す型を統一できる
code:ts
function getQuote(ticker: string): Promise<number> {
if (ticker in cache) {
// ~~~ Type 'number' is not assignable to type 'Promise<number>'
// 型 'number' を型 'Promise<number>' に割り当てることはできません。
}
// ...
}
名前付きの型を使いたい場合
下記の例は、引数は名前付きの型を利用しているのに、戻り値の型には名前がないので違和感がある
https://scrapbox.io/files/68138469463cd31028858ef8.png
推論される型が複雑な場合、名前付きの型を使ったほうが理解しやすくなる
上記のケース以外は型アノテーションは使わずに、型推論に任せるようにする
typescript-eslintのno-inferrable-typesルールを使えば、全ての型アノテーションが本当に必要か確認するのに役立つ