Go to list of users who liked
More than 3 years have passed since last update.
TypeScript の構文の中でも、屈指のググラビリティの低さを誇るis
とin
を自分なりにまとめてみました。
"is"
is
は TypeScript の型推論を補強するuser-defined type guard(ユーザー定義型ガード)
で使われます。
unknown 型や、any 型、Union 型の型の絞り込みを行えます。
使用例
例えば、unknown 型を引数に受け取る関数で、もし引数の型が string 型だったら文字列の長さを出力するという処理があるとします。
その場合は、以下のように型を絞り込む必要があります。
constexample=(foo:unknown)=>{if(typeoffoo==="string"){console.log(foo.length);// fooはstringとして推論される}};
この一つの関数ならば良いのですが、他にも string 型への絞り込みが必要な関数があり、typeof foo === "string"
の部分をisString
関数に抽出して汎用的に使いまわせるようにしたいとします。
一見上手くいきそうですが、typeof での型の絞り込みは関数スコープで完結してしまうので、isString
が true な場合でも、まだ foo は unknown 型として推論され型の絞り込みが行えません。
constisString=(test:unknown):boolean=>{returntypeoftest==="string";};constexample=(foo:unknown)=>{if(isString(foo)){console.log(foo.length);// Error fooはまだunknownとして推論される}};
このような時がis
の出番です。is
を使うとisString
の結果が true の場合は引数で受け取った変数の型は、string 型であるとコンパイラに教えることができます。
constisString=(test:unknown):testisstring=>{returntypeoftest==="string";};constexample=(foo:unknown)=>{if(isString(foo)){console.log(foo.length);// fooはstringとして推論される}};
また、is
はオブジェクト型の型の絞り込みにも使えます。
例えば Fish か Bard の Union 型の型をどちらかの型に絞り込みたい時はif((fishOrBard as Bard).fly() !== undefined){}
で判定を行えそうです。
しかし、この判定ではまだ TypeScript のコンパイラはfishOrBard
を Bard 型と推論してはくれません。
typeBard={fly:()=>{/* Do something */};};typeFish={swim:()=>{/* Do something */};};constexample=(fishOrBard:Fish|Bard)=>{if((fishOrBardasBard).fly()!==undefined){console.log(fishOrBard.fly);// Fish | Bard 型として推論され Fishはfly()を持たないので type error}else{console.log(fishOrBard.swim);// Fish | Bard 型として推論され Bardはswim()を持たないので type error}};
そのような時に(fishOrBard as Bard).fly() !== undefined
を関数に切り出し、is
で型を指定するとコンパイラは型を推論できるようになります。
constisBard=(test:Fish|Bard):testisBard=>{return(testasBard).fly()!==undefined;};constexample=(fishOrBard:Fish|Bard)=>{if(isBard(fishOrBard)){console.log(fishOrBard.fly);// Bard型として推論される}else{console.log(fishOrBard.swim);// Fish型として推論される}};
注意点
is
自体への型推論は効かないので注意です。間違った指定をしてしまうとコンパイルは通っても実行時エラーになります。
constisString=(test:unknown):testisstring=>{returntypeoftest==="number";};constexample=(foo:unknown)=>{if(isString(foo)){console.log(foo.length);// 型エラーは出ないが、fooはnumber型なので実行時Errorが発生する}};
(その挙動の解決策としてこちらの記事で便利なライブラリが紹介されていました。)
"in"
in
は コンテキストによって 2 つの意味を持つ構文です。
一つは JS にもあるオブジェクトが特定のプロパティを持つか判定するもの。型の絞り込みに使えます。
もう一つはkeyof
構文と組み合わせて mapped type の定義に使えます。
使用例
型の絞り込み
前述のis
で オブジェクトの型判定に(fishOrBard as Bard).fly() !== undefined)
を使いましたが、こちらはin
で代替することができます
in
を使えば判定処理を関数に切り出しis
で type predicate を記述する必要もありません。
constexample=(fishOrBard:Fish|Bard)=>{// fishOrBardは'fly'というプロパティを持っているかどうかの判定if("fly"infishOrBard){console.log(fishOrBard.fly);// Bard型として推論される}else{console.log(fishOrBard.swim);// Fish型として推論される}};
mapped type での利用
keyof
構文と組み合わせて mapped type で新しい型を定義できます。keyof
プロパティ名の Union 型を取り出しin
で反復処理するイメージですかね。
typeDog={name:string;run:()=>void;};typePartialDog={[PinkeyofDog]?:Dog[P];};// PartialDogは以下の型になる// { name?: string, run?: () => void}
終わりに
以上 TypeScript のis
とin
構文の説明でした。
分かってみると何かと便利に使えそうですね。
自分自信まだまだ勉強中なので誤った記述などあれば、コメントや修正リクエストで教えて頂けると嬉しいです!!
参考
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme