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


472

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

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

11
Ради интереса Кайл Симпсон добавил все пути принятия решений для стрелок в эту блок-схему . Источник: его комментарий к сообщению в блоге Mozilla Hacks, озаглавленному ES6 In Depth: Arrow functions
gfullam

Как есть отличные ответы и теперь щедрость. Не могли бы вы уточнить, что вы не понимаете, ответы ниже не охватывают.
Майкл Уорнер

5
URL для блок-схемы функций со стрелками теперь не работает, потому что есть новая редакция книги. Рабочий URL-адрес: raw.githubusercontent.com/getify/You-Dont-Know-JS/1st-ed/…
Гупта

Ответы:


834

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

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

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

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

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

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

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

Сосредоточиться на return

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

const f = someParam => returnValue

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

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

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

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

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

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

add(2)(3)  // returns 5

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

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

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

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

Частичное применение

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

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

Вот рабочая демонстрация того, что partialвы можете играть в своем браузере -

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


2
Это замечательно! Как часто кто-то фактически назначает '$'? Или это псевдоним для этого в реакции? Прошу прощения за мое невежество на последнем, просто любопытно, потому что я не вижу символа, получающего назначение слишком часто на других языках.
Caperneoignis

7
@Caperneoignis $использовался для демонстрации концепции, но вы можете назвать ее как хотите. По совпадению , но совершенно не связаны, $ уже используются в популярных библиотек , как JQuery, где $является своего рода глобальной точки входа целой библиотеки функций. Я думаю, что это использовалось и в других. Другое, что вы увидите _, популяризируется в библиотеках, таких как подчеркивание и lodash. Ни один символ не является более значимым, чем другой; Вы назначаете значение для вашей программы. Это просто правильный JavaScript: D
Спасибо

1
Святой Фриоли, хороший ответ. Жаль, что оп принял бы
Mtyson

2
@Blake Вы можете лучше понять $, взглянув на то, как оно используется. Если вы спрашиваете о самой реализации, $это функция, которая получает значение xи возвращает новую функцию k => .... Глядя на тело возвращаемой функции, мы видим, k (x)что мы kтакже должны знать, что она должна быть функцией, и что бы ни k (x)возвращалось в результат $ (...), то, что мы знаем, возвращает другое k => ..., и это продолжается ... Если вы все еще застрять, дайте мне знать.
Спасибо

2
в то время как этот ответ объяснил, как это работает и какие модели существуют с этой техникой. Я чувствую, что нет ничего конкретного в том, почему это действительно лучшее решение в любом сценарии. В какой ситуации abc(1,2,3)менее чем идеально abc(1)(2)(3). Труднее рассуждать о логике кода, трудно прочитать функцию abc и сложнее прочитать вызов функции. Раньше вам нужно было только знать, что делает abc, теперь вы не уверены, что делают неназванные функции, которые возвращает abc, и дважды.
Мухаммед Умер

57

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

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

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 :

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

Это особенно уместно в вашем примере, учитывая, что оно взято из применение. Как отмечает @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)             |  }

53

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

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

галдеж


42

Думайте об этом так, каждый раз, когда вы видите стрелку, вы заменяете ее 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

6
Не забудьте упомянуть лексически связанный this.
Спасибо,

30

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

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

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
)

2

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

Функция Arrow связывает это с лексической точки зрения, т. Е. У них нет собственного 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);
}

1

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

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

пример

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

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

и с рендером, как:

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.