JavaScriptで複数の矢印関数は何を意味しますか?


472

私はたくさんのreactコードを読んでいて、私には理解できない次のようなものが表示されます。

handleChange = field => e => {
  e.preventDefault();
  /// Do something here
}

11
面白さのために、カイルシンプソンは矢印のすべての決定パスをこのフローチャートに入れました。出典:彼のコメントはMozilla Hacksのブログ記事と題した上ES6で深さ:アロー機能
gfullam

すばらしい答えがあり、今は恵みがあるからです。以下の回答がカバーしていない、あなたが理解していないことについて詳しく説明していただけますか?
マイケルワーナー、

5
本の新版があるため、矢印関数のフローチャートのURLが壊れています。作業用URLはraw.githubusercontent.com/getify/You-Dont-Know-JS/1st-ed/…にあります
Dhiraj Gupta

回答:


832

それはカレー関数です

まず、この関数を2つのパラメーターで調べます…

const add = (x, y) => x + y
add(2, 3) //=> 5

ここでは再びカレーの形で…

const add = x => y => x + y

これは、矢印関数なしの同じ1コードです…

const add = function (x) {
  return function (y) {
    return x + y
  }
}

焦点を合わせる return

それを別の方法で視覚化すると役立つ場合があります。アロー関数はこのように機能することがわかっています戻り値に特に注意を払いましょう。

const f = someParam => returnValue

私たちのように、add関数が返す機能を -我々は追加明確にするために括弧を使用することができます。太字のテキストは、私たちの関数の戻り値でありますadd

const add = x => (y => x + y)

つまりadd、ある数の関数は関数を返します

add(2) // returns (y => 2 + y)

カレー関数の呼び出し

したがって、カレー関数を使用するには、少し異なる方法で呼び出す必要があります…

add(2)(3)  // returns 5

これは、最初の(外部)関数呼び出しが2番目の(内部)関数を返すためです。2番目の関数を呼び出して初めて、実際に結果が得られます。これは、呼び出しを2行で区切るとより明白になります…

const add2 = add(2) // returns function(y) { return 2 + y }
add2(3)             // returns 5

新しい理解をコードに適用する

関連:「バインド、部分適用、カレーの違いは何ですか?」

OK、それがどのように機能するかを理解したので、コードを見てみましょう

handleChange = field => e => {
  e.preventDefault()
  /// Do something here
}

まず、矢印関数を使用せずにそれを表現することから始めます…

handleChange = function(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
    // return ...
  };
};

ただし、アロー関数は字句的にバインドするためthis実際には次のようになります…

handleChange = function(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
    // return ...
  }.bind(this)
}.bind(this)

たぶん今、これが何をしているのかをより明確に見ることができます。handleChange関数は、指定する関数を作成していますfield。アプリケーションの状態を更新するには、各入力で独自のリスナーをセットアップする必要があるため、これは便利なReact手法です。このhandleChange関数を使用することで、change各フィールドにリスナーを設定することになる重複コードをすべて排除できます。涼しい!

1ここではthis、元のadd関数はコンテキストを使用しないため、字句的にバインドする必要はありませんでした。この場合、それを保持することは重要ではありません。


さらに多くの矢

必要に応じて、2つ以上の矢印関数をシーケンスできます-

const three = a => b => c =>
  a + b + c

const four = a => b => c => d =>
  a + b + c + d

three (1) (2) (3) // 6

four (1) (2) (3) (4) // 10

カリー化された関数は驚くべきことをすることができます。以下では$、2つのパラメーターを持つカレー関数として定義されていますが、呼び出しサイトでは、任意の数の引数を指定できるように見えます。カリー化はアリティの抽象化です-

const $ = x => k =>
  $ (k (x))
  
const add = x => y =>
  x + y

const mult = x => y =>
  x * y
  
$ (1)           // 1
  (add (2))     // + 2 = 3
  (mult (6))    // * 6 = 18
  (console.log) // 18
  
$ (7)            // 7
  (add (1))      // + 1 = 8
  (mult (8))     // * 8 = 64
  (mult (2))     // * 2 = 128
  (mult (2))     // * 2 = 256
  (console.log)  // 256

部分適用

部分適用は関連する概念です。関数をカレー形式で定義する必要がないことを除いて、カリーと同様に関数を部分的に適用できます-

const partial = (f, ...a) => (...b) =>
  f (...a, ...b)

const add3 = (x, y, z) =>
  x + y + z

partial (add3) (1, 2, 3)   // 6

partial (add3, 1) (2, 3)   // 6

partial (add3, 1, 2) (3)   // 6

partial (add3, 1, 2, 3) () // 6

partial (add3, 1, 1, 1, 1) (1, 1, 1, 1, 1) // 3

ここpartialにあなた自身のブラウザで遊ぶことができる実際のデモがあります-

const partial = (f, ...a) => (...b) =>
  f (...a, ...b)
  
const preventDefault = (f, event) =>
  ( event .preventDefault ()
  , f (event)
  )
  
const logKeypress = event =>
  console .log (event.which)
  
document
  .querySelector ('input[name=foo]')
  .addEventListener ('keydown', partial (preventDefault, logKeypress))
<input name="foo" placeholder="type here to see ascii codes" size="50">


2
これは抜群です!だれが実際に「$」を実際に割り当てる頻度はどれくらいですか?それとも、これの反応のエイリアスですか?最後に私の無知を許してください。好奇心が強いのは、シンボルが他の言語であまりにも頻繁に割り当てられるのを見ないからです。
Caperneoignis

7
@Caperneoignis $はコンセプトのデモに使用されましたが、好きな名前を付けることができます。偶然ではあるが完全に無関係であり、jQueryなどの一般的なライブラリで使用され$ $ます。これは、関数のライブラリ全体への一種のグローバルエントリポイントです。他にも使われていると思います。もう1つは_、アンダースコアやロダッシュなどのライブラリで一般化されているものです。あるシンボルが別のシンボルよりも意味がありません。プログラムに意味割り当てます。それは単に有効なJavaScriptです:D
ありがとう

1
聖なるフリジョリ、いい答え。opが受け入れることを望む
mtyson

2
@Blake $使用方法を確認することで、理解を深めることができます。実装自体について質問している場合$は、値を受け取っxて新しい関数を返す関数k => ...です。返された関数の本体を見ると、関数でなければならないことがk (x)わかりk、結果k (x)がに戻されると$ (...)、別のが返さk => ...れます。行き詰まっている、私に知らせてください。
ありがとう

2
この答えはそれがどのように機能するか、そしてこのテクニックにはどのようなパターンがあるかを説明しました。これがどのシナリオでも実際に優れたソリューションである理由について、具体的なことは何もないように思います。どのような状況でabc(1,2,3)は、は理想的とは言えませんabc(1)(2)(3)。コードのロジックについて推論するのは難しく、関数abcを読み取るのも難しく、関数呼び出しを読み取るのも困難です。abcが何をするのかを知るだけで済む前に、abcが返す名前のない関数が何をするのかわからなくなりました。
Muhammad Umer

57

理解矢印の機能の利用可能な構文は、あなたが提供されている例のように「チェーン」するとき、彼らが導入されているどのような行動の理解が得られます。

複数のパラメーターの有無にかかわらず、ブロック波括弧なしで矢印関数が記述されると、関数の本体を構成する式が暗黙的に返されます。あなたの例では、その式は別の矢印関数です。

No arrow funcs              Implicitly return `e=>{…}`    Explicitly return `e=>{…}` 
---------------------------------------------------------------------------------
function (field) {         |  field => e => {            |  field => {
  return function (e) {    |                             |    return e => {
      e.preventDefault()   |    e.preventDefault()       |      e.preventDefault()
  }                        |                             |    }
}                          |  }                          |  }

矢印構文を使用して無名関数を作成するもう1つの利点は、それらが定義されているスコープに字句的にバインドされることです。MDNの「矢印機能」から:

矢印関数式はに比べて短い構文を持つ関数式と辞書的に結合し、この値を。アロー関数は常に匿名です。

これは、あなたの例で特に適切です。 応用。@naomikによって尖ったアウトのように、中にあなたが頻繁にアクセスリアクトコンポーネントのメンバ関数を使用してthis。例えば:

Unbound                     Explicitly bound            Implicitly bound 
------------------------------------------------------------------------------
function (field) {         |  function (field) {       |  field => e => {
  return function (e) {    |    return function (e) {  |    
      this.setState(...)   |      this.setState(...)   |    this.setState(...)
  }                        |    }.bind(this)           |    
}                          |  }.bind(this)             |  }

53

一般的なヒントとして、新しいJS構文とそのコンパイル方法に混乱した場合は、babelを確認してください。たとえば、コードをバベルでコピーしてes2015プリセットを選択すると、次のような出力が得られます

handleChange = function handleChange(field) {
 return function (e) {
 e.preventDefault();
  // Do something here
   };
 };

バベル


42

このように考えると、矢印が表示されるたびにに置き換えられfunctionます。
function parameters矢印の前に定義されています。
だからあなたの例では:

field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}

そして一緒に:

function (field) { 
    return function (e) { 
        e.preventDefault(); 
    };
}

ドキュメントから

// Basic syntax:
(param1, param2, paramN) => { statements }
(param1, param2, paramN) => expression
   // equivalent to:  => { return expression; }

// Parentheses are optional when there's only one argument:
singleParam => { statements }
singleParam => expression

6
字句的にバインドされていることを忘れないでくださいthis
ありがとう

30

簡潔でシンプル 🎈

短く書かれた別の関数を返す関数です。

const handleChange = field => e => {
  e.preventDefault()
  // Do something here
}

// is equal to 
function handleChange(field) {
  return function(e) {
    e.preventDefault()
    // Do something here
  }
}

人々がそれをする理由❓

カスタマイズ可能な関数を作成する必要があるときに直面しましたか?または、固定パラメーター(引数)を含むコールバック関数を作成する必要がありますが、関数により多くの変数を渡す必要がありますが、グローバル変数は避けますか?あなたの答えが「はい」の場合、それはそれを行う方法です。

たとえばbutton、withClickコールバックがあります。そしてid、関数に渡す必要がありonClickますが、受け入れるパラメータeventは1つだけです。次のように内部に追加のパラメータを渡すことはできません。

const handleClick = (event, id) {
  event.preventDefault()
  // Dispatch some delete action by passing record id
}

効果がないでしょう!

したがって、グローバル変数は悪であるので、グローバル変数なしで独自の変数スコープを持つ他の関数を返す関数を作成します。

関数の下handleClick(props.id)}が呼び出されて関数が返さidれ、そのスコープ内にあります!何度押しても、IDは相互に影響したり変更したりしませんが、完全に分離されています。

const handleClick = id => event {
  event.preventDefault()
  // Dispatch some delete action by passing record id
}

const Confirm = props => (
  <div>
    <h1>Are you sure to delete?</h1>
    <button onClick={handleClick(props.id)}>
      Delete
    </button>
  </div
)

2

あなたの質問の例は、最初の引数curried functionを使用し、arrow functionを持つの例implicit returnです。

Arrow関数はこれを字句的にバインドします。つまり、独自のthis引数はありませんが、this値を囲んでいるスコープから取得します。

上記のコードと同等のものは

const handleChange = (field) {
  return function(e) {
     e.preventDefault();
     /// Do something here
  }.bind(this);
}.bind(this);

例についてもう1つ注意する点はhandleChange、constまたは関数として定義することです。おそらくそれをクラスメソッドの一部として使用していて、class fields syntax

したがって、外部関数を直接バインドする代わりに、クラスコンストラクターでバインドします。

class Something{
    constructor(props) {
       super(props);
       this.handleChange = this.handleChange.bind(this);
    }
    handleChange(field) {
        return function(e) {
           e.preventDefault();
           // do something
        }
    }
}

この例でもう1つ注意する点は、暗黙的な戻りと明示的な戻りの違いです。

const abc = (field) => field * 2;

上記は暗黙の戻りの例です。値フィールドを引数として取り、結果を返しますfield*2関数を明示的に指定したを

明示的な戻りの場合、値を返すようにメソッドに明示的に指示します

const abc = () => { return field*2; }

矢印関数についてもう1つ注意すべき点は、矢印関数には独自の関数はなくarguments、親スコープから継承することです。

たとえば、次のようなアロー関数を定義した場合

const handleChange = () => {
   console.log(arguments) // would give an error on running since arguments in undefined
}

代替の矢印関数として、使用できる残りのパラメーターを提供します

const handleChange = (...args) => {
   console.log(args);
}

1

それは完全に関連しているわけではないかもしれませんが、言及された質問はcaseを使用する(そして私はこのSOスレッドにぶつかる)ため、ここでは明示的に言及されていない二重矢印関数の1つの重要な側面があります。「最初の」矢印(関数)のみが名前が付けられ(したがって、実行時に「識別可能」)、次の矢印は匿名であり、Reactの観点からは、すべてのレンダリングで「新しい」オブジェクトとしてカウントされます。

したがって、二重矢印関数は、PureComponentを常に再レンダリングします。

次のような変更ハンドラーを持つ親コンポーネントがあります。

handleChange = task => event => { ... operations which uses both task and event... };

そして、次のようなレンダーで:

{ tasks.map(task => <MyTask handleChange={this.handleChange(task)}/> }

次に、handleChangeを入力またはクリックで使用します。そして、これはすべてうまくいき、とても素敵に見えます。しかし、それは、親に再レンダリングを引き起こすすべての変更(完全に無関係な状態変更など)も、PureComponentsであるにもかかわらず、MyTaskのすべてを再レンダリングすることも意味します。

これは、「最外」の矢印とそれをフィードするオブジェクトを渡す、カスタムshouldUpdate関数を書く、または名前付き関数を書く(そしてこれを手動でバインドする...)などの基本に戻るなど、多くの方法で軽減できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.