🌟

型アノテーションと型アサーション

2024/03/18に公開

サバイバルTypeScriptを参考にまとめました

型アノテーション

TypeScriptでは変数宣言するときに、その変数にどんな値が代入可能かを指定できます。その指定のことを型注釈(type annotation; 型アノテーション)と言います。

型アノテーションの書き方

const num: number = 123
const array: strng[] = ['apple', 'orange', 'grape']

しかし、実際にはTypeScriptには型推論(type inference)と呼ばれる機能があるため、分かり切っている型を書く必要はりません。

賢い型推論

const num = 123 // numはnumber型だと推論してくれる
const array = ['apple', 'orange', 'grape'] // arrayはstringの配列だと推論してくれる

ここで問題になってくるのは、開発者はこの返り値にはこの型しか来ないとわかっているにも関わらず、型推論の結果、その型以外も推論してしまう場合です。

型推論が不正確なパターン

たとえば、なにかしかのライブラリの提供する関数を利用するとします。
開発者は関数を使用するコンテキストが分かっていて、特定の引数を渡すことを知っているので、返り値にどのような型が返るか知っています。
例えばこんな関数があったとして

function hoge(value: namber | undefinde): number | undefinde {
  if (value === undefinde) return undefinde
  return value * 2
}

実際のこの関数を利用する際、開発者はhugahugaにはnumber型しか来ないことは知っていますが
TypeScriptはそれを理解できない場合

const result = hoge(hugahuga) 
// Typescriptが推論しているresultの型は number | undefind
const roundResult = Math.round(result) // 数値に対する関数を書くと怒られてしまう

となり、分かり切っているのに、コードを通してくれないということになります。

これにはいくつか対応策があります。
その一つが型アサーションです。

型アサーション

型アサーションは型推論を上書きする機能です。
上記で示したようにTypeScriptが行う型推論より、開発者がその型に詳しい時、正しい型をコンパイラに伝えます。
書き方が二つあります

型アサーションの書き方

一つはas構文です

const value: string | number = "this is a string"
const strLength: number = (value as string).length

もう1つはアングルブラケット構文(angle-bracket syntax)です。

const value: string | number = "this is a string"
const strLength: number = (<string>value).length

as構文のほうが用いられているみたいです。アングルブラケット構文はJSXとやらと見分けがつきづらいみたいです。

型アサーションも無敵ではない

明らかに違うだろ、っていうのは注意されます
例えばnumber型をstring型にする型アサーションはコンパイルエラーになります。

const num = 123;
const str: string = num as string;
// エラーになる→Conversion of type 'number' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

このエラーは「number型をstring型にするのは間違いです。お互いの型に共通する部分が少なすぎるからです」という内容です。
それでも自分の書いた型アサーションが正しいという場合は、unknown型を経由することで上のようなエラーを出さないようにもできますが、自分はこの状況に出会ったことが無いので使うべきなのか分かりません。
素人考えではそれはもはや型定義を間違っているのではないかと思っちゃいます
詳しい人いたら使いどころ教えてください

const num = 123;
const str: string = num as unknown as string; // OK

乱用は禁物

型アサーションは型推論を上回る力をもってます。本来そうすべきでないものも型アサーションしてしまった場合、バグの原因になります。

interface User {
    id: number
    username: string
}
 
const alice: User = {} // Type '{}' is missing the following properties from type 'User': id, username
const bob = {} as User // OK

const alice: User = {
    id: 1,
    username: 'alice',
    password: 'passowrd'
}
// Type '{ id: number; username: string; password: string; }' is not assignable to type 'User'.Object literal may only specify known properties, and 'password' does not exist in type 'User'.
 
const bob = {
    id: 2,
    username: 'bob',
    password: 'passowrd'
} as User // OK

なので、利用する際にはバグに十分注意する必要があり、できるだけ、コンパイラーの型推論に頼ったほうが安全なので、型アサーションは、やむを得ない場合にのみ使用したほうがよいそうです。

型アサーションが必要な場面で、型アサーションを使用しない方法のひとつとして型ガードがあります

型ガード

最初に戻って

function hoge(value: namber | undefinde): number | undefinde {
  if (value === undefinde) return undefinde
  return value * 2
}
const result = hoge(hugahuga) 
// TypeScriptが推論しているresultの型は number | undefind

このときの型ガードはどうしたらいいでしょうか
開発者はundefindeになり得ないことを知っています
もしundefindeになったとしたらそれは意図しないバグが発生している可能性があります
エラーを発生させて検知することにしましょう

const result = hoge(hugahuga) 
if (result === undefinde) throw new Error('ERROR!:result is undefinde')
// resultがundefindeの場合はエラーとなり処理が終了するので、以下はnumberの時しか評価されない
const roundResult = Math.round(result) // OK

これで、型アサーションをしようせず、安全に型を限定して使用することができました

Discussion