0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【typescript】superはarrow functionに使えない

Last updated at Posted at 2023-06-26

typescriptというよりjavascriptの話ですけども。
主にクラスを継承しているときに出てくるsuperキーワードで、つまづいたところがあったのでメモ。

superとは

super キーワードは、オブジェクトの親の関数を呼び出すために使用できます。
super.prop および super[expr] 式は、class とオブジェクトリテラルの両方におけるあらゆるメソッド定義で有効です。

super - JavaScript | MDNより

・・・オブジェクトにも使えるらしい。知らなかった。
今回は、クラスの継承先が、親のメソッドを呼び出すときに使った時のお話。

コード

main.ts
class Parent {
  constructor() {
    console.log('parent constructor');
  }

  method() {
    console.log('parent method');
  }
}

class Child extends Parent {
  constructor() {
    super();
    console.log('child constructor');
  }

  method() {
    super.method();
    console.log('child method');
  }
}

例えばこのようなコードがあったとする。
この時、次のように実行すると、このような結果が得られる。

exec.ts
import { Child } from './main';
const child = new Child();
child.method();
% ts-node exec.ts
parent constructor
child constructor
parent method
child method

おそらくこれは予想通りの結果だと思う。

arrow functionに変えてみる

Parentクラスのmethodをarrow functionに変えてみる。

main.ts
class Parent {
  constructor() {
    console.log('parent constructor');
  }

  method = () => {
    console.log('parent method');
  };
}

class Child extends Parent {
  constructor() {
    super();
    console.log('child constructor');
  }

  method = () => {
    super.method();
    console.log('child method');
  };
}

一見特に問題なさそうなので、そのまま実行してみる。

% ts-node exec.ts
parent constructor
child constructor
file:///main.ts:18
    super.method();
          ^
TypeError: (intermediate value).method is not a function

エラーが出た。super.method()が関数ではないと言われている。

原因

"supper" keyword with Arrow function methods · Issue #11575 · microsoft/TypeScript

superで呼び出せるのは、メソッドではなく、プロパティで定義された関数のみであるとのこと。(多分)
arrow functionはメソッドなので、superで呼び出せない。

コメント欄指摘より:逆でした。

superで呼び出せるのは、プロパティではなく、メソッドで定義された関数のみであるとのこと。
arrow functionはプロパティなので、superで呼び出せない。

解決策

解決策というより回避策な気もするが、いくつかある。

メソッドにする

main.ts
class Parent {
  constructor() {
    console.log('parent constructor');
  }

  method () {
    console.log('parent method');
  }
}

最初のコード。 このような定義方法で書いた関数はメソッドではなくプロパティという呼び方になるのを今更知る。 メソッドであってる。
もちろん、プロパティとメソッドでは、挙動が異なる時がある(thisとかthisとか・・・)ので、注意が必要。

事前にコピーしておく

main.ts
class Parent {
  constructor() {
    console.log('parent constructor');
  }

  method = () => {
    console.log('parent method');
  }
}
class Child extends Parent {
  private parentMethod = this.method; // 事前にコピーしておく

  constructor() {
    super();
    console.log('child constructor');
  }

  method = () => {
    const parentMethod = this.parentMethod;
    parentMethod();
    console.log('child method');
  }
}

methodを再定義する前にコピーしておくことで、superではないものの、同じようなことができる。
もちろん、methodを定義してからthis.methodをすると、後で定義したものが入るので、method内で再帰呼び出しからのエラーになる。

参考サイト:アロー関数 - TypeScript Deep Dive 日本語版

その他

プロパティを定義し、専用メソッドを作成し、その中でメソッドを呼び出すという方法もあるようだが、手間の割にメリットが薄いので割愛。

Typescript: How to call method defined with arrow function in base class using super keyword in child class? - Stack Overflow

ちなみに、二重継承の場合

Parentクラスを継承したChildクラスを継承したGrandChildクラスを作成する。
その時、

  • Parentクラスがメソッドで定義された関数
  • Childクラスがプロパティで定義された関数
    の場合はどうなるか。
main.ts
class Parent {
  constructor() {
    console.log('parent constructor');
  }

  method() {
    console.log('parent method');
  }
}

class Child extends Parent {
  constructor() {
    super();
    console.log('child constructor');
  }

  method = () => {
    super.method();
    console.log('child method');
  };
}

class GrandChild extends Child {
  constructor() {
    super();
    console.log('grandchild constructor');
  }

  method = () => {
    super.method();
    console.log('grandchild method');
  };
}
exec.ts
import { GrandChild } from './main';
const grandChild = new GrandChild();
grandChild.method();
% ts-node exec.ts
parent constructor
child constructor
grandchild constructor
parent method
grandchild method

結果はこの通り。
Childmethodがプロパティで定義されているので、superでエラーが出るかと思いきや、
Parentmethodがメソッドで定義されているので、これが呼び出される。

instanceof演算子がconstructor.prototypeを辿っていくのと同じように、
最寄りの親のメソッドを呼び出すような動作になっていると思う。

終わりに

constructor以外はarrow functionで書けば良いと思っていたが、
意外なところでどちらを使うか考える必要があることを知った。

0
0
4

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?