TypeScript 2.2〜2.5までに追加された新機能:特集:TypeScript 2.0概説(1/2 ページ)
着々と進化を続けるTypeScript。バージョン2.2〜2.5までに、この言語がどのような進化を遂げたのかをざっくりと見てみよう。
TypeScriptの言語としての進化はすさまじいものがある。本フォーラムでもTypeScript 2.1については(全てではないものの)その機能を概観していたが、その後、新しい機能がずいぶんと増えている。そこで、本稿ではTypeScript 2.2〜2.5までに追加された新機能を幾つか見ていくことにしよう。
TypeScript 2.5までに追加された新機能
TypeScriptの「What's New」を基に、TypeScript 2.2〜2.5までの間に追加された機能を以下に幾つか列挙しておこう。もちろん、これらが全てではない。詳細については前掲のリンクからバージョンごとのリリースノートを参照されたい
新機能 | 説明 | バージョン |
---|---|---|
Mix-inクラス | Mix-inパターンを利用したクラスのMix-inをサポート | 2.2 |
object型 | プリミティブ型以外の型を表すobject型を導入 | 2.2 |
new.target | 関数やコンストラクタがnew演算子を介して呼び出されたかを判定 | 2.2 |
非同期イテレーション | 非同期のジェネレータ/イテレータがサポートされた | 2.3 |
型パラメーターのデフォルト値 | ジェネリクスを使用する際に型パラメーターのデフォルト値(デフォルトの型)を指定できるように | 2.3 |
非同期import | プログラムの任意の地点でモジュールのインポートが可能に | 2.4 |
文字列のenum | 列挙型のメンバの値に文字列を使用できるようになった | 2.4 |
弱い型定義 | 全てのプロパティがオプションの型を定義できるように | 2.4 |
catch句での変数の省略 | 例外処理時にcatch句で変数を記述しなくてもよくなった | 2.5 |
TypeScript 2.2〜2.5で追加された新機能(一部) |
本稿ではこれらの項目のうち、幾つかについてコード例を挙げて、それらがどんなものかを見ていこう。
Mix-inクラス
Mix-inとは「何らかの機能を実装したコードを、複数のクラスで共有(継承)する」ための仕組みといえる(これに対して、インタフェースは基本的には「どのようなシグネチャを持ったメソッド=インタフェースを持つか、その仕様を複数のクラスが共有(継承)する」ための機構といえる)。Mix-in自体についての詳細な説明はWikipediaの「Mixin」ページやまつもとゆきひろ氏による説明などを参考にされたい。また、ECMAScriptでMix-inを使う方法の概論としてはMDNの「Classes」で述べられている。
上述のMDNのドキュメントを参考に、ECMAScriptでMix-inを行うコードを記述すると次のようなパターンになる。
class Foo {
constructor(msg) { this.msg = msg; }
}
class Bar {
constructor(msg) { this.msg = msg; }
}
function CreateClassWithLogger(Base) {
return class extends Base {
log() { console.log(`hello from logger: ${this.msg}`); }
}
}
const FooWithLogger = CreateClassWithLogger(Foo);
const BarWithLogger = CreateClassWithLogger(Bar);
var fwl = new FooWithLogger("foo with logger");
var bwl = new BarWithLogger("bar with logger");
fwl.log();
bwl.log();
これは「log」という名前のメソッド(の実装)をFooWithLogger/BarWithLoggerという2つのクラスで共有(継承)するコードだ。そのためにCreateClassWithLoggerヘルパー関数を作成し、これに基底クラスとなるFoo/Barクラスを渡すことで、logメソッドを持つクラスが返送されるようになっている。
TypeScriptでこれと同等なことを行っているのが以下のコードだ。
class Foo {
constructor(public msg: string) { }
}
class Bar {
constructor(public msg: string) { }
}
type Constructor<T> = new (...args: any[]) => T;
function CreateClassWithLogger<T extends Constructor<object>>(Base: T) {
return class extends Base {
log() {
console.log(`hello from logger: ${this.msg}`);
};
constructor(...args: any[]) {
super(...args);
}
}
}
const FooWithLogger = CreateClassWithLogger(Foo);
const BarWithLogger = CreateClassWithLogger(Bar);
var foowithlogger = new FooWithLogger("foo with logging");
var barwithlogger = new BarWithLogger("bar with logging");
foowithlogger.log();
barwithlogger.log();
ECMAScriptよりも複雑なコードになっているが、これはTypeScriptが静的型付けであることと、ジェネリクスを利用してMix-inパターンを実現しているためだ。
コード中の「type Constructor<T> = new (...args: any[]) => T;」というのは「any型の可変長引数を1つだけ受け取り(...args: any[])、T型オブジェクトを返送するコンストラクタ」を意味する型である。2つのクラスFooとBarはまさにこれに相当する(文字列を受け取って、Foo型/Bar型のオブジェクトを返送する)コンストラクタを持っている。このように「ある型Xについて、(...args: any[]) => X」となるような型をTypeScriptでは「Mix-inコンストラクタ型」としている。
そして、logメソッドを持つクラスを返送するヘルパー関数であるCreateClassWithLogger関数では、このコンストラクタ型(その型パラメーター「X」を「object」としているが、これはTypeScriptにおける非プリミティブ型を表す型である)を制約とする型パラメーターTを持ち、T型を引数に受け取り、「そのT型を継承する新たなクラス」を返送するようになっている。また、コンストラクタでは「any型の可変長引数を1つだけ受け取り、それをsuper(...args)」のようにして渡している。以上をまとめると、次の2点はTypeScriptでMix-inを行う上でのお作法となる。
- 返送するクラスが継承するクラスはMix-inコンストラクタ型である
- 返送するクラスのコンストラクタは「any型の可変長引数を1つだけ受け取り」、それをsuperメソッド呼び出しに「super(...args)」のように展開して渡す
この作法にのっとったコードを書けば、後はヘルパー関数に何らかのクラスを渡すことで、特定の機能を実装したコードがMix-inされたクラスを手に入れられるようになる。なお、上のコードではmsgプロパティがあることを決め打ちで使っているが、これはあまりよくないことだ(トランスパイル時にエラーとなるが、これはサンプルなのでご容赦願いたい)。
object型
上のコード例(Constructor<object>)にもあったが、object型とは「非プリミティブ型」を表す型でTypeScript 2.2で導入された。つまり、number/string/booleanなどのプリミティブ型以外のオブジェクトの型を表す総称的な型といえる。例えば、Object.createメソッドにはJavaScriptオブジェクトを渡すことはできるが、プリミティブ型の値は渡せない。
Object.create("string"); // 実行時に例外を発生する
例えば、上のコードをnodeコマンドで実行すると、例外が発生する。
> node objectjs.js
C:\project\ts20\ts03\objectjs.js:1
(function (exports, require, module, __filename, __dirname) { Object.create("string");
^
TypeError: Object prototype may only be an Object or null: string
at Object.<anonymous> (C:\project\ts20\ts03\objectjs.js:1:70)
…… 省略 ……
Object.createメソッドの第1引数にはオブジェクトかnullしか渡せないために、実行時にTypeErrorが発生している。object型を使うと、このようなエラーをTypeScriptのトランスパイル時に検出できるようになる。以下はobject型の値をパラメーターに受け取り、それを使ってObject.createメソッドを呼び出すコード例だ。
// パラメーターの型はobject型なのでプリミティブ型は受け付けない
function createObject(obj: object) {
return Object.create(obj);
}
var obj1 = createObject(42);
var obj2 = createObject({ value: 42 });
例えば、上のコードをトランスパイルすると、次のようにエラーが報告される。
> tsc objecttype.ts
objecttype.ts(5,25): error TS2345: Argument of type '42' is not assignable
to parameter of type 'object'.
このようにobject型を使用することで、プリミティブ型かオブジェクトかが重要になる部分で、意図しないエラーが発生することを防ぐことができる。
Copyright© Digital Advantage Corp. All Rights Reserved.