🍃このブログは移転しました。
3秒後、自動的に移動します・・・。

jsx-no-bindだとStateless Functional Componentsでpropsをハンドラに上手く渡せない問題

お前は何を言っているんだみたいなタイトルですが、つまり・・。

// 子
const SFC = ({
  some,
  vars,
  and,
  handler,
}) => {
  return (
    <div onClick={handler}></div>
  );
}

// 親
class Container extends React.Component {
  render() {
    // 略
    <SFC {...item} handler={this.handler} />
  }

  handler(ev) {
    // ココで子に渡したpropsを使ってなんかしたい
  }
}

さてどうしたらいいんだという話。

jsx-no-bindとは

eslint-plugin-react/jsx-no-bind.md at master · yannickcr/eslint-plugin-react · GitHub

eslint-plugin-reactのルールのひとつで、JSXの中で関数書くな!というやつ。
このルールがなければ↓こんな風に書けるので何も困らない。

// SFC
<div onClick={(ev) => { handler(ev, vars); }}</div>

けど、レンダリングする度にFunctionがスコープを作ってパフォーマンスが云々・・というわけで、せっかくSFC使っておきながらなんか中途半端に甘えを許すみたいな気持ちになる。
できればこのルールはstrictにtrueなまま運用したいなーと。

でも`props`渡せないし困るしうーむ。
さて。

案1: 懐かしいdata属性

上位のハンドラで`ev`は取れるなら、そこを経由すればいいのでは作戦。

// SFC
<div onClick={handler} data-vars={vars}></div>

こうしちゃえば、

// 親
handler(ev) {
  // 取れる!
  const vars = ev.currentTarget.getAttribute('data-vars');
}

ただしこれだと文字列しか渡せないので、たぶん後々に困るケースがありそう・・。

案2: jsx-no-bindをあきらめる

jsx-no-bindにはオプションが指定できて、`allowArrowFunctions`ってのがある。

要するに、アローファンクションなら使ってもいいよっていうものなので、これを`true`にすれば最初の例でも怒られない。

いやでもこれはーなんというかー!

案3: SFCをあきらめる

SFCをやめて、普通にCompomentにすればいける。

// 元SFC
class C extends React.Component {
  constructor() {
    super();
    this._handler = this._handler.bind(this);
  }

  render() {
    return (
      <div onClick={this._handler}></div>
    );
  }

  _handler(ev) {
    const { vars, handler } = this.props;
    handler(ev, vars);
  }
}

いやでもそうなると今度は`prefer-stateless-function`というルールがですね・・。

eslint-plugin-react/prefer-stateless-function.md at master · yannickcr/eslint-plugin-react · GitHub

案4: JSXの外でbindする

それで最終的に思いついたのがコレ。

// SFC
const SFC = ({
  some,
  vars,
  and,
  handler,
}) => {
  // ここならJSXの外なので怒られない
  const _handler = (ev) => { handler(ev, vars); };

  return (
    <div onClick={_handler}></div>
  );
}

これだー!!

と思ったけど、これはパフォーマンス的にはいいのか?JSXの中でスコープ作るのと何が違うんやろう?という疑問がわきまして。

どうするのが最良か、誰かおしえてくれんかなーということで、記事にしてみました。

案5: コンポーネントの外でbindする( by @yosuke_furukawa

関数を返すハンドラを渡して、動的にやるパターン。

// SFC
const SFC = ({
  some,
  vars,
  and,
  handler,
}) => {
  return (
    <div onClick={handler(vars)}></div>
  );
}

// 親
handler(vars) {
  return (ev) => {
    vars; // 取れる!
  };
}

なるほど!天才!って最初は思ったけど、生成後のコードという意味ではあまり変わらんので、書き味の好みでお選びくださいって感じです。
他のハンドラと並べて見た時に、親でなんでコレだけ関数返してるんやろう・・って気持ちになる人もいるかもしれない!

ちなみにビルドすると

前後は省略してるけど、違いはコレだけ。

  // 案2
  return React.createElement(
    'div',
    { onClick: function handler(ev) {
          _handler(ev, vars);
        } },

  // 案4
  var _handler = function _handler(ev) {
    handler(ev, vars);
  };

  return React.createElement(
    'div',
    { onClick: _handler }

`React.createElement()`にどう関数が渡るかだけの違いで、これだけ見るとなんも変わらんよね。

というわけで結論

今のところコレだ!!というパターンはないっぽいので、お好きなやつを使う。

まさかのオチ