¿Qué significan las funciones de flecha múltiple en javascript?

472

He estado leyendo un montón de reactcódigo y veo cosas como esta que no entiendo:

handleChange = field => e => {
  e.preventDefault();
  /// Do something here
}
jhamm
fuente
11
Solo por diversión, Kyle Simpson puso todos los caminos de decisión para las flechas en este diagrama de flujo . Fuente: su comentario en una publicación de blog de Mozilla Hacks titulada ES6 In Depth: Arrow functions
gfullam
Como hay grandes respuestas y ahora una recompensa. ¿Puede explicar lo que no entiende que las respuestas a continuación no cubren?
Michael Warner
55
La URL para el diagrama de flujo de funciones de flecha ahora está rota porque hay una nueva edición del libro. La URL de trabajo está en raw.githubusercontent.com/getify/You-Dont-Know-JS/1st-ed/…
Dhiraj Gupta

Respuestas:

833

Esa es una función curry

Primero, examine esta función con dos parámetros ...

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

Aquí está de nuevo en forma de curry ...

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

Aquí está el mismo código 1 sin funciones de flecha ...

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

Concentrarse en return

Puede ser útil visualizarlo de otra manera. Sabemos que las funciones de flecha funcionan así: prestemos especial atención al valor de retorno .

const f = someParam => returnValue

Entonces, nuestra addfunción devuelve una función : podemos usar paréntesis para mayor claridad. El texto en negrita es el valor de retorno de nuestra función.add

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

En otras palabras, addalgún número devuelve una función

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

Llamar a funciones curry

Entonces, para usar nuestra función de curry, tenemos que llamarlo un poco diferente ...

add(2)(3)  // returns 5

Esto se debe a que la primera llamada a la función (externa) devuelve una segunda función (interna). Solo después de llamar a la segunda función, realmente obtenemos el resultado. Esto es más evidente si separamos las llamadas en dos líneas ...

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

Aplicando nuestra nueva comprensión a su código

relacionado: "¿Cuál es la diferencia entre encuadernación, aplicación parcial y curry?"

Bien, ahora que entendemos cómo funciona, veamos tu código

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

Comenzaremos por representarlo sin usar las funciones de flecha ...

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

Sin embargo, debido a que las funciones de flecha se unen léxicamente this, en realidad se vería más así ...

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

Quizás ahora podamos ver lo que esto está haciendo más claramente. La handleChangefunción está creando una función para un determinado field. Esta es una técnica práctica de React porque debes configurar tus propios oyentes en cada entrada para actualizar el estado de tus aplicaciones. Al usar la handleChangefunción, podemos eliminar todo el código duplicado que resultaría en la configuración de changeescuchas para cada campo. ¡Frio!

1 Aquí no tuve que vincular léxicamente thisporque la addfunción original no usa ningún contexto, por lo que no es importante preservarla en este caso.


Aún más flechas

Se pueden secuenciar más de dos funciones de flecha, si es necesario:

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

Las funciones curry son capaces de sorprender. A continuación vemos $definido como una función currificada con dos parámetros, pero en el sitio de la llamada, parece que podemos suministrar cualquier número de argumentos. El curry es la abstracción de la aridad .

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

Aplicación parcial

La aplicación parcial es un concepto relacionado. Nos permite aplicar parcialmente las funciones, de forma similar al currículum, excepto que la función no tiene que definirse en forma de currículum.

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

Aquí hay una demostración funcional con la partialque puedes jugar en tu propio navegador:

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

Gracias
fuente
2
¡Esto es excepcional! Sin embargo, ¿con qué frecuencia alguien asigna realmente el '$'? ¿O es un alias para esto en reaccionar? Perdone mi ignorancia en el último, solo curiosidad porque no veo un símbolo recibiendo una tarea con demasiada frecuencia en otros idiomas.
Caperneoignis
77
@Caperneoignis $se usó para demostrar el concepto, pero puedes nombrarlo como quieras. Casualmente pero completamente sin relación, $ se ha utilizado en bibliotecas populares como jQuery, donde $es una especie de punto de entrada global a toda la biblioteca de funciones. Creo que también se ha usado en otros. Otro que verá es _, popularizado en bibliotecas como subrayado y lodash. Ningún símbolo es más significativo que otro; Usted asigna el significado de su programa. Es simplemente JavaScript válido: D
Gracias
1
Santo Frijoli, buena respuesta. desearía que el
operador
2
@Blake Puede obtener una mejor comprensión de $cómo se usa. Si está preguntando acerca de la implementación en sí, $es una función que recibe un valor xy devuelve una nueva función k => .... Al observar el cuerpo de la función devuelta, vemos k (x)que sabemos kque también debe ser una función, y cualquiera que sea el resultado k (x)se vuelve a poner $ (...), lo que sabemos devuelve otra k => ..., y continúa ... Si todavía quedarse atascado, hágamelo saber.
Gracias
2
mientras que esta respuesta explica cómo funciona y qué patrones hay con esta técnica. Siento que no hay nada específico sobre por qué esta es realmente una mejor solución en cualquier escenario. En qué situación, abc(1,2,3)es menos que ideal que abc(1)(2)(3). Es más difícil razonar sobre la lógica del código y es difícil leer la función abc y es más difícil leer la llamada a la función. Antes solo necesitabas saber qué hace abc, ahora no estás seguro de qué funciones sin nombre abc está volviendo a hacer, y dos veces más.
Muhammad Umer
57

Comprender las sintaxis disponibles de las funciones de flecha le dará una idea de qué comportamiento están introduciendo cuando están "encadenados", como en los ejemplos que proporcionó.

Cuando una función de flecha se escribe sin llaves de bloque, con o sin múltiples parámetros, la expresión que constituye el cuerpo de la función se devuelve implícitamente . En su ejemplo, esa expresión es otra función de flecha.

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

Otra ventaja de escribir funciones anónimas usando la sintaxis de flecha es que están vinculadas léxicamente al alcance en el que están definidas. Desde 'Funciones de flecha' en MDN :

Una expresión de función de flecha tiene una sintaxis más corta en comparación con expresiones de función y enlaza léxicamente este valor. Las funciones de flecha son siempre anónimas .

Esto es particularmente pertinente en su ejemplo teniendo en cuenta que se toma de un solicitud. Como señaló @naomik, en React a menudo accedes a las funciones miembro de un componente usando this. Por ejemplo:

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
fuente
53

Un consejo general, si se confunde con alguna de las nuevas sintaxis de JS y cómo se compilará, puede verificar babel . Por ejemplo, copiar su código en babel y seleccionar el preajuste es2015 dará una salida como esta

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

Babel

Rahil Ahmad
fuente
42

Piénselo así, cada vez que ve una flecha, la reemplaza por function.
function parametersse definen antes de la flecha.
Entonces en tu ejemplo:

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

y luego juntos:

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

De los documentos :

// 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
fuente
66
No olvides mencionar el límite léxico this.
Gracias
30

Breve y simple 🎈

Es una función que devuelve otra función escrita de forma breve.

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

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

¿Por qué la gente lo hace ?

¿Te has enfrentado cuando necesitas escribir una función que se puede personalizar? ¿O tiene que escribir una función de devolución de llamada que tenga parámetros fijos (argumentos), pero necesita pasar más variables a la función pero evitando las variables globales? Si su respuesta es " ", entonces es la forma de hacerlo.

Por ejemplo, tenemos una buttondevolución de llamada con onClick. Y necesitamos pasar ida la función, pero onClickacepta solo un parámetro event, no podemos pasar parámetros adicionales dentro de este:

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

¡No funcionará!

Por lo tanto, hacemos una función que devolverá otra función con su propio alcance de variables sin ninguna variable global, porque las variables globales son malvadas 😈.

¡Debajo de la función handleClick(props.id)}se llamará y devolverá una función y tendrá idsu alcance! No importa cuántas veces se presione, los identificadores no se afectarán ni cambiarán entre sí, están totalmente aislados.

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
)
sultán
fuente
2

El ejemplo en su pregunta es el de un curried functionque utiliza arrow functiony tiene un implicit returnpara el primer argumento.

La función de flecha enlaza léxicamente esto, es decir, no tienen su propio thisargumento, pero toman el thisvalor del ámbito de aplicación

Un equivalente del código anterior sería

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

Una cosa más a tener en cuenta sobre su ejemplo es que se define handleChangecomo una constante o una función. Probablemente lo esté usando como parte de un método de clase y use unclass fields syntax

así que en lugar de vincular la función externa directamente, la vincularía en el constructor de la clase

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

Otra cosa a tener en cuenta en el ejemplo es la diferencia entre el retorno implícito y explícito.

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

Arriba hay un ejemplo de retorno implícito, es decir. toma el campo de valor como argumento y devuelve el resultado field*2que especifica explícitamente la función a devolver

Para un retorno explícito, le diría explícitamente al método que devuelva el valor

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

Otra cosa a tener en cuenta sobre las funciones de flecha es que no tienen las suyas propias, argumentssino que también las heredan del ámbito de los padres.

Por ejemplo, si solo define una función de flecha como

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

Como alternativa, las funciones de flecha proporcionan los parámetros de descanso que puede usar

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

Puede que no esté totalmente relacionado, pero dado que la pregunta mencionada reacciona usa el caso (y sigo tropezando con este hilo SO): hay un aspecto importante de la función de doble flecha que no se menciona explícitamente aquí. Solo se nombra la 'primera' flecha (función) (y por lo tanto 'distinguible' por el tiempo de ejecución), las flechas siguientes son anónimas y desde el punto de vista de React cuentan como un objeto 'nuevo' en cada render.

Por lo tanto, la función de doble flecha hará que cualquier componente puro se vuelva a procesar todo el tiempo.

Ejemplo

Tiene un componente principal con un controlador de cambios como:

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

y con un render como:

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

handleChange luego se usa en una entrada o haga clic. Y todo esto funciona y se ve muy bien. PERO significa que cualquier cambio que hará que los padres vuelvan a rendirse (como un cambio de estado completamente no relacionado) también volverá a procesar TODA su MyTask, aunque sean Componentes Puros.

Esto puede aliviarse de muchas maneras, como pasar la flecha 'más externa' y el objeto con el que lo alimentaría o escribir una función de actualización personalizada o volver a lo básico, como escribir funciones con nombre (y vincular esto manualmente ...)

Don Kartacs
fuente