Was bedeuten mehrere Pfeilfunktionen in Javascript?


472

Ich habe eine Menge reactCode gelesen und sehe solche Dinge, die ich nicht verstehe:

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

11
Nur zum Spaß hat Kyle Simpson alle Entscheidungspfade für Pfeile in dieses Flussdiagramm eingefügt . Quelle: Sein Kommentar zu einem Mozilla Hacks-Blog-Beitrag mit dem Titel ES6 In Depth: Arrow-Funktionen
Gfullam

Da gibt es tolle Antworten und jetzt ein Kopfgeld. Können Sie bitte näher erläutern, was Sie nicht verstehen, was die folgenden Antworten nicht abdecken?
Michael Warner

5
Die URL für das Flussdiagramm der Pfeilfunktionen ist jetzt fehlerhaft, da es eine neue Ausgabe des Buches gibt. Die Arbeits-
Dhiraj Gupta

Antworten:


831

Das ist eine Curry-Funktion

Untersuchen Sie diese Funktion zunächst mit zwei Parametern…

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

Hier ist es wieder in Curryform…

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

Hier ist der gleiche 1 Code ohne Pfeilfunktionen…

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

Konzentrieren Sie sich auf return

Es könnte hilfreich sein, es anders zu visualisieren. Wir wissen, dass Pfeilfunktionen so funktionieren - achten wir besonders auf den Rückgabewert .

const f = someParam => returnValue

Unsere addFunktion gibt also eine Funktion zurück - wir können Klammern für zusätzliche Klarheit verwenden. Der fettgedruckte Text ist der Rückgabewert unserer Funktionadd

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

Mit anderen Worten, addeine Zahl gibt eine Funktion zurück

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

Curry-Funktionen aufrufen

Um unsere Curry-Funktion nutzen zu können, müssen wir sie etwas anders nennen…

add(2)(3)  // returns 5

Dies liegt daran, dass der erste (äußere) Funktionsaufruf eine zweite (innere) Funktion zurückgibt. Erst nachdem wir die zweite Funktion aufgerufen haben, erhalten wir tatsächlich das Ergebnis. Dies wird deutlicher, wenn wir die Anrufe auf zwei Leitungen trennen ...

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

Wenden Sie unser neues Verständnis auf Ihren Code an

verwandt: "Was ist der Unterschied zwischen Binden, Teilapplikation und Currying?"

OK, jetzt, da wir verstehen, wie das funktioniert, schauen wir uns Ihren Code an

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

Wir beginnen damit, es ohne Pfeilfunktionen darzustellen…

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

Da Pfeilfunktionen jedoch lexikalisch binden this, würde es tatsächlich eher so aussehen…

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

Vielleicht können wir jetzt klarer sehen, was dies bewirkt. Die handleChangeFunktion erstellt eine Funktion für eine bestimmte field. Dies ist eine praktische Reaktionstechnik, da Sie für jeden Eingang eigene Listener einrichten müssen, um den Anwendungsstatus zu aktualisieren. Mit der handleChangeFunktion können wir den gesamten duplizierten Code entfernen, der zum Einrichten von changeListenern für jedes Feld führen würde. Cool!

1 Hier musste ich nicht lexikalisch binden, thisda die ursprüngliche addFunktion keinen Kontext verwendet, daher ist es in diesem Fall nicht wichtig, sie beizubehalten.


Noch mehr Pfeile

Bei Bedarf können mehr als zwei Pfeilfunktionen sequenziert werden -

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

Curry-Funktionen können überraschen. Im Folgenden wird $eine Curry-Funktion mit zwei Parametern definiert. Am Aufrufort scheint es jedoch so, als könnten wir eine beliebige Anzahl von Argumenten liefern. Currying ist die Abstraktion von Arity -

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

Teilanwendung

Teilanwendung ist ein verwandtes Konzept. Es ermöglicht uns, Funktionen, ähnlich wie beim Curry, teilweise anzuwenden, außer dass die Funktion nicht in Curry-Form definiert werden muss -

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

Hier ist eine funktionierende Demo, mit der partialSie in Ihrem eigenen Browser spielen können -

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
Das ist hervorragend! Wie oft weist jemand tatsächlich das '$' zu? Oder ist es ein Alias ​​dafür zu reagieren? Vergib mir meine Unwissenheit beim letzten Mal, nur neugierig, weil ich nicht sehe, dass ein Symbol in anderen Sprachen zu oft eine Aufgabe bekommt.
Caperneoignis

7
@Caperneoignis $wurde verwendet, um das Konzept zu demonstrieren , aber Sie können es benennen, was Sie wollen. Zufällig, aber völlig unabhängig, $ wurde es in populären Bibliotheken wie jQuery verwendet, wo $es eine Art globaler Einstiegspunkt für die gesamte Funktionsbibliothek ist. Ich denke, es wurde auch in anderen verwendet. Eine andere, die Sie sehen werden, ist _in Bibliotheken wie Unterstrich und Lodash populär. Kein Symbol ist aussagekräftiger als das andere. Sie weisen die Bedeutung für Ihr Programm zu. Es ist einfach gültiges JavaScript: D
Danke

1
heiliger Frijoli, nette Antwort.
Ich

2
@Blake Sie können ein besseres Verständnis davon erlangen, indem Sie sich ansehen, $wie es verwendet wird. Wenn Sie nach der Implementierung selbst fragen, $handelt es sich um eine Funktion, die einen Wert empfängt xund eine neue Funktion zurückgibt k => .... Wenn wir k (x)uns den Körper der zurückgegebenen Funktion ansehen, sehen wir, dass wir wissen, dass kes auch eine Funktion sein muss, und was auch immer das Ergebnis von k (x)ist, wird zurückgesetzt $ (...), von dem wir wissen, dass es eine andere zurückgibt k => ..., und weiter geht es ... Wenn Sie immer noch sind stecken bleiben, lass es mich wissen.
Vielen Dank, dass Sie

2
Diese Antwort erklärte, wie es funktioniert und welche Muster es bei dieser Technik gibt. Ich glaube, es gibt nichts Spezifisches darüber, warum dies in jedem Szenario tatsächlich eine bessere Lösung ist. In welcher Situation abc(1,2,3)ist weniger als ideal als abc(1)(2)(3). Es ist schwieriger, über die Logik des Codes nachzudenken, und es ist schwierig, die Funktion abc zu lesen, und es ist schwieriger, den Funktionsaufruf zu lesen. Bevor Sie nur wissen mussten, was abc tut, sind Sie sich jetzt nicht sicher, welche unbenannten Funktionen abc zurückgibt, und zwar zweimal.
Muhammad Umer

57

Wenn Sie die verfügbaren Syntaxen von Pfeilfunktionen verstehen, können Sie verstehen, welches Verhalten sie einführen, wenn sie wie in den von Ihnen bereitgestellten Beispielen verkettet sind.

Wenn eine Pfeilfunktion ohne Klammern mit oder ohne mehrere Parameter geschrieben wird, wird implizit der Ausdruck zurückgegeben, der den Funktionskörper bildet . In Ihrem Beispiel ist dieser Ausdruck eine weitere Pfeilfunktion.

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()
  }                        |                             |    }
}                          |  }                          |  }

Ein weiterer Vorteil des Schreibens anonymer Funktionen mithilfe der Pfeilsyntax besteht darin, dass sie lexikalisch an den Bereich gebunden sind, in dem sie definiert sind. Aus 'Pfeilfunktionen' auf MDN :

Ein Pfeilfunktionsausdruck hat im Vergleich zu Funktionsausdrücken eine kürzere Syntax und bindet diesen Wert lexikalisch . Pfeilfunktionen sind immer anonym .

Dies ist in Ihrem Beispiel besonders relevant, wenn man bedenkt, dass es aus a stammt Anwendung. Wie Wie von @naomik, Reaktion in oft Sie einen Zugriff auf Komponente Member - Funktionen verwenden this. Zum Beispiel:

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

Ein allgemeiner Tipp: Wenn Sie durch eine neue JS-Syntax und deren Kompilierung verwirrt sind, können Sie babel überprüfen . Wenn Sie beispielsweise Ihren Code in babel kopieren und die Voreinstellung es2015 auswählen, erhalten Sie eine Ausgabe wie diese

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

babel


42

Stellen Sie sich das so vor: Jedes Mal, wenn Sie einen Pfeil sehen, ersetzen Sie ihn durch function.
function parameterswerden vor dem Pfeil definiert.
Also in Ihrem Beispiel:

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

und dann zusammen:

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

Aus den Dokumenten :

// 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
Vergessen Sie nicht, die lexikalisch gebundenen zu erwähnen this.
Danke

30

Kurz und einfach 🎈

Es ist eine Funktion, die eine andere kurz geschriebene Funktion zurückgibt.

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

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

Warum Leute es tun

Haben Sie sich gestellt, wenn Sie eine Funktion schreiben müssen, die angepasst werden kann? Oder Sie müssen eine Rückruffunktion schreiben, die feste Parameter (Argumente) hat, aber Sie müssen mehr Variablen an die Funktion übergeben, aber globale Variablen vermeiden? Wenn Sie mit " Ja " antworten , ist dies die richtige Vorgehensweise.

Zum Beispiel haben wir einen buttonRückruf mit onClick. Und wir müssen an iddie Funktion übergeben, onClickakzeptieren aber nur einen Parameter event, wir können keine zusätzlichen Parameter wie folgt übergeben:

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

Es wird nicht funktionieren!

Deshalb machen wir eine Funktion, die andere Funktionen mit ihrem eigenen Variablenbereich ohne globale Variablen zurückgibt, weil globale Variablen böse sind 😈.

Unten wird die Funktion handleClick(props.id)}aufgerufen und eine Funktion zurückgegeben, die idin ihrem Umfang liegt! Unabhängig davon, wie oft gedrückt wird, wirken sich die IDs nicht gegenseitig aus oder ändern sich gegenseitig. Sie sind vollständig isoliert.

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

Das Beispiel in Ihrer Frage ist das von a, curried functiondas arrow functionein implicit returnfür das erste Argument verwendet und hat .

Die Pfeilfunktion bindet dies lexikalisch, dh sie haben kein eigenes thisArgument, sondern übernehmen den thisWert aus dem umschließenden Bereich

Ein Äquivalent des obigen Codes wäre

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

Eine weitere Sache, die Sie an Ihrem Beispiel beachten sollten, ist die Definition handleChangeals Konstante oder Funktion. Wahrscheinlich verwenden Sie es als Teil einer Klassenmethode und es verwendet aclass fields syntax

Anstatt die äußere Funktion direkt zu binden, binden Sie sie im Klassenkonstruktor

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

Eine andere Sache, die im Beispiel zu beachten ist, ist der Unterschied zwischen impliziter und expliziter Rückgabe.

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

Oben ist ein Beispiel für implizite Rückgabe, dh. Es nimmt das Wertefeld als Argument und gibt das Ergebnis zurück, field*2das die zurückzugebende Funktion explizit angibt

Bei einer expliziten Rückgabe würden Sie die Methode explizit anweisen, den Wert zurückzugeben

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

Eine andere Sache, die Sie bei Pfeilfunktionen beachten sollten, ist, dass sie keine eigenen haben, argumentssondern diese auch vom Umfang der Eltern erben.

Zum Beispiel, wenn Sie nur eine Pfeilfunktion wie definieren

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

Alternativ bieten Pfeilfunktionen die Restparameter, die Sie verwenden können

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

1

Es mag nicht vollständig verwandt sein, aber da die erwähnte Frage "React Use Case" (und ich stoße immer wieder auf diesen SO-Thread): Es gibt einen wichtigen Aspekt der Doppelpfeilfunktion, der hier nicht explizit erwähnt wird. Nur der 'erste' Pfeil (Funktion) wird benannt (und somit durch die Laufzeit 'unterscheidbar'), alle folgenden Pfeile sind anonym und zählen aus Sicht von React bei jedem Rendern als 'neues' Objekt.

Somit bewirkt die Doppelpfeilfunktion, dass jede PureComponent ständig neu gerendert wird.

Beispiel

Sie haben eine übergeordnete Komponente mit einem Änderungshandler wie folgt:

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

und mit einem Render wie:

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

handleChange wird dann für eine Eingabe oder einen Klick verwendet. Und das alles funktioniert und sieht sehr gut aus. ABER es bedeutet, dass jede Änderung, die das übergeordnete Element zum erneuten Rendern veranlasst (wie eine völlig unabhängige Statusänderung), auch ALLE Ihre MyTask neu rendert, obwohl es sich um PureComponents handelt.

Dies kann auf viele Arten gelindert werden, z. B. indem Sie den 'äußersten' Pfeil und das Objekt, mit dem Sie ihn füttern würden, übergeben oder eine benutzerdefinierte shouldUpdate-Funktion schreiben oder zu den Grundlagen zurückkehren, z. B. benannte Funktionen schreiben (und diese manuell binden ...).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.