技術と魚

技術調査、開発Tips、及びしょうもない文章

Immutable.Recordのクラスにインスタンスメソッドをarrow関数で定義したらダメだったよ

細かすぎて伝わらないJavaScript芸人やります。

まず二つのものについて紹介します。

Immutable.js というのがあります。これはいわゆる不変性を保ってくれるクラスの集まりです。 いわゆる関数型言語の値のようにlistやkey-value型のmapをこねこねできます。この中にRecordというクラスがあり、これは言わば限定されたプロパティのみを持つmapです。JSのPlain Objectだと発生してしまう副作用を防ぐことができます。例えば、

class Point extends Immutable.Record({ x: 0, y: 0}) {
  ..
}

const a = new Point({ x: 1, y: 2 });
const b = a.set('x', 3);
console.log(b.x); // => 3
console.log(a.x); // => 1 (変わってない)

なんてことができます。やったね。

Class Property Transformというのがあります。Babelについては省略します。このモジュールはクラスのクラス変数/メンバ変数/クラスメソッド等を定義可能にするbabelの変換です。これのメリットは、JSの .bind(this) を不要にしてくれる点です。例えば、

class Foo extends React.Component {
  handleClick() {
    console.log(this.props.foo);
  }

  render() {
    return <Button onClick={this.handleClick.bind(this)} />;
  }
}

と書かなければならない所を

class Foo extends React.Component {
  handleClick = () => {
    console.log(this.props.foo);
  }

  render() {
    return <Button onClick={this.handleClick} />; // bindがいらない
  }
}

と書けるようになります。

ガッチャンコ!!

てなわけで、こんなことしますよね。

class Point extends Immutable.Record({ x: 0, y: 0 }) {
  length = () => Math.sqrt(this.length2());

  length2 = () => this.x * this.x + this.y * this.y;
}

いい感じです。

const a = new Point({ x: 4, y: 3 });
console.log(a.length()); // => 5

いいですね〜!

const a = new Point({ x: 4, y: 3 });
const b = a.set('y', 6);
console.log(b.length());

< Uncaught TypeError: b.length is not a function

え?

console.log(a instanceof Point); // => true
console.log(b instanceof Point); // => true
console.log(a.length); // => () => ...;
console.log(b.length); // => undefined

f:id:norainu234:20180524191647j:plain

ダメだった

どうやら、Class Property Transformはメンバ変数の定義をconstructorの中に展開するんですね。つまり、

class Point extends Immutable.Record({ x: 0, y: 0 }) {
  constructor(...args) {
    super(...args);
    this.length = () => Math.sqrt(this.length2());
    this.length2 = () => this.x * this.x + this.y * this.y;
}

に近いコードに置き換えられるようです。しかし、ImmutableJSは .set などのメソッドで新しいインスタンスを作る時に、new ではなく Object.create を使うようです。それゆえに、constructorは評価されなくなり、メソッドが無いことになってしまう、ということらしいです。なんとかならないかとImmutableJSを見に行ったけど、issueが一蹴されていたので多分ムリですな。

github.com