Ho letto un sacco di react
codice e vedo cose come questa che non capisco:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Ho letto un sacco di react
codice e vedo cose come questa che non capisco:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Risposte:
Questa è una funzione al curry
Innanzitutto, esamina questa funzione con due parametri ...
const add = (x, y) => x + y
add(2, 3) //=> 5
Eccolo di nuovo in forma al curry ...
const add = x => y => x + y
Ecco lo stesso 1 codice senza funzioni freccia ...
const add = function (x) {
return function (y) {
return x + y
}
}
Concentrarsi su return
Potrebbe aiutare a visualizzarlo in un altro modo. Sappiamo che le funzioni delle frecce funzionano così: prestiamo particolare attenzione al valore di ritorno .
const f = someParam => returnValue
Quindi la nostra add
funzione restituisce una funzione : possiamo usare le parentesi per maggiore chiarezza. Il testo in grassetto è il valore di ritorno della nostra funzioneadd
const add = x => (y => x + y)
In altre parole, un add
certo numero restituisce una funzione
add(2) // returns (y => 2 + y)
Chiamare le funzioni al curry
Quindi, per usare la nostra funzione al curry, dobbiamo chiamarla in modo leggermente diverso ...
add(2)(3) // returns 5
Questo perché la prima chiamata di funzione (esterna) restituisce una seconda funzione (interna). Solo dopo aver chiamato la seconda funzione otteniamo effettivamente il risultato. Ciò è più evidente se separiamo le chiamate su due linee ...
const add2 = add(2) // returns function(y) { return 2 + y }
add2(3) // returns 5
Applicando la nostra nuova comprensione al tuo codice
correlati: "Qual è la differenza tra associazione, applicazione parziale e curry?"
OK, ora che capiamo come funziona, diamo un'occhiata al tuo codice
handleChange = field => e => {
e.preventDefault()
/// Do something here
}
Inizieremo rappresentandolo senza usare le funzioni freccia ...
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
};
};
Tuttavia, poiché le funzioni della freccia si legano in modo lessicale this
, sembrerebbe in realtà più simile a questo ...
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
}.bind(this)
}.bind(this)
Forse ora possiamo vedere cosa sta facendo più chiaramente. La handleChange
funzione sta creando una funzione per un determinato field
. Questa è una pratica tecnica React perché è necessario impostare i propri listener su ciascun input per aggiornare lo stato delle applicazioni. Usando la handleChange
funzione, possiamo eliminare tutto il codice duplicato che comporterebbe la configurazione di change
listener per ciascun campo. Freddo!
1 Qui non ho dovuto legarmi lessicamente this
perché la add
funzione originale non utilizza alcun contesto, quindi non è importante preservarla in questo caso.
Ancora più frecce
Più di due funzioni freccia possono essere sequenziate, se necessario -
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
Le funzioni al curry sono in grado di sorprendere. Di seguito vediamo $
definita una funzione curry con due parametri, ma nel sito della chiamata sembra che possiamo fornire un numero qualsiasi di argomenti. Currying è l'astrazione dell'arità -
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
Applicazione parziale
L'applicazione parziale è un concetto correlato. Ci consente di applicare parzialmente funzioni, simili al curry, tranne per il fatto che la funzione non deve essere definita in forma curry -
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
Ecco una demo funzionante con partial
cui puoi giocare nel tuo browser -
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">
$
stato usato per demo il concetto, ma puoi chiamarlo come vuoi. Per coincidenza, ma completamente non correlato, $
è stato utilizzato in librerie popolari come jQuery, dove si $
trova una sorta di punto di accesso globale all'intera libreria di funzioni. Penso che sia stato usato anche in altri. Un altro che vedrai è _
, reso popolare in biblioteche come underscore e lodash. Nessun simbolo è più significativo di un altro; si assegna il significato al proprio programma. È semplicemente valido JavaScript: D
$
come viene usato. Se stai chiedendo dell'implementazione stessa, $
è una funzione che riceve un valore x
e restituisce una nuova funzione k => ...
. Guardando il corpo della funzione restituita, vediamo k (x)
quindi che k
dobbiamo anche essere una funzione, e qualunque sia il risultato di k (x)
viene rimandato a $ (...)
, che sappiamo restituisce un'altra k => ...
, e continua ... Se sei ancora rimanere bloccato, fammi sapere.
abc(1,2,3)
è meno che ideale di abc(1)(2)(3)
. È più difficile ragionare sulla logica del codice ed è difficile leggere la funzione abc ed è più difficile leggere la chiamata della funzione. Prima avevi solo bisogno di sapere cosa fa abc, ora non sei sicuro di quali funzioni senza nome abc stia tornando e due volte.
Comprendere le sintassi disponibili delle funzioni freccia ti darà una comprensione del comportamento che stanno introducendo quando sono "incatenati" come negli esempi che hai fornito.
Quando una funzione freccia viene scritta senza parentesi graffe a blocchi, con o senza parametri multipli, l'espressione che costituisce il corpo della funzione viene restituita implicitamente . Nel tuo esempio, quell'espressione è un'altra funzione freccia.
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()
} | | }
} | } | }
Un altro vantaggio della scrittura di funzioni anonime utilizzando la sintassi della freccia è che sono legati lessicamente all'ambito in cui sono definiti. Da "Funzioni freccia" su MDN :
Un'espressione di funzione freccia ha una sintassi più breve rispetto alle espressioni di funzione e associa lessicamente questo valore. Le funzioni freccia sono sempre anonime .
Questo è particolarmente pertinente nel tuo esempio considerando che è preso da a reactjsapplicazione. Come sottolineato da @naomik, in React si accede spesso alle funzioni membro di un componente usando this
. Per esempio:
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) | }
Un consiglio generale, se vieni confuso da una qualsiasi delle nuove sintassi JS e da come verrà compilata, puoi controllare babel . Ad esempio, copiando il codice in babel e selezionando il preset es2015 si otterrà un output come questo
handleChange = function handleChange(field) {
return function (e) {
e.preventDefault();
// Do something here
};
};
Pensala in questo modo, ogni volta che vedi una freccia, la sostituisci con function
. function parameters
sono definiti prima della freccia.
Quindi nel tuo esempio:
field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}
e poi insieme:
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
.
Breve e semplice 🎈
È una funzione che restituisce un'altra funzione scritta in modo breve.
const handleChange = field => e => {
e.preventDefault()
// Do something here
}
// is equal to
function handleChange(field) {
return function(e) {
e.preventDefault()
// Do something here
}
}
Perché le persone lo fanno ❓
Ti sei trovato di fronte quando hai bisogno di scrivere una funzione che può essere personalizzata? Oppure devi scrivere una funzione di callback con parametri fissi (argomenti), ma devi passare più variabili alla funzione evitando le variabili globali? Se la tua risposta " sì ", allora è il modo in cui farlo.
Ad esempio abbiamo un button
callback con onClick. E dobbiamo passare id
alla funzione, ma onClick
accetta solo un parametro event
, non possiamo passare parametri extra all'interno di questo:
const handleClick = (event, id) {
event.preventDefault()
// Dispatch some delete action by passing record id
}
Non funzionerà!
Pertanto creiamo una funzione che restituirà altre funzioni con il proprio ambito di variabili senza variabili globali, poiché le variabili globali sono malvagie 😈.
Sotto la funzione handleClick(props.id)}
verrà chiamata e restituirà una funzione e avrà id
nel suo ambito! Indipendentemente da quante volte verrà premuto, gli ID non si influenzeranno o si cambieranno a vicenda, sono totalmente isolati.
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
)
L'esempio nella tua domanda è quello di a curried function
che usa arrow function
e ha un implicit return
come primo argomento.
La funzione freccia associa in modo lessicale ciò, cioè non hanno il proprio this
argomento ma prendono il this
valore dall'ambito che lo racchiude
Un equivalente del codice sopra sarebbe
const handleChange = (field) {
return function(e) {
e.preventDefault();
/// Do something here
}.bind(this);
}.bind(this);
Un'altra cosa da notare sul tuo esempio è che definisci handleChange
una const o una funzione. Probabilmente lo stai usando come parte di un metodo di classe e usa aclass fields syntax
quindi invece di associare direttamente la funzione esterna, la assoceresti al costruttore della classe
class Something{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(field) {
return function(e) {
e.preventDefault();
// do something
}
}
}
Un'altra cosa da notare nell'esempio è la differenza tra ritorno implicito ed esplicito.
const abc = (field) => field * 2;
Sopra è un esempio di ritorno implicito, ad es. prende il campo valore come argomento e restituisce il risultato field*2
che specifica esplicitamente la funzione da restituire
Per un ritorno esplicito si direbbe esplicitamente al metodo di restituire il valore
const abc = () => { return field*2; }
Un'altra cosa da notare sulle funzioni delle frecce è che non hanno le proprie arguments
ma ereditano anche quelle dal campo dei genitori.
Ad esempio se si definisce semplicemente una funzione freccia come
const handleChange = () => {
console.log(arguments) // would give an error on running since arguments in undefined
}
In alternativa, le funzioni freccia forniscono i restanti parametri che è possibile utilizzare
const handleChange = (...args) => {
console.log(args);
}
Potrebbe non essere del tutto correlato, ma poiché la domanda menzionata reagisce usando il caso (e continuo a imbattermi in questo thread SO): c'è un aspetto importante della funzione doppia freccia che non è esplicitamente menzionato qui. Solo la "prima" freccia (funzione) viene nominata (e quindi "distinguibile" dal tempo di esecuzione), tutte le seguenti frecce sono anonime e dal punto di vista di React contano come "nuovo" oggetto su ogni rendering.
Pertanto, la funzione doppia freccia farà sì che ogni PureComponent venga reso nuovamente.
Esempio
Hai un componente padre con un gestore modifiche come:
handleChange = task => event => { ... operations which uses both task and event... };
e con un rendering come:
{
tasks.map(task => <MyTask handleChange={this.handleChange(task)}/>
}
handleChange quindi utilizzato su un input o clic. E tutto questo funziona e sembra molto bello. MA significa che qualsiasi modifica che provocherà il rendering del genitore (come un cambiamento di stato completamente non correlato) restituirà anche TUTTO il tuo MyTask, anche se sono PureComponents.
Questo può essere alleviato in molti modi come passare la freccia "più esterna" e l'oggetto con cui lo si nutre o scrivere una funzione di aggiornamento personalizzata o tornare alle basi come scrivere funzioni con nome (e associarlo manualmente ...)