Que signifient les fonctions de flèche multiples en javascript?

472

J'ai lu un tas de reactcode et je vois des trucs comme ça que je ne comprends pas:

handleChange = field => e => {
  e.preventDefault();
  /// Do something here
}
jhamm
la source
11
Pour le plaisir, Kyle Simpson a mis tous les chemins de décision pour les flèches dans cet organigramme . Source: Son commentaire sur un article de blog de Mozilla Hacks intitulé ES6 In Depth: Arrow functions
gfullam
Comme il y a d'excellentes réponses et maintenant une prime. Pouvez-vous expliquer ce que vous ne comprenez pas que les réponses ci-dessous ne couvrent pas.
Michael Warner
5
L'URL de l'organigramme des fonctions fléchées est maintenant rompue car il existe une nouvelle édition du livre. L'URL de travail est à raw.githubusercontent.com/getify/You-Dont-Know-JS/1st-ed/…
Dhiraj Gupta

Réponses:

833

C'est une fonction curry

Examinons d'abord cette fonction avec deux paramètres…

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

Le voici à nouveau au curry…

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

Voici le même code 1 sans fonction de flèche…

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

Se concentrer sur return

Cela pourrait aider à le visualiser d'une autre manière. Nous savons que les fonctions fléchées fonctionnent comme ceci - accordons une attention particulière à la valeur de retour .

const f = someParam => returnValue

Notre addfonction renvoie donc une fonction - nous pouvons utiliser des parenthèses pour plus de clarté. Le texte en gras est la valeur de retour de notre fonctionadd

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

En d'autres termes, un addcertain nombre renvoie une fonction

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

Appel de fonctions au curry

Donc pour utiliser notre fonction curry, il faut l'appeler un peu différemment…

add(2)(3)  // returns 5

En effet, le premier appel de fonction (externe) renvoie une seconde fonction (interne). Ce n'est qu'après avoir appelé la deuxième fonction que nous obtenons réellement le résultat. C'est plus évident si nous séparons les appels sur deux lignes…

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

Appliquer notre nouvelle compréhension à votre code

liés: "Quelle est la différence entre la reliure, l'application partielle et le curry?"

OK, maintenant que nous comprenons comment cela fonctionne, regardons votre code

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

On va commencer par le représenter sans utiliser les fonctions flèches…

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

Cependant, comme les fonctions fléchées se lient lexicalement this, cela ressemblerait en fait à ceci…

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

Peut-être que maintenant nous pouvons voir ce que cela fait plus clairement. La handleChangefonction crée une fonction pour un spécifié field. Il s'agit d'une technique React pratique car vous devez configurer vos propres écouteurs sur chaque entrée afin de mettre à jour l'état de vos applications. En utilisant la handleChangefonction, nous pouvons éliminer tout le code dupliqué qui entraînerait la configuration d' changeécouteurs pour chaque champ. Cool!

1 Ici, je n'ai pas eu à lier lexicalement thisparce que la addfonction d' origine n'utilise aucun contexte, il n'est donc pas important de la conserver dans ce cas.


Encore plus de flèches

Plus de deux fonctions flèches peuvent être séquencées, si nécessaire -

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

Les fonctions au curry sont capables de surprendre les choses. Ci-dessous, nous voyons $défini comme une fonction curry avec deux paramètres, mais sur le site de l'appel, il semble que nous pouvons fournir un nombre illimité d'arguments. Le curry est l'abstraction de l' 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

Application partielle

L'application partielle est un concept connexe. Il nous permet d'appliquer partiellement des fonctions, similaires au curry, sauf que la fonction n'a pas à être définie sous forme de 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

Voici une démonstration de travail avec laquelle partialvous pouvez jouer dans votre propre navigateur -

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">

Je vous remercie
la source
2
C'est exceptionnel! Mais à quelle fréquence quelqu'un attribue-t-il réellement le «$»? Ou est-ce un alias pour cela en réaction? Pardonnez mon ignorance sur le dernier, juste curieux parce que je ne vois pas un symbole obtenir une affectation trop souvent dans d'autres langues.
Caperneoignis
7
@Caperneoignis a $été utilisé pour faire une démonstration du concept, mais vous pouvez le nommer comme vous le souhaitez. Par coïncidence mais complètement sans rapport, $ a été utilisé dans des bibliothèques populaires comme jQuery, où $est en quelque sorte le point d'entrée global de la bibliothèque entière de fonctions. Je pense que cela a également été utilisé dans d'autres. Un autre que vous verrez est _, popularisé dans des bibliothèques comme le soulignement et le lodash. Aucun symbole n'a plus de sens qu'un autre; vous attribuez la signification de votre programme. C'est tout simplement du JavaScript valide: D
Merci le
1
saint frijoli, belle réponse. souhaite que l'op accepte
mtyson
2
@Blake Vous pouvez mieux comprendre $en regardant comment il est utilisé. Si vous posez des questions sur l'implémentation elle-même, $est une fonction qui reçoit une valeur xet renvoie une nouvelle fonction k => .... En regardant le corps de la fonction renvoyée, nous voyons k (x)donc nous savons que kdoit également être une fonction, et quel que soit le résultat de celui-ci k (x)est réinjecté $ (...), ce que nous savons en retourne une autre k => ..., et ainsi de suite ... Si vous êtes toujours coincé, faites le moi savoir.
Merci
2
tandis que cette réponse a expliqué comment cela fonctionne et quels modèles il y a avec cette technique. Je pense qu'il n'y a rien de précis sur la raison pour laquelle c'est en fait une meilleure solution dans n'importe quel scénario. Dans quelle situation, abc(1,2,3)est moins qu'idéal que abc(1)(2)(3). Il est plus difficile de raisonner sur la logique du code et il est difficile de lire la fonction abc et il est plus difficile de lire l'appel de fonction. Avant, vous n'aviez besoin que de savoir ce que fait abc, maintenant vous n'êtes pas sûr des fonctions sans nom que retourne abc, et deux fois.
Muhammad Umer
57

Comprendre les syntaxes disponibles des fonctions fléchées vous permettra de comprendre le comportement qu'elles introduisent lorsqu'elles sont "enchaînées" comme dans les exemples que vous avez fournis.

Lorsqu'une fonction de flèche est écrite sans accolades de bloc, avec ou sans plusieurs paramètres, l'expression qui constitue le corps de la fonction est implicitement renvoyée. Dans votre exemple, cette expression est une autre fonction de flèche.

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 autre avantage de l'écriture de fonctions anonymes à l'aide de la syntaxe fléchée est qu'elles sont liées lexicalement à l'étendue dans laquelle elles sont définies. Depuis «Fonctions fléchées» sur MDN :

Une expression de fonction flèche a une syntaxe plus courte par rapport aux expressions de fonction et lie lexicalement cette valeur. Les fonctions fléchées sont toujours anonymes .

Ceci est particulièrement pertinent dans votre exemple étant donné qu'il est tiré d'un application. Comme l'a souligné @naomik, dans React, vous accédez souvent aux fonctions membres d' un composant à l' aide de this. Par exemple:

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)             |  }
sdgluck
la source
53

Une astuce générale, si vous êtes confus par l'une des nouvelles syntaxes JS et comment elles seront compilées, vous pouvez vérifier babel . Par exemple, copier votre code dans babel et sélectionner le préréglage es2015 donnera une sortie comme celle-ci

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

babel

Rahil Ahmad
la source
42

Pensez-y comme ceci, chaque fois que vous voyez une flèche, vous la remplacez par function.
function parameterssont définis avant la flèche.
Donc dans votre exemple:

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

puis ensemble:

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

De la documentation :

// 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
LifeQuery
la source
6
N'oubliez pas de mentionner le lié lexicalement this.
Merci
30

Bref et simple 🎈

C'est une fonction qui retourne une autre fonction écrite de façon courte.

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

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

Pourquoi les gens le font

Avez-vous dû faire face lorsque vous avez besoin d'écrire une fonction qui peut être personnalisée? Ou vous devez écrire une fonction de rappel qui a des paramètres fixes (arguments), mais vous devez passer plus de variables à la fonction mais en évitant les variables globales? Si vous répondez « oui », c'est la façon de le faire.

Par exemple, nous avons un buttonrappel avec onClick. Et nous devons passer idà la fonction, mais onClickn'accepte qu'un seul paramètre event, nous ne pouvons pas passer de paramètres supplémentaires comme ceci:

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

Ça ne marchera pas!

Par conséquent, nous créons une fonction qui retournera une autre fonction avec sa propre portée de variables sans aucune variable globale, car les variables globales sont mauvaises 😈.

Ci-dessous la fonction handleClick(props.id)}sera appelée et renverra une fonction et elle aura iddans sa portée! Peu importe combien de fois il sera pressé, les identifiants n'affecteront ni ne changeront les uns les autres, ils sont totalement isolés.

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
)
sultan
la source
2

L'exemple de votre question est celui curried functionqui utilise arrow functionet a unimplicit return pour le premier argument.

La fonction flèche lie lexicalement ce qui signifie qu'ils n'ont pas leur propre thisargument mais prennent la thisvaleur de la portée englobante

Un équivalent du code ci-dessus serait

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

Une autre chose à noter à propos de votre exemple est que définir handleChangecomme une constante ou une fonction. Vous l'utilisez probablement dans le cadre d'une méthode de classe et il utilise unclass fields syntax

donc au lieu de lier directement la fonction externe, vous la lieriez dans le constructeur de classe

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

Une autre chose à noter dans l'exemple est la différence entre le retour implicite et explicite.

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

Ci-dessus est un exemple de retour implicite ie. il prend le champ de valeur comme argument et renvoie le résultatfield*2 qui spécifie explicitement la fonction à renvoyer

Pour un retour explicite, vous diriez explicitement à la méthode de renvoyer la valeur

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

Une autre chose à noter sur les fonctions fléchées est qu'elles n'ont pas les leurs, argumentsmais héritent également de la portée des parents.

Par exemple, si vous définissez simplement une fonction de flèche comme

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

Comme alternative, les fonctions fléchées fournissent les paramètres de repos que vous pouvez utiliser

const handleChange = (...args) => {
   console.log(args);
}
Shubham Khatri
la source
1

Ce n'est peut-être pas totalement lié, mais puisque la question mentionnée réagit utilise le cas (et je continue de me heurter à ce fil SO): Il y a un aspect important de la fonction de double flèche qui n'est pas explicitement mentionné ici. Seule la `` première '' flèche (fonction) est nommée (et donc `` reconnaissable '' par l'exécution), les flèches suivantes sont anonymes et du point de vue React comptent comme un `` nouvel '' objet sur chaque rendu.

Ainsi, la fonction de double flèche entraînera le rendu permanent de tout composant PureComponent.

Exemple

Vous avez un composant parent avec un gestionnaire de modifications comme:

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

et avec un rendu comme:

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

handleChange puis utilisé sur une entrée ou un clic. Et tout cela fonctionne et est très joli. MAIS cela signifie que tout changement qui entraînera le rendu du parent (comme un changement d'état complètement indépendant) restituera également TOUS vos MyTask également, même s'il s'agit de PureComponents.

Cela peut être atténué de nombreuses façons, comme passer la flèche `` la plus à l'extérieur '' et l'objet avec lequel vous l'alimenter ou écrire une fonction shouldUpdate personnalisée ou revenir à des bases telles que l'écriture de fonctions nommées (et la liaison manuelle de ceci ...)

Don Kartacs
la source