はじめに
TypeScript 2.0で追加されるTagged (or discriminated) union typesにより、String literal typesのプロパティでif文やswitch文でObjectの型を絞り込むことができるが、同じく2.0で追加されるThe never typeと合わせて使うと、絞り込み対象のUnion Typesを網羅的にif文やswitch文で処理しているかどうかをコンパイラでチェックすることができる。
例
先日投稿したRedux typed actions でReducerを型安全に書く (TypeScriptのバージョン別)で作成したサンプルアプリのRedux Reducerを例に説明する。
このReducerは、Union TypesであるActions.Actions
を、switch case
にてtype
プロパティで判別して処理している。
その際、switch case
にてActions.Actions
のすべてのtype
に対して漏れなく処理していることをコンパイラレベルで検出しよう、というのが今回の話。
// Union Types
export type Actions = IncrementAction | DecrementAction | SetCounterAction;
export interface IncrementAction extends Action {
type: 'INCREMENT';
}
export function increment(): IncrementAction {
return {
type: 'INCREMENT'
};
}
export interface DecrementAction extends Action {
type: 'DECREMENT';
}
export function decrement(): DecrementAction {
return {
type: 'DECREMENT'
};
}
export interface SetCounterAction extends Action {
type: 'SET_COUNT';
payload: {
count: number;
}
}
export function setCount(num: number): SetCounterAction {
return {
type: 'SET_COUNT',
payload: {
count: num
}
};
}
// Reducer
export const appStateReducer = (state: AppState = init(), action: Actions.Actions) => {
switch (action.type) {
case 'INCREMENT':
return Object.assign({}, state, {
count: state.count + 1
});
case 'DECREMENT':
return Object.assign({}, state, {
count: state.count + -1
});
case 'SET_COUNT':
return Object.assign({}, state, {
count: action.payload.count
});
}
return state;
};
never型を使わない場合
case 'SET_COUNT':
の時の処理をまるっと消してみる。もちろん、何もチェックは働かずコンパイラはエラーを検出することはできない。
never型を使った場合
次にnever型を使ってみた場合。使い方は、下記のようにnever
型の変数にaction
変数をアサインする処理を追加する。
// neverでチェックするようにしたReducer
export const appStateReducer = (state: AppState = init(), action: Actions.Actions) => {
switch (action.type) {
case 'INCREMENT':
return Object.assign({}, state, {
count: state.count + 1
});
case 'DECREMENT':
return Object.assign({}, state, {
count: state.count + -1
});
case 'SET_COUNT':
return Object.assign({}, state, {
count: action.payload.count
});
// never型でチェック
default:
const _exhaustiveCheck: never = action;
}
return state;
};
これで先ほどと同じくcase 'SET_COUNT':
の処理を消すと...
このように、never
型にアサイン不可のためコンパイラによりエラーが検出される。
Actions.Actions
のすべてのtype
プロパティに対して処理をしている場合はここは到達不可能となり、action
はnever
型になる。よってnever
型の変数へのアサインではエラーにならなくなるというわけだ。
if文でnever型を使った場合
念のためif文でも動作確認してみたところ、TypeScript 2.0では不足時のチェックは働くが、すべての条件を網羅的に書いた時もコンパイラエラーが出てしまうようで駄目でした。
一方、npm i typescript@next
でインストール可能な開発中のTypeScript 2.1だと上記の誤検知エラーはなくなり大丈夫でした。
まとめ
というわけではやく2.1がリリースされて欲しい...!!
参考
今回のnever
型を使ったチェックはTypeScript Deep Diveで紹介されていました。