TypeScript3.4 の const assertion

`const assertion` は TypeScript3.4 の新機能です。[Const contexts for literal expressions](https://github.com/Microsoft/TypeScript/pull/29510)
すでに master に入っているので、nightly build で挙動を確認することができます。

```
$ yarn add typescript@next
```
VSCode のワークスペースバージョンを切り替えれば、本稿内容の挙動を確認できます。const assertion により、個人的に待望だった widening Literal Types の抑止が叶います。


# widening Literal Types って何？

従来、const 宣言代入で得られた Literal Types は widening 挙動により、可変の変数に代入すると、Literal Types ではなくなってしまう仕様がありました。参考：https://qiita.com/Takepepe/items/2c06f65a51a12ffe4d61

NonWidening にするため次のように、全く同じ文字列で assertion を付与する必要がありました（または annotation）。

```typescript
const widening = 'LITERAL_TYPES_TEST'
const nonwidening = 'LITERAL_TYPES_TEST' as 'LITERAL_TYPES_TEST'
const check = {
  widening,
  nonwidening
}
// const widening: 'LITERAL_TYPES_TEST'
// const nonwidening: 'LITERAL_TYPES_TEST'
// const check = {
//   widening: string <- !?
//   nonwidening: 'LITERAL_TYPES_TEST'
// }
```

それが今回の変更で、次のように `as const` アサーションを付与すれば NonWidening になる様になりました。

```typescript
const widening = 'LITERAL_TYPES_TEST' as const
const nonwidening = 'LITERAL_TYPES_TEST' as 'LITERAL_TYPES_TEST'
const check = {
  widening,
  nonWidening
}
// const widening: 'LITERAL_TYPES_TEST'
// const nonWidening: 'LITERAL_TYPES_TEST'
// const check = {
//   widening: 'LITERAL_TYPES_TEST' <- 🎉
//   nonWidening: 'LITERAL_TYPES_TEST'
// }
```

次のような書き方でも widening 挙動を抑制可能です。readonly もついてきます。

```typescript
const ONE = 'ONE'
const TWO = 'TWO'
const THREE = 'THREE'

const widening = { ONE, TWO, THREE }
// const widening: {
//   ONE: string
//   TWO: string
//   THREE: string
// }
const nonwidening = { ONE, TWO, THREE } as const
// const nonwidening: {
//   readonly ONE: 'ONE'
//   readonly TWO: 'TWO'
//   readonly THREE: 'THREE'
// }
const enumLike = {
  FOUR: 'FOUR',
  FIVE: 'FIVE',
  SIX: 'SIX'
} as const
// const enumLike: {
//   readonly FOUR: 'FOUR'
//   readonly FIVE: 'FIVE'
//   readonly SIX: 'SIX'
// }
```

`export = {} as const` もばっちりです。

```typescript
export = {
  SEVEN: 'SEVEN',
  EIGHT: 'EIGHT',
  NINE: 'NINE'
} as const
```

# Tuple 宣言が楽になるぞ

Tuple 宣言の Assertion も冗長でなくなります。

```typescript
const a  = [0, 1, 2]
const t1 = [0, 1, 2] as [0, 1, 2]
const t2 = [0, 1, 2] as const

// const a: number[]
// const t1: [0, 1, 2]
// const t2: readonly [0, 1, 2]
```

# オブジェクトリテラルの戻り型推論が固定できるぞ

変更できないはずの、オブジェクトリテラルに含まれる戻り値型も Widening 扱いです。これは悔しい…

```typescript
function wideningIncrement() {
  return { type: 'INCREMENT' }
}
function wideningDecrement() {
  return { type: 'DECREMENT' }
}
function wideningSetCount(amount: number) {
  return { type: 'SET_COUNT', payload: { amount } }
}
// function wideningIncrement() {
//   { type: string } <- !?
// }
// function wideningDecrement() {
//   { type: string } <- !?
// }
// function wideningSetCount(amount: number) {
//   type: string <- !?
//   payload: { amount: number }
// }
```

これも `as const` で一発です。

```typescript
function nonWideningIncrement() {
  return { type: 'INCREMENT' } as const
}
function nonWideningDecrement() {
  return { type: 'DECREMENT' } as const
}
function nonWideningSetCount(amount: number) {
  return { type: 'SET_COUNT', payload: { amount }} as const
}
// function nonWideningIncrement() {
//   readonly { type: 'INCREMENT' } <- 🎉
// }
// function nonWideningDecrement() {
//   readonly { type: 'DECREMENT' } <- 🎉
// }
// function nonWideningSetCount(amount: number) {
//   readonly type: 'SET_COUNT'; <- 🎉
//   payload: { readonly amount: number };
// }
```

# 配列の中身が Literal Union Types でとれるぞ

`type LegacyAssertActionTypes` と `type LazyAssertActionTypes` の型は一緒です。

```typescript
const legacyAssertActions = [
  'INCREMENT' as 'INCREMENT',
  'DECREMENT' as 'DECREMENT',
  'SET_COUNT' as 'SET_COUNT'
]
const lazyAssertActions = [
  'INCREMENT',
  'DECREMENT',
  'SET_COUNT'
] as const

type Unpacked<T> = T extends { [K in keyof T]: infer U } ? U : never
type LegacyAssertActionTypes = Unpacked<typeof legacyAssertActions>
type LazyAssertActionTypes = Unpacked<typeof lazyAssertActions>
// type LegacyAssertActionTypes = 'INCREMENT' | 'DECREMENT' | 'SET_COUNT'
// type LazyAssertActionTypes   = 'INCREMENT' | 'DECREMENT' | 'SET_COUNT'
```

`type Unpacked<T>` についてはこちらを参照ください。
[TypeScript3.1 で ReduxAction型定義は不要になりました](https://qiita.com/Takepepe/items/9be98c8a05d22fd08055)

JavaScript らしさを損なわないために導入されているであろう widening 挙動。しっかり型付けしたい・けれども冗長さを払拭していきたい、という意向が伺えますね。最高です。