TypeScriptの as って何です？(型アサーションについて)

2020年8月10日　加筆修正

# TypeScriptのas って何？

TypeScriptを勉強していて as の表記を見つけました。
これは一体何なのでしょう- . - 。

## 結論
**型アサーションです。**


## もっと詳しく!!

TypeScriptさんは賢いので型推論 [^1] をしてくれます。
型アサーション(Type Assertion)とは、その推論された型や、既に型定義済みの変数の型を**上書き**します。

```typescript
const test = {} // ここでTSは　testの型はプロパティがゼロのオブジェクトね!　と型推論する
test.foo = 123  // numberなのでコンパイルエラー!! 

```

上記を解決するために **型アサーション**、やってみましょう。

```typescript
interface Test {
  foo: number
}

const test = {} as Test
test.foo = 123   // OK!!

```

これでコンパイルエラーは出なくなりました。

当然ですが、全く関係のない[^2]型には変換不可能です。

```typescript
let foo: number = 'aaa' as number  // コンパイルエラー
```


## <型> での型アサーション
実は型アサーションは `as`　以外に `<型>` 表記でも指定できます。

```typescript
const foo: any
const str = <string> foo
```

上記をasで表すと下記になります。

```typescript
const foo: any
const str = foo as string
```

２つの表記があってどっちを選ぼうか~ と思うかもしれませんが、`as`を使用する方が良さそうです。
なぜなら、JSXの構文と区別できなくなります。

```typescript
const foo = <string> aaa;
</string>

// 型アサーションなのかJSXのstringタグなのかわかりにくい...
```

**JSXと区別するために`as`を使った方が良いですね。**

## それで、asはどういう時に使うの？
jsをtsに移行途中で、エラー文をとりあえず出ないように制御する為によく使用されるようです。
つまり、確かにtsの記述として間違っているけど、移行途中の為とりあえず型推論でエラーが出ないようにしたい!　などの場合です。

```ts
const foo = 111
const bar = 'hey'

bar = foo as any
```
上記は本来であれば fooはnumberとして型推論され、コンパイルエラーになりますが、
`as any`によって`foo`を型アサーションし、コンパイルを通るようにしています。


## 型アサーションの危険性
型アサーションは便利ですが、コンパイラの型情報を上書きしているので、次のような危険性をはらみます。
・実行時エラーを起こす恐れがある
・プロパティの追加を忘れても、コンパイラが指摘してくれない。

例を交えて細かく説明します。

### 実行時エラーを起こす恐れがある

先ほど説明したように、明らかにエラーとわかる型アサーションは弾かれます。

```ts
const bar = 'test' as number
```

```:コンパイル結果
index.ts:1:13 - error TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

1 const bar = 'test' as number
              ~~~~~~~~~~~~~~~~
Found 1 error.
```

しかし、下記のように一度`any`を通して型アサーションするとどんな型にでも変換可能です。
結果コンパイルエラーを免れてしまい、実行時のエラーという一番起きて欲しくないことを起こす可能性が高まります。
バグの温床になりますね。
そもそも実行時エラーを起こさないためのtsだったのに、パワーが活かせません。

```ts
const foo: any = 'test'
const bar: number = foo as number   // 嘘つけ!
```

### プロパティの追加を忘れても、コンパイラが指摘してくれない。
下記のコードであれば、必要なプロパティがないぞ! とtsがコンパイル時にエラー表示してくれます。
このことによってイージーミスなどに気づけます。

```ts
interface Foo {
  bar: number
}
const foo : Foo = {}
```

```:コンパイル結果
index.ts:5:7 - error TS2741: Property 'bar' is missing in type '{}' but required in type 'Foo'.

5 const foo : Foo = {}
        ~~~

  index.ts:2:3
    2   bar: number
        ~~~
    'bar' is declared here.


Found 1 error.
```
barプロパティが定義されてるのにないぞ! というエラーですね。

かたや型アサーションでは、なんの指摘もされません。

```ts
interface Foo {
  bar: number
}

const foo = {} as Foo // コンパイル時なんのエラーも出ない
```

どちらが優れているかは瞭然ですね!
口うるさい(エラーがでる)のは煩わしさもありますが、それがコードの品質を守ってくれます🙂

### 結論
型アサーション(`as`)は移行期などの緊急の場合や有効な場合のみ使用し、
多用しない方が良さそうです。

## asの二重がけ？
実はasですが、一行で二重にかけることもできます。（**ダブルアサーション**)

```ts
const foo = bar as any as string
```

これは先ほど述べた、 [anyを通すとどの型へも移行できる](#実行時エラーを起こす恐れがある)、という仕組みをあえて使いたい時に有効です。
もちろん[危険性をはらみますが](#実行時エラーを起こす恐れがある)、場合によっては有効です。

```ts
function handler(event: Event) {
    let element = event as any as HTMLElement; // 一度anyを挟むことでHTMLElementとして扱える。
```


## 参考資料

[TypeScript Deep Dive　（型アサーション)](https://typescript-jp.gitbook.io/deep-dive/type-system/type-assertion)
[TypeScript Deep Dive (JavaScriptからの移行ガイド)](https://typescript-jp.gitbook.io/deep-dive/type-system/migrating)
[仕事ですぐに使えるTypeScript](https://future-architect.github.io/typescript-guide/typing.html)
[なぜアップキャストは安全で、ダウンキャストは危険なのか](https://qiita.com/RYO-4947123/items/eaeb48b6fcf97c02710f)



[^1]: 型が明示されていない場合に、型をいい感じTSが推測してくれる機能です。(http://js.studio-kingdom.com/typescript/handbook/type_inference)
[^2]: 関係がない、と言うのは、スーパータイプでもサブタイプでもない、と意味です。