質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.33%

  • JavaScript

    11275questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • React.js

    378questions

    A JavaScript library for building user interfaces

  • Babel

    31questions

    Babelは、JavaScriptの次世代仕様であるECMAScriptのコンパイラ。次世代の標準機能を用いて記述されたコードを、それらの機能に対応していないブラウザでも動作するコードに変換することができます。

Reactにおける関数の渡し方による動作の違い

解決済

回答 4

投稿 2017/11/24 12:30 ・編集 2017/11/25 00:43

  • 評価
  • クリップ 1
  • VIEW 170

minadukirein

score 77

現在、React.jsを用いて開発を行っております。環境は以下の通りです。
開発環境: Babel + ES6 + React.js + JSX

聞きたいこと

Reactにおける関数の渡し方による動作の違いについて知りたいです。
たとえば以下のようなコードがあったとします。

import React from 'react';

class CountUp extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick = (e) => {
    this.setState({ count: this.state.count + 1 });
    // 上の setState() の記述は好ましくないようです(詳しくは回答を参照ください)
  };

  render() {
    return(
      <button onClick={this.handleClick}>
        {this.state.count}
      </button>
    );
  }
}

このコードのonClickの部分を以下のように変更したとします。

- <button onClick={this.handleClick}>
+ <button onClick={(e) => this.handleClick(e)}>

ちなみにどちらのコードでも同じ結果になります。

Web上で様々なサンプルコードなどを見ていると、
上の記述も下の記述も両方よく見かけるような気がします。
そのとき、ふとこれらの記述に何か違いがあるのかと疑問に思ったしだいです。

もちろん渡せる引数を変更できるという点で違いがあるかと思いますが、
今回は内部的に何か動作に違いがあるかを知りたいです。よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

+5

上のコードの方がパフォーマンス上優れています。

下の方ではrenderする時に毎回アローファンクションを定義していて、比較されるDOMが別になってしまします。
そのため、毎回DOMの更新が起こってしまうので、パフォーマンスが下がります。

参考
https://qiita.com/cubdesign/items/ee8bff7073ebe1979936 (コメント欄も参照してください)
https://reactjs.org/docs/handling-events.html

投稿 2017/11/24 12:51

編集 2017/11/24 13:02

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/24 13:00 編集

    上の方のリンク先'[]'の中も'()'の中もどちらも、404になりました。訂正していただけるとありがたいです。

    キャンセル

  • 2017/11/24 13:03

    リンク修正いたしました

    キャンセル

  • 2017/11/24 13:06

    対応いただき、ありがとうございました。

    キャンセル

  • 2017/11/24 13:19 編集

    リンク先読みました。
    「これを下位コンポーネントとして別のコンポーネントを呼び出した場合、再レンダリングが発生する可能性がある」と読み取れたので混乱しています。

    「毎回」とは書かれていないように思えますが、アロー演算子を使った瞬間、必ずパフォーマンスは下がるのでしょうか?

    キャンセル

  • 2017/11/24 22:19

    もうすでに詳細を調べられたようですが、
    (() => {}) === (() => {})
    が、Falseとなってしまいますので、アローファンクションをプロパティとして渡した場合にはレンダーされるDOMが異なりますので、必ず差分が検知され再描画が走ってしまうこととなります。
    これを防ぐ方法がありまして、プロパティの内容を深くまで操作しないshallowな比較で行うことです。

    React15.3ではそれがトップレベルのAPI「Pure Components」として定義されております。
    http://jyane.jp/2016/08/12/react-purecomponent.html

    それを使えば、このような記法の問題を気にすることがなくコードを書けることとなります。
    しかし、shallowな比較自体はそれほどコストに優れたわけではないので、逆にパフォーマンスが良くないこともあります。
    この例では上のコードであればもう問題とならないのですから、Pure Componentsを使う必要はないでしょう。

    実運用レベルで遅くなるのかということも気になるかと思います。
    私はどちらかと言うと業務系のアプリを書くことが多いのですが、正直パフォーマンスであまり気になったことは正直ありません。
    書きやすさは個人的にもアローファンクションですらっと書きたいと思います。
    しかし、原理を理解し、パフォーマンスに問題があるということを把握したあとは、使うべきではないなと思い利用を控えるようにしています

    キャンセル

  • 2017/11/25 00:07 編集

    なるほどPure Componentというものがあるのですね。参考になります。
    試しに開発環境で子のコンポーネントをPure Componentとして5000個生成し、
    すべての子のコンポーネントに親のコンポーネントから {(e) => this.handleClick(e)} を
    prop として渡して計測したところ、{this.handleClick} を渡した場合より、
    再レンダリングに約2.5倍時間がかかりました。
    質問文のコードのように prop で渡さないで onClick に設定しただけの場合は、
    2つの再レンダリングにかかる時間はほとんど変わりませんでした。

    即時のアロー関数を使って渡す場合は、shouldComponentUpdate() で差分を比較した
    パフォーマンスチューニングなどの場合に困りそうなので、注意深く利用していこうと思います。
    非常に具体的な情報を教えていただき本当にありがとうございます!

    キャンセル

+4

質問の主題からは外れますが、this.setState({ count: this.state.count + 1 })の部分は公式サイトでも注意喚起されている好ましくない書き方です。this.setStateは非同期で実行されるため、以下のようにthis.stateの値が本当に今現在のstateなのか保証されません。

 参考

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

 Wrong

handleClick = (e) => {
    this.setState({ count: this.state.count + 1 });
};

 Correct

handleClick = (e) => {
    this.setState((prevState, props) => ({
        count: prevState.count + 1
    });
};

投稿 2017/11/24 13:05

編集 2017/11/24 19:53

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/24 15:03

    掲示してくださったリンクを拝見しました。
    いままでずっと setState() の詳しい仕様を知らずにコードを書いておりました。
    これからは公式のドキュメントもきちんと目を通すようにいたします。

    それにしてもWeb上のサンプルコードなどを見ると、
    質問文のように this.state.count をインクリメントして
    そのまま SetState() にオブジェクトとして渡していることが多いですよね...。
    非常に有益なアドバイスをいただきありがとうございます!

    キャンセル

  • 2017/11/24 19:39

    そうですね。いろんなところでthis.stateを参照してアップデートかけてるコードを見かけます。

    キャンセル

checkベストアンサー

+2

JS的には殆ど同様です。

var hoge = it => it + 3;
// 方法1
console.log(hoge(3)); // 6
// 方法2
console.log((it => hoge(it))(3)); // 6

即興のアロー関数を1個用意してネストさせている以上の違いはありません。
他にあげるとすれば関数呼び出しの回数が増えて遅くなるくらいですかね。
JSの関数呼び出しはほぼ無視出来るコストらしいですが……

JSでは関数を作るとthisの指している箇所が変わりますが、
アロー関数はthisを作らないので、質問文の内部で平然とthis.handleClickのプロパティを取りに行って関数を叩いているのが確認できます。


【追記】reactの場合

ただしReactの内部で使う時は話が変わってきます。

Handling Events - Reactによると、
親子関係を結ぶ時は再更新処理が増えるので注意しましょうとのことです。
仕組みはこうです。

var a = it => it + 3;
var b = it => it + 3;
console.log(a == b); // false

var c = {};
var d = {};
console.log(c == d);

一見全く同じものにしか見えませんが、
Array、Object、Functionは作成する度に別オブジェクトとして見えないIDが割り振られ別個のものとして認識されます。

Reactの仕組み上、renderは何度も叩かれて毎回別の関数が生まれてしまうので、
親のコンポーネントは前回のDOMと今回のDOMが同一であることが分からなくなってしまうようですね。
即興のアロー関数ではなく、何か固定化した関数をはめ込むのがオススメだよと記載されています。

投稿 2017/11/24 12:50

編集 2017/11/24 13:32

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/24 19:59

    追記ありがとうございます!非常にわかりやすかったです。
    ドキュメントを拝見しましたが、下位コンポーネントに prop としてコールバックを渡すと
    余計な再レンダリングが発生する可能性があると書かれているので、
    できるだけ this.handleClick をそのまま onClick に渡したほうがいいみたいですね。
    ただ「可能性がある」と書かれているので、すべての場合においてパフォーマンスに
    影響があるかは気になるところです。

    キャンセル

+2

そもそも、該当のコードはstage 3のClass Fieldsを用いた記法です。ECMAScriptの正式な仕様としては認められていません。stage 3ですので、このまま採用される可能性はありますが、変更される可能性もあることに留意すべきです。

さて、話は置いといて、ECMAScript 2017の仕様でそのまま書いた場合、handleClickは普通のメソッドとして定義することになると思います。

import React from 'react';

class CountUp extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  handleClick(e) {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return(
      <button onClick={this.handleClick}>
        {this.state.count}
      </button>
    );
  }
}

もちろん、これは正常に動きません。なぜなら、onClickにはhandleClickという関数だけを渡しているため、thisが何であるかという情報が欠如されるからです。クリックによってコールバックで呼び出されるときにthisはこのオブジェクトのことではなくなり、this.setState()の呼出しに失敗します。

それを解決する手段の一つとして、{(e) => this.handleClick(e)}があったのです。こちらは=>によってthisが束縛されるため、thisがこのオブジェクトであるところの関数というものをonClickに渡します。ですので、コールバックでもthisが何か別のものになるのを防ぎます。他の解決方法として{this.handleClick.bind(this)}とする方法や、コンストラクタでthis.handleClick = this.handleClick.bind(this);としておくという方法があります。そして、第4の方法として、該当コードであるClass Fieldsを用いた記法が(まだ正式では無いが)できるようになったと言うことです。

つまり、イベントのコールバックされる関数のthisの問題に対する解決方法について、次のいずれかを行えば良いとなります。

  1. アロー関数=>で囲って渡す。{(e) => this.handleClick(e)}
  2. bind(this)して渡す。{this.handleClick.bind(this)}
  3. コンストラクタでbind(this)しておく。this.handleClick = this.handleClick.bind(this);
  4. Class Fieldsでアロー関数を使う。handleClick = (e) => {...};

これはどれか一つだけで十分であり、複数を採用することは意味がありません。どれがいいかは一長一短になります。1.は毎回アロー関数を作るコストがかかります。2.も毎回新しい関数オブジェクトを作っています。3.はメソッド定義とコンストラクタにそのメソッドに対する情報が分散しています。4.はまだstage 3であり、仕様が変わる恐れがあります。

参考: Handling Events - React


[蛇足]

私のお勧めは第5の邪法であるCoffeeScript(2.0.0以上)を使うことです。

import React from 'react'

class CountUp extends React.Component
  constructor: (props) ->
    super(props)
    @state = count: 0

  handleClick: (e) =>
    this.setState count: @state.count + 1

  render: ->
    <button onClick={@handleClick}>
      {@state.count}
    </button>

JSX記法にも対応したCoffeeScriptに死角は無い!

投稿 2017/11/24 20:53

編集 2017/11/24 20:58

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/24 21:45

    ECMAScriptの仕様について教えていただきありがとうございます。
    Class Fieldは this を bind することなく使えるので、stage 3ということを気にせず使っていました。
    これからはきちんと検討して使用するか判断するようにします。
    また詳細に説明してくださったおかげで this の扱いについて整理できたので良かったです。
    CoffeeScript で記述すると非常にスマートに記述できますね!素晴らしい!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.33%

関連した質問

同じタグがついた質問を見る

  • JavaScript

    11275questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • React.js

    378questions

    A JavaScript library for building user interfaces

  • Babel

    31questions

    Babelは、JavaScriptの次世代仕様であるECMAScriptのコンパイラ。次世代の標準機能を用いて記述されたコードを、それらの機能に対応していないブラウザでも動作するコードに変換することができます。