【TypeScript】型ガード(type guards)とは
更新日:2024/02/27
TypeScriptの型類推の仕組みを理解するには、型ガードについて知っておく必要があります。
そこで今回は、型ガードについてお伝えします。
型ガードとは
型ガードは、複数の型を許容する変数やプロパティに対して、TypeScriptが型を絞り込む指標です。
ただし型コンテキストなどのTypeScriptで導入された構文ではありません。
JavaScriptに元からあるtypeof演算子またはinstanceof演算子を使った型判定処理であり、TypeScript側で便宜的に「型ガード」という名前を付けたものです。
typeof型ガード
TypeScriptはif文等の条件判定に typeof演算子が使用されていると、その結果から変数の型を類推します。
これにより、正確な型チェックを行うことができます。
型不一致によるエラー
次のコードは、トランスパイル時にエラーが出力されます。
const func = (param:string|number):string=>{
return param.toUpperCase(); // エラー:Property 'toUpperCase' does not exist on type 'string | number'.
}
console.log( func( "hello" ) );
console.log( func( 100 ) );
※エラー内容:プロパティ 'toUpperCase' はタイプ 'string | number' に存在しません。
引数paramは string型または number型です。
toUpperCaseメソッドは number型のときに使用できません。
実際にトランスパイルで生成されたコードを実行すると、エラーで停止します。
console.log( func( "hello" ) ); // "HELLO"
console.log( func( 100 ) ); // TypeError:param.toUpperCase is not a function
※エラー内容:param.toUpperCase はfunctionではない
このような実行時エラーを回避できるのは、TypeScriptの良い点ですね。
typeof演算子によるエラー回避
次に、typeof演算子で型判定を行います。
これで実行時エラーが発生するのを防ぐことができます。
const func = (param:string|number):string=>{
if( typeof param === "string" ){
// paramはstring型
return param.toUpperCase();
}else{
// paramはnumber型
return param.toString();
}
}
const func = (param:string|number):string=>
typeof param === "string" ? param.toUpperCase() : param.toString();
どちらにすべきだという話ではないですが、覚えておくといいですね。
TypeScript側の型ガード認識
一方、TypeScript側もtypeof演算子での判定を行ったことで、実行時エラーが回避されたことを認識する必要があります。
つまりTypeScriptがtypeof param === "string"を型ガードとして認識して、{ }内の 引数paramに string型を型付けします。
また else側の { }内は、string型以外の型で型付けされます。
今回はstring|numberなのでnumber型ですね。
型ガード内のreturnも把握している
なお、TypeScriptは型ガード内の returnも把握しています。
次のように elseを使用しなくても型ガード以降の型を適切に選択してくれます。
const func = (param:string|number):string=>{
if( typeof param === "string" ){
// paramはstring型
return param.toUpperCase();
}
// paramはnumber型
return param.toString();
}
3つ以上の型の型ガード判定
変数が3つ以上の型になる時は、各型を判定する型ガードを適用していきます。
const func = (param:string|number|boolean):string=>{
if( typeof param === "string" ){
// paramはstring型
return param.toUpperCase();
}
if(typeof param === "number"){
// paramはnumber型
return param.toString();
}
// paramはboolean型
return param.toString().toUpperCase();
}
if文に2つ以上の型ガード判定
条件判定に複数の型ガードを使用できます。
const func = (param:string|number|boolean):string=>{
if( typeof param === "string" || typeof param === "number"){
// paramは、stringまたはnumber型
if(typeof param === "number"){
// paramはnumber型
return param.toString();
}
// paramはstring型
return param.toUpperCase();
}
// paramはboolean型
return param.toString().toUpperCase();
}
switch文での型ガード判定
switch文の条件にtypeof演算子を使用すると、型ガードとして機能します。
const func = (param:string|number|boolean):string=>{
switch(typeof param){
case "string":
return param.toUpperCase();
case "number":
return param.toString();
default:
return param.toString().toUpperCase();
}
}
instanceof型ガード
instanceof演算子も型ガードとして機能します。
JavaScriptのinstanceof演算子については、次の記事で紹介しているので参考にしてください。
次のコードは所持するプロパティが異なるオブジェクトを一つの引数で受け取り、適切な文字列を返す関数です。
class Person1{
name:string;
constructor(name: string) {
this.name = name
}
}
class Person2{
name:string;
familyName:string;
constructor(name: string,familyName:string) {
this.name = name;
this.familyName = familyName;
}
}
const func = (param:Person1| Person2):string=>{
if( param instanceof Person1 ){
return param.name;
}else{
return param.name + " " + param.familyName;
}
}
console.log( func( new Person1("taro") ) );
console.log( func( new Person2("hanako","yamada") ) );
これは、わかりやすいですね。
次のコードは、Arrayオブジェクト(配列)またはArrayLikeオブジェクトを引数として受け取る関数を受け取り、要素を連結して返す関数を定義しています。
const func = (param:Array<string>| ArrayLike<string>):string=>{
if( param instanceof Array ){ // ①
return param.join("|");
}else{
return Array.prototype.join.call(param,"|");
}
}
console.log( func( ["a","b","c"] ) );
console.log( func( {0:"a",1:"b",2:"c",length:3} ) );
ArrayLikeオブジェクトはjoin()メソッドを所持していないため、Array.prototype.join.call()を呼び出しています。
ここで非常に分かりにくいのが、コードの①の部分のparam instanceof Array を param instanceof ArrayLikeにすることができない点です。
次のように変更すると、エラーが出力されます。
const func = (param:Array<string>| ArrayLike<string>):string=>{
if( param instanceof ArrayLike ){ // 'ArrayLike' only refers to a type, but is being used as a value here.
return Array.prototype.join.call(param,"|");
}else{
return param.join("|");
}
}
※エラー内容: 「ArrayLike」は型のみを参照しますが、ここでは値として使用されています。
param instanceof ArrayLikeはJavaScriptで処理されるコードです。
そのため、実体を持つ値を指定する必要があります。
しかしArrayLikeはTypeScriptで定義されている型、つまり実体ではないためエラーになります。
一方のArrayはTypeScriptで定義されている型ですが、Arrayという名前の組み込オブジェクトが存在しています。
つまり、たまたま型名と同じオブジェクトが存在していたので、上手く動作したのです。
ちなみにJavaSciptのクラスは、実体を持ったオブジェクトです。
そのため最初の例のクラスは型でもありオブジェクトでもあります。
従って、エラーになりません。
any型と型ガード
型の絞り込みをおこなってくれて実行時エラーを回避してくれる型ガードですが、any型には無抵抗でお手上げです。
次のコードをトランスパイルすると、エラー出力されません。
const func = (param:any):string=>{
if( typeof param === "string" ){
return param.toUpperCase();
}else{
return param.toUpperCase();
}
}
func( 100 );
しかし生成されたコードを実行すると、エラーで停止します。
func( 100 ); // TypeError: param.toUpperCase is not a function
else{ } 内の 引数paramは、any型で型付けされます。
any型は型チェックを放棄しているので、toUpperCase()の呼び出しについて問題がないか等の判定が行われません。
そのため、エラー出力されないのです。
そして生成されたコードを実行すると、number型に対してtoUpperCase()を呼び出します。
しかし、number型にtoUpperCase()が存在しないため、実行時エラーになります。
any型の変数を型ガードで使用する場合は、実行時エラーが発生する確率が上がります。
十分に注意する必要がありますね。
ユーザー定義型ガード
TypeScriptは様々な型を定義できますが、typeof演算子またはinstanceof演算子で判定できないものが多いです。
例えば、次のように二つのオブジェクト型を判定しようとすると、エラーになります。
type Obj1 = {value1:number,text1:string};
type Obj2 = {value2:number,text2:string};
const func = (obj:Obj1|Obj2):string=>{
if( obj instanceof Obj1 ){ // 'Obj1' only refers to a type, but is being used as a value here.
return obj.text1;
}else{
return obj.text2;
}
}
エラー内容: 「Obj1」は型のみを参照しますが、ここでは値として使用されています。
Obj1が定義されている行は、トランスパイル時に削除されます。
その結果、トランスパイルで生成されたコードを実行すると、 if( obj instanceof Obj1 )でエラーになります。
上記のエラーは、この結果をTypeScriptが予測して出力したものです。
このような実行時のエラーを回避しながら型ガードを適用するために、ユーザー定義の型ガードが導入されています。
ユーザー定義の型ガードは、戻り値に「変数 is 型」という特殊な記述をした関数です。
const isObj1 = (obj:Obj1|Obj2):obj is Obj1 =>"value1" in obj;
const func = (obj:Obj1|Obj2):string=>{
if( isObj1(obj) ){
return obj.text1;
}else{
return obj.text2;
}
}
詳しくは、次のページを読んでみてください。
更新日:2024/02/27
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。