typeof
Class<T>
Class<T>
は抽象クラスに一致しませんこの章では、値としてのクラスについて探ります。
次のクラスを考えてみましょう。
class Point {: number;
x: number;
yconstructor(x: number, y: number) {
.x = x;
this.y = y;
this
} }
この関数はクラスを受け取り、そのインスタンスを作成します。
function createPoint(PointClass: ???, x: number, y: number) {
new PointClass(x, y);
return }
Point
またはそのサブクラスにする場合、パラメータPointClass
にはどのような型を使用すべきでしょうか?
typeof
§7.7「2つの言語レベル:動的 vs. 静的」では、TypeScriptの2つの言語レベルについて調べました。
クラスPoint
は2つのものを生成します。
Point
Point
のインスタンスのためのインターフェースPoint
Point
の記述場所によって、意味が異なります。そのため、PointClass
に型Point
を使用することはできません。これはクラスPoint
自体ではなく、クラスPoint
のインスタンスに一致します。
代わりに、型演算子typeof
(JavaScriptにも存在するTypeScript構文のもう一つの要素)を使用する必要があります。typeof v
は、動的(!)値v
の型を表します。
function createPoint(PointClass: typeof Point, x: number, y: number) { // (A)
new PointClass(x, y);
return
}
// %inferred-type: Point
= createPoint(Point, 3, 6);
const point .ok(point instanceof Point); assert
コンストラクタ型リテラルは、接頭辞new
が付いた関数型リテラルです(A行)。この接頭辞は、PointClass
がnew
を介して呼び出されなければならない関数であることを示しています。
function createPoint(
: new (x: number, y: number) => Point, // (A)
PointClass: number, y: number
x
) {new PointClass(x, y);
return }
インターフェースとオブジェクトリテラル型(OLT)のメンバーには、メソッドシグネチャとコールシグネチャが含まれることを思い出してください。コールシグネチャにより、インターフェースとOLTは関数を記述できます。
同様に、コンストラクタシグネチャにより、インターフェースとOLTはコンストラクタ関数を記述できます。これは、接頭辞new
が追加されたコールシグネチャのように見えます。次の例では、PointClass
はコンストラクタシグネチャを持つオブジェクトリテラル型を持っています。
function createPoint(
: {new (x: number, y: number): Point},
PointClass: number, y: number
x
) {new PointClass(x, y);
return }
Class<T>
得られた知識を用いて、型パラメータT
を導入することにより、値としてのクラスのためのジェネリック型を作成できます。
type Class<T> = new (...args: any[]) => T;
型エイリアスの代わりに、インターフェースを使用することもできます。
<T> {
interface Classnew(...args: any[]): T;
}
Class<T>
は、そのインスタンスが型T
と一致するクラスのための型です。
Class<T>
を使用すると、createPoint()
のジェネリックバージョンを書くことができます。
function createInstance<T>(AnyClass: Class<T>, ...args: any[]): T {
new AnyClass(...args);
return }
createInstance()
は次のように使用されます。
class Person {constructor(public name: string) {}
}
// %inferred-type: Person
= createInstance(Person, 'Jane'); const jane
createInstance()
は、関数によって実装されたnew
演算子です。
Class<T>
を使用してキャストを実装できます。
function cast<T>(AnyClass: Class<T>, obj: any): T {
if (! (obj instanceof AnyClass)) {
new Error(`Not an instance of ${AnyClass.name}: ${obj}`)
throw
};
return obj }
cast()
を使用すると、値の型をより具体的なものに変更できます。これは、静的に型を変更し、動的なチェックを実行するため、ランタイムでも安全です。次のコードは例を示しています。
function parseObject(jsonObjectStr: string): Object {
// %inferred-type: any
= JSON.parse(jsonObjectStr);
const parsed cast(Object, parsed);
return }
Class<T>
とcast()
の1つのユースケースは、型安全なマップです。
class TypeSafeMap {= new Map<any, any>();
#data get<T>(key: Class<T>) {
= this.#data.get(key);
const value cast(key, value);
return
}set<T>(key: Class<T>, value: T): this {
cast(key, value); // runtime check
.#data.set(key, value);
this;
return this
}has(key: any) {
.#data.has(key);
return this
} }
TypeSafeMap
の各エントリのキーはクラスです。そのクラスはエントリの値の静的型を決定し、ランタイムでのチェックにも使用されます。
これは動作中のTypeSafeMap
です。
= new TypeSafeMap();
const map
.set(RegExp, /abc/);
map
// %inferred-type: RegExp
= map.get(RegExp);
const re
// Static and dynamic error!
.throws(
assert// @ts-expect-error: Argument of type '"abc"' is not assignable
// to parameter of type 'Date'.
=> map.set(Date, 'abc')); ()
Class<T>
は抽象クラスに一致しませんClass<T>
が期待される場合、抽象クラスを使用することはできません。
abstract class Shape {
}
class Circle extends Shape {// ···
}
// @ts-expect-error: Type 'typeof Shape' is not assignable to type
// 'Class<Shape>'.
// Cannot assign an abstract constructor type to a non-abstract
// constructor type. (2322)
: Array<Class<Shape>> = [Circle, Shape]; const shapeClasses1
なぜでしょうか?その理由は、コンストラクタ型リテラルとコンストラクタシグネチャは、実際にnew
で呼び出すことができる値に対してのみ使用するべきであるためです(詳細情報を含むGitHub issue)。
これは回避策です。
type Class2<T> = Function & {prototype: T};
: Array<Class2<Shape>> = [Circle, Shape]; const shapeClasses2
このアプローチの欠点
instanceof
チェック(右辺オペランドとして)に使用できません。