TypeScript 2.0で追加されたnever型/読み込み専用プロパティなどの機能に加えて、TypeScript 2.1の新機能についても幾つか見てみよう。
前回から時間が空いてしまったが、その間にTypeScript 2.1がリリースされ、2.2のリリースも間近に迫ってきた。そこで今回は、「あり得ない」を表現するnever型や読み込み専用プロパティなど、TypeScript 2.0の新機能でまだ紹介できていなかったものを幾つか取り上げていく。また、最後にTypeScript 2.1で改善された型推測機能など、TypeScript 2.1/2.2の新機能を少しだけ取り上げる(詳しくは回をあらためて紹介する予定だ)。
never型は「それが発生することがない値であること」を示すために使用する。簡単には、「制御を返さずに終了する関数の戻り値型」や、前回に紹介した型ガードの中でたどり着くことがない変数の型などがnever型である。
TypeScript 2.0のリリースノートによると、never型には次のような特徴がある。
関数末尾が到達不可能かどうかは、制御フローの解析によって判断される。なお、上の特徴の3つ目については注意が必要だ。型注釈がないときに戻り値型がneverであると推測されるのは、関数式かアロー関数を利用して関数を定義した場合となる。「function funcname() { …… }」形式の関数定義では明示的に型注釈を行う必要がある。
以下に簡単な例を示す。
// 戻り値型がneverになる
const hello = (name) => {
console.log('hello ' + name);
throw Error('error');
console.log('never executed');
}
// 戻り値型がvoidになる
const goodbye = (name) => {
console.log('goodbye ' + name);
}
// 戻り値型がvoidになる
function hello2(name) {
console.log('hello ' + name);
throw Error('error');
console.log('never executed');
}
アロー関数を用いて定義している関数helloは、2行目で例外を送出しているので、3行目のconsole.logメソッド呼び出しは到達不可能である。また、そこまでにreturn文で制御を呼び出し元に返してもいない。そのため、これは制御を返さずに終了する関数であり、その戻り値型がneverであると推測される。これに対して、関数goodbyeは戻り値を返さないが、制御を呼び出し側に戻すので、その戻り値型はvoidである。関数hello2の定義では関数式/アロー関数を使っておらず、かつ、型注釈を付加していないので、この関数の戻り値型はneverではなく、voidと推測される。以下に例を示す(2つのポップアップが表示している戻り値型の違いに注意)。
関数の戻り値型だけがnever型として型注釈を付加する対象ではない。例えば、次のようなコードがあったとする。
const foo = (x: string | number) => {
if (typeof x === 'string') {
return 'hello ' + x;
} else if (typeof x === 'number') {
return x * 2;
}
x;
}
この関数は制御フロー解析ベースの型解析により、if〜else if節が終了した時点で「xの型は共用型のstringでもnumberでもないことを表すnever」となる。これをチェックするためにif〜else if節の次に「x;」行を追加してある。Visual Studio Codeで、この行にカーソルを合わせると、次のようになることからxの型が確かにneverとなっていることが分かるはずだ(画像中央のポップアップに注目)。
never型の使い道の1つとしては、共用型(Union Types)の値を処理する際に、それぞれの型に応じた処理の記述漏れをコンパイル時点で(あるいはIDEのレベルで)検出できることが挙げられる。以下に例を示す。
const error = (msg: never) =>
{ throw new Error(`error: not processed ${typeof msg}`); }
const foo = (x: string | number) => {
if (typeof x === 'string') {
return 'hello ' + x;
} else if (typeof x === 'number') {
return x * 2;
}
return error(x);
}
console.log(foo(100));
最初に定義している関数errorは、例外を送出し、ある型の処理が行われていないことを示すものだ。例外を発生するので、この関数は制御を返すことがなく、その戻り値型は(アロー関数で定義しているので)neverとなる。また、パラメーターの型もneverになっていることに注意しよう。
一方、関数fooは、先ほど見た文字列か数値の値をパラメーターに受け取り、その型に応じた処理を行う関数でif〜else if節の後に「既に2つの型を処理し終わったので、ここに到達することがないことを意味するerror呼び出し」を追加したものだ。
ここで、else if節を取り除くとどうなるだろう。つまり、文字列だけを処理し、数値の処理を忘れていたとしたらどうなるだろう。
const foo = (x: string | number) => {
if (typeof x === 'string') {
return 'hello ' + x;
}
return error(x);
}
Visual Studio Codeでは次のようになる。赤線が引かれたエラー部分とその上のポップアップに注目されたい。
「number型の値をnever型のパラメーターに代入できない」というエラーメッセージが表示されている。最初に述べたように、never型は全ての型に代入可能だが、never型にはnever型以外の型の値を代入できない。この特性を利用して、処理の記述漏れを(ここでは、あり得ないことを示すnever型の値にnumber型の値を代入しようとしたことで)事前に検出できるようになる。一方、関数fooでは「return error(x)」としているが、関数errorの戻り値型はneverだった。これも前述したように、neverは全ての型のサブタイプで、関数fooの戻り値型である「string | number」に代入可能であるためにエラーとはなっていないということだ。
ここまで見たように「あり得ない事態をコードとして表現できるようになる」ことと「そのことを利用した処理の記述漏れを検出できる」のがnever型の大きな特徴の1つといえる。
Copyright© Digital Advantage Corp. All Rights Reserved.