8️⃣

[TypeScript UtilityTypes] ThisType

2024/01/18に公開

TypeScript入門メモ
[Utility Types] ThisType について

ThisType<Type>

公式ドキュメント
https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypetype

このユーティリティは変換された型を返さない。代わりに、文脈上の this 型のマーカーとして機能します。このユーティリティを使用するには noImplicitThis フラグが有効になっていなければならないことに注意してください。

type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};
 
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return { ...data, ...methods } as D & M;
}
 
let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx; // Strongly typed this
      this.y += dy; // Strongly typed this
    },
  },
});
 
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

上記の例では、makeObjectの引数にあるmethodsオブジェクトは、ThisType<D & M>を含む文脈的な型を持っています。
そのため、methodsオブジェクト内のthisの型は、{ x: number, y: number } & { moveBy(dx: number, dy: number): void }になります。
methodsプロパティの型が、同時に推論の対象であり、methods内のthisの型の源泉となっていることに注目してください。
ThisType<T>マーカーインターフェースは、単にlib.d.tsで宣言された空のインターフェースです。
オブジェクトリテラルの文脈的な型で認識されることを除いて、このインターフェースは他の空のインターフェースと同様に機能します。

使い所

ミックスインや高度なオブジェクト合成:
オブジェクトが動的に構成される場合、TypeScriptがthisの型を自動的に推論するのが難しくなることがある。
このような場合にThisType<T>を使用すると、合成されたオブジェクトのメソッド内でthisが期待する型を持つことを保証できる。

複雑な型構造を持つオブジェクト:
オブジェクトが複数の型から成る複雑な構造を持つ場合、ThisType<T>を使用して、メソッド内のthisに期待される正確な型を指定できる。

type Movable = {
  x: number;
  y: number;
  moveBy(dx: number, dy: number): void;
};

type Drawable = {
  draw(): void;
};

// MovableとDrawableの型をミックスインするためにThisTypeを使用
type MovableDrawable = ThisType<Movable & Drawable> & Movable & Drawable;

function createMovableDrawable(): MovableDrawable {
  return {
    x: 0,
    y: 0,
    moveBy(dx, dy) {
      this.x += dx;
      this.y += dy;
    },
    draw() {
      console.log(`Drawing at (${this.x}, ${this.y})`);
    }
  };
}

const myObject = createMovableDrawable();
myObject.moveBy(3, 3);
myObject.draw(); // Drawing at (3, 3)

myObject.moveBy(7, 7);
myObject.draw(); // Drawing at (10, 10)

MovableとDrawableという二つの型を持つオブジェクトを作成しています。ThisType<Movable & Drawable>を使用することで、createMovableDrawable関数が返すオブジェクトのメソッド内でthisがMovable & Drawable型として扱われるようにしています。これにより、moveByメソッドとdrawメソッドの両方でthisを適切に使用できるようになる。

が、別にThisTypeを使わなくても表現できるので正直使い所がわからない。

Discussion