Я читав купу react
коду і бачу такі речі, які я не розумію:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Я читав купу react
коду і бачу такі речі, які я не розумію:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Відповіді:
Спочатку вивчіть цю функцію з двома параметрами…
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">
$
був використаний для демонстрації концепції, але ви можете назвати її все, що завгодно. За збігом , але абсолютно не пов'язані, $
вже використовуються в популярних бібліотек , як JQuery, де $
є свого роду глобальної точки входу цілої бібліотеки функцій. Я думаю, що його використовували і в інших. Ще одне, що ви побачите, - це _
популяризація в бібліотеках, як підкреслення та подача. Жоден символ не є більш значимим, ніж інший; ви присвоюєте значення для вашої програми. Це просто дійсний JavaScript: D
$
, як він використовується. Якщо ви запитуєте про саму реалізацію, $
це функція, яка отримує значення x
і повертає нову функцію k => ...
. Дивлячись на тіло повернутої функції, ми бачимо, k (x)
тому ми знаємо, що k
також повинна бути функцією, і який би результат k (x)
був повернутий назад, до $ (...)
якого ми знаємо, повертається інший k => ...
, і він продовжує ... Якщо ти ще застрягаючи, дайте мені знати.
abc(1,2,3)
менше, ніж ідеал, ніж abc(1)(2)(3)
. Важче міркувати про логіку коду, і важко читати функцію abc, і важче читати виклик функції. Перш ніж вам потрібно було лише знати, що робить abc, тепер ви не впевнені, які функції безіменних функцій abc повертає, і двічі.
Розуміння наявних синтаксисів функцій стрілок дасть вам зрозуміти, яку поведінку вони впроваджують, коли вони «прикуті», як у наведених вами прикладах.
Коли функція стрілки записується без блокових дужок, з чи без декількох параметрів, вираз, що становить тіло функції, повертається неявно . У вашому прикладі це вираження - це ще одна функція стрілки.
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 :
Вираз функції стрілки має більш короткий синтаксис по порівнянні з функціональними виразами і лексичний пов'язує це значення. Функції стрілок завжди анонімні .
Це особливо доречно у вашому прикладі, враховуючи, що воно взято з а reactjsзастосування. Як зазначає @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) | }
Загальна порада, якщо вас плутає будь-який із нових синтаксисів JS і як він буде складатись, ви можете перевірити бабету . Наприклад, копіювання коду в babel і вибір попередньої настройки es2015 дасть такий вихід
handleChange = function handleChange(field) {
return function (e) {
e.preventDefault();
// Do something here
};
};
Подумайте про це так, кожного разу, коли бачите стрілку, замінюєте її 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
this
.
Короткий і простий 🎈
Це функція, яка повертає іншу функцію, написану коротко.
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
)
Приклад у вашому запитанні є прикладом, 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);
}
Це може бути не зовсім пов’язаним, але оскільки згаданий питання реагує, використовує випадок (і я постійно натикаюся на цю тему SO): Є один важливий аспект функції подвійної стрілки, про який явно не йдеться. Тільки "перша" стрілка (функція) отримує ім'я (і, таким чином, "відрізняється" за часом виконання), будь-які наступні стрілки є анонімними та з точки зору Реагування вважаються "новим" об'єктом на кожному візуалізації.
Таким чином, функція подвійної стрілки призведе до того, що будь-який PureComponent буде рендерироваться весь час.
Приклад
У вас є батьківський компонент із обробником змін як:
handleChange = task => event => { ... operations which uses both task and event... };
і з візуалізацією, як:
{
tasks.map(task => <MyTask handleChange={this.handleChange(task)}/>
}
потім обробляється зміна, яка використовується при вході або натисканні. І це все працює і виглядає дуже приємно. Але це означає, що будь-яка зміна, яка призведе до повторного відображення батька (як абсолютно незв'язана зміна стану), також повторно подасть ВСІ ваші MyTask, навіть якщо вони є PureComponents.
Це може бути полегшено багатьма способами, такими як передача стрілки 'outmost' та об'єкт, яким ви будете подавати її, або написання користувацької функції mustUpdate або повернення до основ, таких як написання іменованих функцій (та прив'язування цього вручну ...)