Що означають кілька функцій стрілок у JavaScript?


472

Я читав купу reactкоду і бачу такі речі, які я не розумію:

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

11
Для задоволення, Кайл Сімпсон включив усі шляхи прийняття рішень для стрілок у цю схему потоків . Джерело: Його коментар до публікації в блозі Mozilla Hacks під назвою ES6 In Depth: Функції стрілки
gfullam

Як є чудові відповіді і зараз щедрість. Чи можете ви, будь ласка, детальніше розповісти про те, чого ви не розумієте, що відповіді нижче не стосуються.
Майкл Уорнер

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

Відповіді:


831

Це викривлена ​​функція

Спочатку вивчіть цю функцію з двома параметрами…

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

Прокляті функції здатні дивувати речі. Нижче ми бачимо, що $визначена функція curried з двома параметрами, але на сайті виклику здається, що ми можемо надати будь-яку кількість аргументів. Каррінг це абстракція арность -

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

Часткове застосування

Часткове застосування - це споріднене поняття. Це дозволяє нам частково застосовувати функції, подібні до currying, за винятком того, що функцію не потрібно визначати у витриманому вигляді -

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, де $є свого роду глобальної точки входу цілої бібліотеки функцій. Я думаю, що його використовували і в інших. Ще одне, що ви побачите, - це _популяризація в бібліотеках, як підкреслення та подача. Жоден символ не є більш значимим, ніж інший; ви присвоюєте значення для вашої програми. Це просто дійсний 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 і вибір попередньої настройки 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перший аргумент.

Функція стрілки лексично пов'язує це, тобто вони не мають власного 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): Є один важливий аспект функції подвійної стрілки, про який явно не йдеться. Тільки "перша" стрілка (функція) отримує ім'я (і, таким чином, "відрізняється" за часом виконання), будь-які наступні стрілки є анонімними та з точки зору Реагування вважаються "новим" об'єктом на кожному візуалізації.

Таким чином, функція подвійної стрілки призведе до того, що будь-який PureComponent буде рендерироваться весь час.

Приклад

У вас є батьківський компонент із обробником змін як:

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

і з візуалізацією, як:

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

потім обробляється зміна, яка використовується при вході або натисканні. І це все працює і виглядає дуже приємно. Але це означає, що будь-яка зміна, яка призведе до повторного відображення батька (як абсолютно незв'язана зміна стану), також повторно подасть ВСІ ваші MyTask, навіть якщо вони є PureComponents.

Це може бути полегшено багатьма способами, такими як передача стрілки 'outmost' та об'єкт, яким ви будете подавати її, або написання користувацької функції mustUpdate або повернення до основ, таких як написання іменованих функцій (та прив'язування цього вручну ...)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.