Что означают множественные функции стрелок в javascript?

Я читал кучу react код, и я вижу такие вещи, которые я не понимаю:

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

9 ответов

Это функция карри

Сначала рассмотрим эту функцию с двумя параметрами...

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

Вот оно снова в карри...

let add = x => y => x + y;

Вот тот же код1 без функций стрелок...

let add = function (x) {
  return function (y) {
    return x + y;
  };
};

Сконцентрируйсяreturn

Это может помочь визуализировать это по-другому. Мы знаем, что функции стрелок работают следующим образом - давайте обратим особое внимание на возвращаемое значение.

let f = someParam => returnValue

Так что наши add Функция возвращает функцию - мы можем использовать скобки для большей ясности. Текст, выделенный жирным шрифтом, является возвращаемым значением нашей функции. add

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

Другими словами add некоторого числа возвращает функцию

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

Вызов карри функций

Таким образом, чтобы использовать нашу функцию карри, мы должны вызвать ее немного по-другому...

add(2)(3); // returns 5

Это потому, что первый (внешний) вызов функции возвращает вторую (внутреннюю) функцию. Только после вызова второй функции мы действительно получаем результат. Это станет более очевидным, если мы разделим звонки на две строки...

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

Применяя наше новое понимание к вашему коду

по теме: "В чем разница между связыванием, частичным применением и каррированием?"

Хорошо, теперь, когда мы понимаем, как это работает, давайте посмотрим на ваш код

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

Мы начнем с представления его без использования функций стрелок...

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

Однако, потому что функции стрелок лексически связывают this, на самом деле это будет выглядеть больше так...

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

Может быть, теперь мы можем видеть, что это делает более четко. handleChange Функция создает функцию для указанного field, Это удобный метод React, потому что вам необходимо настроить своих собственных слушателей на каждом входе, чтобы обновить состояние ваших приложений. Используя handleChange функция, мы можем устранить весь дублированный код, который приведет к настройке change слушатели для каждого поля.

Здорово!


+1 Здесь мне не нужно было лексически связывать this потому что оригинал add Функция не использует никакого контекста, поэтому не важно сохранять ее в этом случае.

Кратко и просто

Это функция, которая возвращает другую функцию, написанную кратко.

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

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

Почему люди это делают?

Сталкивались ли вы когда вам нужно написать функцию, которую можно настроить? Или вам нужно написать функцию обратного вызова, которая имеет фиксированные параметры (аргументы), но вам нужно передать больше переменных в функцию, но избегая глобальных переменных? Если ваш ответ "да", то это способ, как это сделать.

Например, у нас есть button с обратным вызовом onClick. И нам нужно пройти id к функции, но onClick принимает только один параметр event, мы не можем передать дополнительные параметры, как это:

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

Она не будет работать!

Поэтому мы создаем функцию, которая будет возвращать другую функцию с собственным диапазоном переменных без каких-либо глобальных переменных, потому что глобальные переменные являются злом.

Ниже функции handleClick(props.id)} будет вызван и вернет функцию, и она будет иметь id в своем объеме! Независимо от того, сколько раз он будет нажат, идентификаторы не будут влиять или изменять друг друга, они полностью изолированы.

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
)

Общий совет: если вас смущает какой-либо новый синтаксис JS и способ его компиляции, вы можете проверить babel. Например, копирование кода в babel и выбор предустановки es2015 приведут к следующему выводу

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

галдеж

Понимание доступных синтаксисов функций стрелок даст вам представление о том, какое поведение они вводят, когда они "связаны", как в приведенных вами примерах.

Когда функция стрелки написана без фигурных скобок, с несколькими параметрами или без них, неявно возвращается выражение, которое составляет тело функции. В вашем примере это выражение является еще одной функцией стрелки.

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

Другое преимущество написания анонимных функций с использованием синтаксиса стрелки заключается в том, что они лексически связаны с областью, в которой они определены. Из "Стрелка функций" на MDN:

Выражение функции со стрелкой имеет более короткий синтаксис по сравнению с выражениями функции и лексически связывает значение this. Функции стрелок всегда анонимны.

Это особенно уместно в вашем примере, учитывая, что оно взято из приложения actjs. Как отмечает @naomik, в React вы часто получаете доступ к функциям-членам компонента, используя this, Например:

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

Думайте об этом так, каждый раз, когда вы видите стрелку, вы заменяете ее на function,
function parameters определяются перед стрелкой.
Итак, в вашем примере:

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

а потом вместе:

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

Это может быть не полностью связано, но поскольку упомянутый вопрос использует case (и я продолжаю натыкаться на этот поток SO): есть один важный аспект функции двойной стрелки, который здесь явно не упоминается. Только "первая" стрелка (функция) получает имя (и, следовательно, "различима" во время выполнения), любые последующие стрелки являются анонимными и с точки зрения React считаются "новым" объектом при каждой визуализации.

Таким образом, функция двойной стрелки приведет к постоянному повторному рендерингу любого PureComponent.

пример

У вас есть родительский компонент с обработчиком изменений как:

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

и с рендером вроде:

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

handleChange затем используется для ввода или щелчка. И все это работает и выглядит очень красиво. НО это означает, что любое изменение, которое приведет к повторному рендерингу родителя (например, совершенно несвязанное изменение состояния), также будет повторно отображать ВСЕ ваши MyTask, даже если они являются PureComponents.

Это можно смягчить многими способами, такими как передача "крайней" стрелки и объекта, которым вы ее скармливаете, или написания пользовательской функции shouldUpdate, или возврата к основам, таким как написание именованных функций (и привязка this вручную...)

Пример в вашем вопросе является примером curried function который использует arrow function и имеет implicit return за первый аргумент.

Стрелка функции лексически связывают это, то есть они не имеют своих собственных this аргумент, но принять this значение из окружающей области

Эквивалентом приведенного выше кода будет

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

Еще один момент, который стоит отметить в вашем примере: handleChange как const или функция. Возможно, вы используете его как часть метода класса, и он использует class fields syntax

поэтому вместо того, чтобы связывать внешнюю функцию напрямую, вы должны связать ее в конструкторе класса

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

Еще одна вещь, которую следует отметить в примере, - это разница между неявным и явным возвратом.

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

Выше приведен пример неявного возврата т.е. он принимает поле значения в качестве аргумента и возвращает результат field*2 который явно указывает функцию для возврата

Для явного возврата вы бы явно указали методу вернуть значение

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

Еще одна вещь, которую следует отметить о функциях стрелок, это то, что они не имеют своих собственных arguments но унаследуйте это и от родителей.

Например, если вы просто определите функцию стрелки, как

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

В качестве альтернативы функции стрелки предоставляют остальные параметры, которые вы можете использовать

const handleChange = (...args) => {
   console.log(args);
}
 var handleChange = field => e => {
  e.preventDefault();
  /// Do something here
 }

В Ecma5 переведите:

 "use strict";

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

 var f = function(x,y) { return x+y }
 var g = function(x) { return function(y) { return x+y }}

 f: (T x T) -> T
 g: T -> T -> T

T: универсальный тип

Это изменит тип функции, но результата нет.

Множественные стрелочные функции в JavaScript представляют собой цепочку функций, в которой выходные данные одной функции используются в качестве входных данных следующей функции и так далее. например:

      const add = (x) => (y) => x + y;
const multiply = (x) => (y) => x * y;

const result = add(2)(3); // 5
const finalResult = multiply(result)(4); // 20

В приведенном выше примереaddиmultiply— это стрелочные функции, которые принимают один параметр и возвращают другую стрелочную функцию. Первая стрелочная функция принимает число и возвращает другую стрелочную функцию, которая принимает другое число и возвращает сумму и . Вторая стрелочная функция принимает число и возвращает другую стрелочную функцию, которая принимает другое число и возвращает произведениеxиy.

Чтобы использовать функции, вы можете объединить их вместе, вызвав одну функцию с ее параметром, а затем немедленно вызвав возвращаемую функцию со следующим параметром. В приведенном выше примереresultэто результатadd(2)(3)который5, иfinalResultэто результатmultiply(result)(4)который20.

Другие вопросы по тегам