Ich habe eine Menge react
Code gelesen und sehe solche Dinge, die ich nicht verstehe:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Ich habe eine Menge react
Code gelesen und sehe solche Dinge, die ich nicht verstehe:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Antworten:
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 add
Funktion 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, add
eine 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 handleChange
Funktion 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 handleChange
Funktion können wir den gesamten duplizierten Code entfernen, der zum Einrichten von change
Listenern für jedes Feld führen würde. Cool!
1 Hier musste ich nicht lexikalisch binden, this
da die ursprüngliche add
Funktion 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 partial
Sie 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">
$
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
$
wie es verwendet wird. Wenn Sie nach der Implementierung selbst fragen, $
handelt es sich um eine Funktion, die einen Wert empfängt x
und 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 k
es 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.
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.
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 reagierenAnwendung. 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) | }
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
};
};
Stellen Sie sich das so vor: Jedes Mal, wenn Sie einen Pfeil sehen, ersetzen Sie ihn durch function
. function parameters
werden 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();
};
}
// 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
this
.
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 button
Rückruf mit onClick. Und wir müssen an id
die Funktion übergeben, onClick
akzeptieren 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 id
in 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
)
Das Beispiel in Ihrer Frage ist das von a, curried function
das arrow function
ein implicit return
für das erste Argument verwendet und hat .
Die Pfeilfunktion bindet dies lexikalisch, dh sie haben kein eigenes this
Argument, sondern übernehmen den this
Wert 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 handleChange
als 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*2
das 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, arguments
sondern 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);
}
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 ...).