2.8TypeScriptには基本型に加え、より複雑な型を表す機能が備わっています。Types」にもとづき、解説とコード例を大きく改めました。Type)は、複数の型をひとつに合成します。ある型を組み合わせて、必要な機能の兼ね備わった型ができるのです。ひとつに合成された型には、もとの型のすべてのメンバーが含まれます。交差型が用いられるのは、mixinなどこれまでのオブジェクト指向の枠に収まらない場合が多いでしょう。つぎのコード001は、mixinをつくる簡単な例です。関数(extend)が引数ふたつのオブジェクトを合成して、交差型の新たなオブジェクトとして返します。複数の型を結ぶのは記号&です。戻り値のオブジェクトは、もとのふたつのオブジェクトが備えるメソッドのどちらも呼び出せます。**はべき乗演算子です。59.99999999999999なお、前掲のコードはtsconfig.jsonのtargetをes6にすると、正しく動きません。クラスに定めたメソッドが、for...in文で取り出せないからです。Type)も複数の型をひとつにまとめます。交差型が複数の型のすべてのメンバーを備えるのに対して、共用型はいずれかひとつの型と一致すれば足ります。つぎのコードは、共用型を使っていない関数の例です。数値を文字列にしたうえで、頭に0を加えて桁揃えします(コードの中身については「数字の頭に0を加えて桁揃えする4つのやり方」の「Array.prototype.join()メソッドで0を埋める」参照)。第1引数の数は文字列も受け取れるよう、anyで型づけしました。第2引数は揃える桁数です。anyは数値(number)と文字列(string)にかぎらずとおってしまうため、それ以外のオブジェクトなどを渡すと意図しない結果が返されてしまいます。Object]typeof演算子などでデータの型を調べ、処理を分ければ目的の結果が得られるでしょう。つぎのコードの抜書きでは、数値と文字列以外を渡すと空文字列''が返されます。数値や文字列についても、適切でない値ははじきました。'';}}さらに、関数の第1引数をnumberとstringの共用型で定めれば、型チェックが働きます。複数の型を結ぶのは記号|です。共用型のデータから参照するメンバーは、すべての型に備わっていなければなりません。ないときは、<>などの構文による型変換で型を限定したうえで参照します。parseInt(<string>number);break;}}共用型で型づけし、typeof演算子によりデータの型を確かめるよう改めた関数がつぎのコード001です。数値(number)と文字列(string)以外を引数に渡すと、コンパイルエラーになります。型ガードの関数を定める前掲コード001のふたつのクラスを例にとりましょう。それぞれに、メソッドがひとつずつ定めてあります。ここで考えるのは、引数に渡したオブジェクトが備えるメソッドを呼び出す関数です。引数はふたつのクラスの共用型とします。this.x);}}このような場合、JavaScriptではメソッドのあるなしを調べてから呼び出す、という手法がよく用いられます。共用型で型づけしたとき気をつけるのは、前述のとおりすぺての型が備えているのでないメソッドを参照する場合は型変換しなければならないことです。Guard)という仕組みは、型を確かめたうえで、そのブロックのスコープ内は判別した型のデータとして扱うものです。そのため、ブロックの中では型変換はせずに済みます。型ガードを定めるのは、つぎの構文にもとづく関数です。ブール値;}前掲の関数は型ガードを用いて書き替えると、つぎのコード003のようになります。型ガードにより、関数本体のifとelseブロック内は型変換が要らなくなりました。typeof演算子による型ガード前掲コード002の関数に型ガードを用いることもできます。その例がつぎのコードです。String(int);}}もっとも、TypeScriptはtypeof演算子による型の評価は、つぎの場合には関数を定めなくても型ガードとみなして扱います。typeof演算子の戻り値を等価(===)もしくは不等価(!===)で比較している。比較する値の文字列が、"number"、"string"、"boolean"、"symbol"いずれかのプリミティブである。したがって、前掲コード002は型ガード関数を使うことなく、つぎのように書き替えられます。ifで等価比較を行わなければなりません。instanceof演算子による型ガードtypeof演算子による評価は、オブジェクトの型ガードには使えませんでした。その場合に用いるのは、instanceof演算子です。オブジェクトのconstructor.prototypeプロパティのチェーンにコンストラクタのObject.prototypeが含まれるかどうかによって、型の一致を確かめます。instanceof演算子による型ガードで前掲コード003を書き直したのが、つぎのコード005てす。型ガード関数は要らなくなります。--strictNullChecksモードTypeScriptには、特別なnull型とundefined型があります。それぞれ、値はnullとundefinedです。デフォルトでは、nullとundefinedはすべての型に代入できます。それを避けるためのフラグは、--strictNullChecksです。true}}--strictNullChecksのモードでは、他の型で宣言された変数にはnullもundefinedも値として納められません。これらの値を受け入れるためには、共用型で型づけにはっきりと含めなければならないのです。このモードはnullとundefinedを異なる扱いとすることにご注意ください。共用型にnull型を加えていても、undefinedは与えられません。そのためには、undefined型も含めなければならないのです。オプションの引数とプロパティ--strictNullChecksモードでは、オプションの引数は自動的にundefined型が加わった共用型になります。コンパイルエラーこれは、クラスのオプションのプロパティについても同じです。型ガードと型変換--strictNullChecksモードで共用型によりnullを含めたとき、値から除くには型ガードのコードを書き加えなければなりません。つぎのようなJavaScriptコードです。value;}}あるいは、論理演算子||を用いて、もっと簡単に書くこともできます(「if文なしに論理演算子で条件判定の処理をする」01「初期化されていない変数にデフォルト値を与える」参照)。'default';}つぎのコードで入れ子の関数は、共用型でnullが含まれてる変数に対してメソッドを呼び出すために型変換しています。!をつぎの構文で用いることにより、識別子の型からnullとundefinedが除けます。postfix('great');}前掲コードでコンパイラは、入れ子の関数に引数として明らかにnullでない値を渡して呼び出しても、型からnullが除けませんでした。入れ子の関数は、いつどこから呼び出されるか、すべてを捉えることができないからです。そのため、関数本体が実行されたときの識別子の型も限定できません。Alias)は、型に新たな名前を与えるものです。インタフェースとも似ています。プリミティブや共用型、タプル型など、どのような型にもエイリアスはつくれます。宣言に用いるキーワードはtypeです。1型エイリアスは、新たな型をつくるのではありません。型を参照する新しい名前ができるだけです。プリミティブの型エイリアスは、さほど有用とはいえません。ドキュメントのかたちで使われることはあるでしょう。インタフェースと同じく、ジェネリック型の型エイリアスもできます。型パラメータを加えて、エイリアス宣言の右辺に用いるだけです。T};定めた型エイリアスのプロパティを、そのエイリアスで型づけすることもできます。Tree<T>;};さらに、交差型とともに用いると、複雑な型もつくれます。つぎの連結リストのコード006は、型エイリアスを交差型で定め、プロパティにそのエイリアスで型づけをしました。すると、そのプロパティのオブジェクトがまた同じ型エイリアスのプロパティをもつ、というように循環することになります。そこで、オブジェクトのクラスはコンストラクタが、オプションの引数にプロパティのオブジェクトを受け取ることにしました。前述06-02のとおり、オプションの引数はundefinedが受け取れます。そこで循環が切れるのです。Carrollなお、つぎのような型エイリアスの宣言は、循環参照が切れないのでエラーになります。Aliases」の項は、型宣言の右辺に型エイリアスは使えないとしています。型エイリアスを右辺に用いること自体は可能です。'Carroll'}];型エイリアスは、インタフェースと似たように使えます。型エイリアスは、継承や実装ができません。他の型を型エイリアスに継承・実装するといった仕組みも備わっていないのです。拡張に対して開かれたソフトウェアをつくるためには、できるだけインタフェースを用いるぺきでしょう。他方で、共用型やタプル型を用いるとき、インタフェースで型を表現するのがむずかしい場合もあります。そのようなときには、型エイリアスを使ってください。Type)は、文字列型のとるべき値を直に定めた型です。共用型と型ガードを組み合わせることで、決まった文字列だけを型の値として扱えます。文字列の値を使って列挙型のような処理ができるのです。つぎのコード007は、文字列リテラル型で4つの矢印キー定めました。クラスのコンストラクタにキーの文字列を渡してつくったインスタンスは、その矢印キーが押されたかどうかを監視します。コンストラクタの引数に文字列リテラル型にない文字列を渡すと、コンパイルエラーになります。文字列リテラル型は、オーバーロードする関数を分けるために使うこともできます。つぎの関数は文字列の引数を文字列リテラル型で定めました。Type)があります。数値型のとるぺき値そのものを、数値で型として与えられるのです。randomInt;}数値リテラル型は、暗黙で働くこともあります。つぎのコードは働かない場合です。関数本体のif条件の式に、論理演算子||が用いられています。右の式は意味がありません。左の式がfalseと評価されたとき、右の式はつねにfalseだからです。{//}}以下のコードでは、うえの例の比較演算子(>)をそれぞれ不等価(!==)と等価(===)に替えました。右の式は意味がありません。それだけでなく、右の式についてコンパイルエラーが起こります(図001)。エラーメッセージは、つぎのとおりです。左の式がfalseと評価されると変数は数値リテラル型1と推論され、2とは比べられないということです。適用することはできません。列挙型(enum)は、メンバーすべてがリテラルで初期値を与えられている場合には、その値は型として扱われます。types」の項をお読みください。Union)という応用的な型の仕組みがつくれます(判別共用体、あるいは代数的データ型、タグ付き共用型などとも呼ばれる考え方です)。関数型プログラミングにとくに役立ちます。TypeScriptは、JavaScriptの構造にもとづいて判別共用型を組み込みました。つぎの3つの要素から成り立ちます。判別用の型からなる共用型の型エイリアス共通のプロパティの型ガードまず定めるのはインタフェースで、それをつぎに共用型にまとめるという手順です。インタフェースには共通のプロパティを加え、判別のための異なる文字列リテラル型が与えられます。このプロパティは判別用のタグとも呼ばれます。インタフェースのその他のプロパティは、それぞれ違って構いません。インタフェースは同じ名前のプロパティがあるほかは、互いに何も関わりはないのです。そこで、これらのインタフェースを共用型にまとめて、型エイリアスを定めます。number;}それでは、インタフェースに共通に与えたプロパティに対して、型ガードの処理を加えましょう。関数の引数は、共用型の型エイリアスで型づけします。switch文で共通のプロパティの型を確かめればよいのです。プロパティの文字列リテラル型に与えられていない文字列と比較しようとすれば、コンパイルエラーになります。前項のコード例に、新たなインタフェースを加え、共用型に含めてみます。number;}すると、関数本体の型ガードで、switch文が判別共用型のすべてのcaseを拾いきれていません。関数の戻り値にundefinedが含まれるということです。--strictNullChecksモードであれば、戻り値に型を与えることによりコンパイルエラーで確かめられます。ない}}--strictNullChecksモードでなくても使えるのがnever型です。取り得るすべての型が除かれた型を表します。条件分岐で取りこぼしがあると、実際にある型が残されてしまいます。すると、never型で受け入れられないために、コンパイルエラーが起こるのです(図002)。never型判別の関数を新たに加えなければならないものの、拾い漏れをよりはっきりと確かめられます。コード全体は、以下のコード008にまとめました。polymorphismと呼ばれます)。階層をつなぐインタフェースが簡単につくれます。つぎのクラスのメソッドは、プロパティの座標計算のあとインスタンスを返します。すると、メソッドをつなげて呼び出すことができるのです。0.9999999999999999ただし、このクラスを継承すると問題が生じます。スーパークラスのメソッドが返すのは親の型のインスタンスなので、サブクラスのメソッドが呼び出せないのです。コンパイルエラーそこで、thisを返すメソッドは、戻り値をthisで型づけします。すると、サブクラスのインスタンスがスーパークラスのメソッドを呼び出しても、自分のクラスの型のインスタンスが得られるのです。以下のコード009は、メソッドの戻り値をthis型に改めました。すると、たとえばつぎのように、親クラスのメソッドから返されたインスタンスに対して、自分のクラスのメソッドが呼び出せます。type)を用いると、プロパティ名を動的に使うコードがコンパイラでチェックできます。そのために用いる演算子を先にご紹介しましょう。インデックス型クエリ演算子keyofです。オペランド(被演算子)の型が備えるpublicのプロパティ名を文字列リテラル型としてまとめた共用型を返します。もとの型のプロパティを取り出して、自動的に文字列リテラルの共用型がつくられるということです。インデックス型クエリ演算子は、ジェネリック型のクラスや関数にも使えます。引数に渡されたオブジェクトが、プロパティを実際に備えているかどうか確かめられるのです。コンパイルエラーつぎに、インデックスアクセス演算子[]です。型からプロパティを参照し、その型が返されます。コンパイルエラーインデックスアクセス演算子[]は、インデックス型クエリ演算子keyofとともにジェネリック型に用いられることで真価が発揮されます。その場合、型変数にextendsキーワードを添えることにより、keyofの型が拡張して加わるようにします。つぎの関数は、オブジェクトからプロパティ名で取り出した値を返すため、戻り値の型はインデックスアクセス演算子[]で定めています。第2引数に渡すプロパティ名によりこの型が変わりますので、それに応じて戻り値のデータ型も異なることになるのです。第1引数のオブジェクトにないプロパティを第2引数に渡せば、コンパイルエラーになります。コンパイルエラーオブジェクトから複数のプロパティを取り出す関数に、インデックスで型づけしたのが以下のコード010です。第2引数には、つぎのようにプロパティ名の配列を渡します。プロパティは、Array.map()メソッドで新たな配列にして返しています。object[n]);}インタフェースにインデックスアクセス演算子[]でプロパティを定めると、任意の名前でプロパティが加えられます(04「余分なプロパティの扱い」)。このとき、インタフェースにkeyof演算子を用いるとプロパティ名(キー)の型が、[]演算子で任意のプロパティ名を渡すとその値の型が得られるのです。つぎのインタフェースから、マップによりプロパティの定めに少し手の加わった新たな型をつくってみましょう。number;}この型はインデックスを用いたマップにより、つぎのようにつくることができるのです。プロパティ名とその型は、マップ先の型にそのまま移されます。そこに、省略可能の定めが新たに加えられたのです。"Alice"}つぎに、プロパティを読み取り専用にする場合です。このときは、プロパティの前に修飾子readonlyを添えます。number;}この型も、インデックスでマップすればつくれます。つぎのようにreadonly修飾子をマップに加えればよいのです。コンパイルエラーこれらふたつの型のマップは便利なので、実はTypeScriptの標準ライブラリにそれぞれキーワードPartialとReadonlyとして備わっています。Readonly<Person>;型をマップするためのもっとも基本的な構文は、for...in文と似ています。つぎの3つの要素で構成され、以下がコードの例です。false};この例では、共用型の文字列リテラルをキーワードinにより、プロパティ名として取り出しました。そのうえで、いずれもbooleanを型に与えたのです。したがって、つぎの型と同じ定めになります。boolean;};もっと実際的なのは、前述のようにすでにある型をもとにすることです。その場合、keyof演算子でプロパティ名を取り出し、[]演算子により型づけするのです。つぎのコードは、--strictNullChecksモードでもプロパティにnullが与えられる型をつくります。コンパイルエラーPartialやReadonlyのようにジェネリック型にすれば、より使い勝手は増すでしょう。null};うえの例では、keyof演算子によりプロパティのリストを取り出し、[]演算子で得た型に手を加えました。これは、使い回しのきく型のマップの仕方です。マップするときに、もとのプロパティと型の構造を崩しません。新たな型はもとのプロパティにもとづいてつくられ、読み取り専用や省略可能の定めもそのまま引き継がれるのです。以下のコード011に定めた関数は、引数に受け取ったオブジェクトのプロパティを別に定めた型でラップして返します。その結果、戻り値の新たなオブジェクトには、つぎのようにプロパティにget()/set()メソッドが備わるのです。ひとつは、Pickです。もとの型からプロパティの一部を取り出して新たな型にします。PartialやReadonlyと同じく、もとの型の構造を崩さない準同型です。7};もうひとつはRecordで、同じ型のプロパティを複数定めます。与えるプロパティ名は文字列リテラルの共用型です。もとになるプロパティをもつ型がないので、準同型の変換ではありません。'two'}なお、PickとRecordの実装を示すと、つぎのとおりです。T;};前掲コード011のオブジェクトをラップして返す関数の変換は準同型です。したがって、もとの型のオブジェクトに戻すことも簡単にできます。その関数が以下のコード012です。つぎのようにして試せます。