LoginSignup
2
0

More than 3 years have passed since last update.

【TypeScript】 this のトリセツ(3. 多様性の this の型)

Last updated at Posted at 2021-01-13

はじめに

JavaScript でおなじみの this ですが、ご存知の通り、様々な落とし穴があります。
(通常のの function と arrow function で挙動が違う、呼び出し元次第で値が変わる、strict モードか否かで挙動が違う、等々)

TypeScript では、this におけるこれらの落とし穴を避けるための 以下の仕組みがあります。

  1. this パラメーター
  2. This に関する Utility Type
  3. 多様性の this の型(Polymorphic this types)

それぞれの仕様をまとめてみました。

この記事では 多様性の this の型(Polymorphic this types)について説明します。

環境

TypeScript: v4.1.3

コード

例 1: Playground Link

例 2: Playground Link

多様性の this の型(Polymorphic this types)

TypeScript では、this を型として扱うことができます。
これはインターフェースやクラスの関数の返り値に使用されます。
返り値に this を指定すると、関数の呼び出し元と同様の型を返すことができます。

例 1 (Set)

JS の組み込みオブジェクトのSetを例に、説明します。

以下は Set を簡略化したものになります。

class Set {
  add(value: number): this {}
}
new Set().add(1); // Set

add の返り値を this にしているため、add メソッドの返り値は Set になります。
次に、この Set を拡張したクラスを作成し、delete メソッドを用意します。

class MutableSet extends Set {
  delete(value: number) {}
}
new MutableSet().add(1); // MutableSet

// No Error
new MutableSet().add(1).delete(1);

このとき、new MutableSet().add(1)の返り値の型は Set ではなく、呼び出し元の MutableSet となります。
もし返り値に this ではなく Set を指定した場合、サブクラス(MutableSet)側でスーパークラス(Set)側の呼び出しが困難になります。

class Set {
  add(value: number): Set {} // this -> Set
}
new Set().add(1); // Set

class MutableSet extends Set {
  delete(value: number) {}
}

new MutableSet().add(1); // Set !

// Property 'delete' does not exist on type 'Set'.ts(2339)
new MutableSet().add(1).delete(1);
class MutableSet extends Set {
  delete(value: number) {}
  add(value: number): MutableSet {} // 毎回オーバーライドするひつようが出てくる
}
new MutableSet().add(1); // MutableSet

参考: プログラミング TypeScript ――スケールする JavaScript アプリケーション開発

おまけ: 実際の Set の型定義は以下のようになります。

interface Set<T> {
  add(value: T): this;
  clear(): void;
  delete(value: T): boolean;
  forEach(
    callbackfn: (value: T, value2: T, set: Set<T>) => void,
    thisArg?: any
  ): void;
  has(value: T): boolean;
  readonly size: number;
}

参照: lib.es2015.collection

例 2 (Array)

もうひとつの例として、Array を紹介します。
Array では、every メソッドsort メソッド の返り値で this を使用しています。

interface Array<T> {
  /**
   * Determines whether all the members of an array satisfy the specified test.
   * @param predicate A function that accepts up to three arguments. The every method calls
   * the predicate function for each element in the array until the predicate returns a value
   * which is coercible to the Boolean value false, or until the end of the array.
   * @param thisArg An object to which the this keyword can refer in the predicate function.
   * If thisArg is omitted, undefined is used as the this value.
   */
  every<S extends T>(
    predicate: (value: T, index: number, array: T[]) => value is S,
    thisArg?: any
  ): this is S[];
  /**
   * Sorts an array in place.
   * This method mutates the array and returns a reference to the same array.
   * @param compareFn Function used to determine the order of the elements. It is expected to return
   * a negative value if first argument is less than second argument, zero if they're equal and a positive
   * value otherwise. If omitted, the elements are sorted in ascending, ASCII character order.
   * ```ts
   * [11,2,22,1].sort((a, b) => a - b)
   * ```
   */
  sort(compareFn?: (a: T, b: T) => number): this;
}

特に、every では this をUser-Defined Type Guardsと組み合わせて使用しています。
つまり、every の引数にtype predicateを返す関数を指定することで、呼び出し元の Array の型を TypeScript に推論させることができます。

const isString = (value: string | number): value is string =>
  typeof value === "string";

if (array.every(isString)) {
  array; // array: string[]
} else {
  array; // array: (string | number)[]
}

こういう使い方もできるんですね。

終わりに

TypeScript ではあまり触れることのない返り値の this ですが、知っておくことで柔軟なクラス定義ができそうです。

参考文献

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0