32
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TypeScript 2.0のneverでTagged union typesの絞込を漏れ無くチェックする

Posted at

はじめに

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':の時の処理をまるっと消してみる。もちろん、何もチェックは働かずコンパイラはエラーを検出することはできない。

no-never.png

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':の処理を消すと...

use-never.png

このように、never型にアサイン不可のためコンパイラによりエラーが検出される。
Actions.Actionsのすべてのtypeプロパティに対して処理をしている場合はここは到達不可能となり、actionnever型になる。よってnever型の変数へのアサインではエラーにならなくなるというわけだ。

if文でnever型を使った場合

念のためif文でも動作確認してみたところ、TypeScript 2.0では不足時のチェックは働くが、すべての条件を網羅的に書いた時もコンパイラエラーが出てしまうようで駄目でした。

if-never.png

一方、npm i typescript@nextでインストール可能な開発中のTypeScript 2.1だと上記の誤検知エラーはなくなり大丈夫でした。

まとめ

というわけではやく2.1がリリースされて欲しい...!!

参考

今回のnever型を使ったチェックはTypeScript Deep Dive紹介されていました

32
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?