メインコンテンツまでスキップ

特殊な型

TypeScriptにはちょっと特殊な型としてunknown型、never型、any型、void型が存在します。 これらの特徴について説明していきます。

unknown型

これがどういった型であるか集合として定義すると、「全ての値を要素とする集合」です。

少年チックにすると「ひとつなぎの大秘宝(ワンピース)」です。この世の全てがそこにおいてあります。

そのためunknown型にはいかなる型であっても、それはunknown型の部分集合となるので、代入が可能となります。

let a: unknown = 1
a = "string"

しかし逆に言えば、unknown型を除く全ての型について、unknown型は代入不可能です。

const a: unknown = 1
const b: number = a // ERROR!

すなわち、いかなる値としても扱うことができません。

const a: unkown = 1
a + 10 // ERROR!

const b: unkown = [1, 2, 3]
b[0] // ERROR!

この特性を活かし、JSのコードをTSに置き換える際、型がわからない変数などにunknown型を仮で設定することにより、コンパイルエラーをもみ消すことなく置き換えていくことが可能です。しかしこの場合全てのunknownを消し去るまで基本的にコンパイルが通ることがないため、試しに動かすまではかなり長い道のりとなります。

any型

TypeScriptにはちょっと特殊な型としてany型が存在します。

これがどういった型であるかと言うと「なんでも許される」型です。

少年チックにすると「Mr.アンチェイン」です。牢獄の中でタバコを吸ったって怒られないし、牢獄からお出かけすることだってかまいません!

したがって以下のようなプログラムはコンパイルエラーが起こりません。ただし実行時エラーは発生します。

const a: any = undefined
a.hoge

このように、any型はコンパイラによって「なにをしても許される」ため、そのプログラムが正しいかどうかはプログラマが保証する必要があります。

これではせっかくJSの世界からTSの世界にきたと言うのに逆戻りです。

このように、一般的に「Mr.アンチェイン」さんは世界にいっぱいいて欲しくありません。できる限りany型が存在しないコードを書く方が良いです。

ではany型はどんな時に発生するでしょうか?

まずは、型として明示的にanyを指定した時。

const a: any = 1

次に、変数に型を指定せず、なおかつ初期化をしない場合。

let a // a は 暗黙的にany型になる

最後に、関数の引数に型を指定しなかった場合。

function f1(a) {}    // a は 暗黙的にany型になる
const f2 = (a) => {} // アロー関数でも同様

any型の特性を活かし、JSのコードをTSに置き換える際、型がわからない変数などにanyを仮で設定することによりコンパイル・実行することが可能となります。ただしunknownにおいて発生したはずのコンパイルエラーは発生しないため、手段としてはunknownよりも危険です。しかしunknownを使った置き換えのデメリットを克服できるため一長一短といえます。したがってanyを使うかunknown を使うかは適宜判断する必要があります。

never型

これがどういった型であるか集合として定義すると、「空集合」です。

少年チックな言い方は思いつきませんでした。

空集合の性質としては、「任意の集合の部分集合である」があります。したがって、いかなる型にもnever型は代入できます。

逆に言うと、never型以外のいかなる型もnever型には代入ができません。

これはなかなか困った性質です。どうして困るかと言うと、「説明が難しく」なります。なんせnever型の変数を作っても、その中には何も代入することができません。そしてTypeScriptにおいては値の入っていない変数は使用することができません。

const a: never      // OK!
const b: number = b // 型はOK。ただし初期化されていない変数bを使用しているためコンパイルエラー

const c: never = 1 // ERROR! いかなる値もnever型には存在しない

こんなものいつどこで使うんだと言う感じですが、一応使い道はあります。

まず、「実行が終了しない関数」を表現するために使います。例えば無限ループです。

function f(): never {
while(true) {}
}

次に、switch文の網羅チェック。

以下のコードは網羅チェックを使い、コンパイルが通るコード。

let a: 1 | 2 = 1

switch (a) {
case 1:
// aは1型になる
break
case 2:
// aは2型になる
break
default: {
// aは1でも2でもないためnever型になる
const x: never = a // OK!
}
}

以下のコードは網羅チェックを使い、コンパイルが通らないコード。

let a: 1 | 2 | 3 = 1

switch (a) {
case 1:
// aは1型になる
break
case 2:
// aは2型になる
break
default: {
// a は 1でも2でもないため3型になる
const x: never = a // NG!
}
}

このようにちょっと特殊なところで使うのがnever型です。

void型

これがどういった型であるかというと、ちょっと特殊なundefined型です。

これは本当に特殊で、void型は要素にundefinedのみを持ち、undefined型とvoid型は同様に扱えるかのように思えるのですが、そうではありません。void型はundefined型の部分集合として定義されています。そのためvoid型にはundefined型が代入できますが、その逆はできません。

const a: undefined = undefined
const b: void = a // OK!

const c: void = undefined
const d: undefined = c // NG!

void型は本来、「値として扱う」と言うよりも、「値を返さない関数を表現」するために使用します。すなわちvoid型は上記のように変数の型として使うわけではなく、以下のように関数の返り値の型として利用します。

function a(): void {
return
}

返り値がvoid型の関数の記述は大雑把に3つになります。そのうち上二つが一般的であり、最後の一つは「可能ではあるが、意味論的におかしい」方法となります。なぜならvoid型は返り値がないことを表すのに、undefinedを返しているので返り値があると言うことになってしまいます。

function a(): void {
// return文を書かない
}

function b(): void {
// return文は書くが、なんの値も返さない
return
}

function c(): void {
// undefinedをreturnする
return undefined
}

これらは基本的に返り値の型をundefined型にしても可能ですが、その場合には上の3つのうち最後の1つを使うのが一般的でしょう。上の理屈と同じような感じです。