const assertion
は TypeScript3.4 の新機能です。Const contexts for literal expressions
すでに 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)。
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 になる様になりました。
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 もついてきます。
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
もばっちりです。
export = {
SEVEN: 'SEVEN',
EIGHT: 'EIGHT',
NINE: 'NINE'
} as const
Tuple 宣言が楽になるぞ
Tuple 宣言の Assertion も冗長でなくなります。
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 扱いです。これは悔しい…
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
で一発です。
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
の型は一緒です。
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型定義は不要になりました
JavaScript らしさを損なわないために導入されているであろう widening 挙動。しっかり型付けしたい・けれども冗長さを払拭していきたい、という意向が伺えますね。最高です。