Unknown and Never
unknown型
unknown型は、一度詳しくなると、非常に多くの用途があることに気が付く型の一つです。
unknown型は、any型とよく似た振る舞いをします。
anyは曖昧なままでも使用できますが、 unknownは
型を特定する必要がある点が異なります。
JSONパーサーのWrapperは良い例です。
JSONのデータは、多種多様であるため、JSONパーサーの利用者が
データの形を知っているにもかかわらず、JSONパーサーの作者は、
データがどのような形になっているのかを知ることはできません。
const jsonParser = (jsonString: string) => JSON.parse(jsonString);
const myAccount = jsonParser(`{ "name": "Dorothea" }`);
myAccount.name;
myAccount.email;
// 変数jsonParserにカーソルを合わせると、戻り値の型がany型であることがわかります。
そして、変数myAccountも同様にany型になります。
これについて、ジェネリクスを使って型付けすることもできますが、
unknown型を使うことによっても型付けできます。
const jsonParserUnknown = (jsonString: string): unknown => JSON.parse(jsonString);
const myOtherAccount = jsonParserUnknown(`{ "name": "Samuel" }`);
myOtherAccount.name;
// myOtherAccountの型をTypeScriptに示すまでは、myOtherAccountのオブジェクトを使用する
ことができません。こうすることによって、APIの使用者に、型について前もって
考えさせることができます。
type User = { name: string };
const myUserAccount = jsonParserUnknown(`{ "name": "Samuel" }`) as User;
myUserAccount.name;
// unknown型はとても優れたツールです。より深く理解したい場合はこれらを参照してください。
https://mariusschulz.com/blog/the-unknown-type-in-typescript
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type
never型
TypeScriptは、データフローの解析をサポートしているため、
論理的に絶対に発生しないコードを表現できる必要があります。
例えば、こちらの関数は絶対にreturnされません。
const neverReturns = () => {
// 1行目で例外を投げる場合
throw new Error("必ず例外が投げられるので、戻り値は返りません");
};
// この関数にカーソルを合わせると、型が () => neverとなっていることがわかります。
つまり、この関数は絶対にreturnされません。
これらは、他の値と同様に、代入することは可能です。
const myValue = neverReturns();
// never型を返す関数は、
JavaScriptランタイムの予想できない挙動に対応する場合や、
APIの利用者が型を使わない可能性がある場合に有用です。
const validateUser = (user: User) => {
if (user) {
return user.name !== "NaN";
}
// 型システム的には、このコードは絶対に実行されません。
// つまり、関数neverReturnsの戻り値の型に合致します。
return neverReturns();
};
// この関数の型定義では、引数userが必ず渡されることになっていますが、
JavaScriptには様々な抜け道があるので、
それを保証することはできません。
上記の例のようにnever型を返す関数を使うことで、実行される可能性がない箇所に
コードを追加することができます。
これは、より良いエラーメッセージを表示したい場合や、ファイルや
ループなどのリソースをクローズしたい場合などで有用です。
とてもよくあるnever型の使われ方として、
switch文でのすべてのパスが網羅されていることの担保があります。
以下は、enumと、neverによってすべてのパスが網羅されているswith文の例です。
試しにFlowerに新しいオプションを追加してみてください(例: Tulipなど)
enum Flower {
Rose,
Rhododendron,
Violet,
Daisy,
}
const flowerLatinName = (flower: Flower) => {
switch (flower) {
case Flower.Rose:
return "Rosa rubiginosa";
case Flower.Rhododendron:
return "Rhododendron ferrugineum";
case Flower.Violet:
return "Viola reichenbachiana";
case Flower.Daisy:
return "Bellis perennis";
default:
const _exhaustiveCheck: never = flower;
return _exhaustiveCheck;
}
};
// 新しく追加したオプションの型は、never型に変換できない
というエラーが表示されるはずです。
共用体でのnever型
共用体では、never型は
自動的に取り除かれます。
type NeverIsRemoved = string | never | number;
// NeverIsRemovedの型を見てみると、
string | number になっています。
これは、never型の代入は実行時には絶対に起こらないためです。
この特徴は、Conditinal Typesで良く使われています。 example:conditional-types