Czytam sporo react
kodu i widzę takie rzeczy, których nie rozumiem:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Czytam sporo react
kodu i widzę takie rzeczy, których nie rozumiem:
handleChange = field => e => {
e.preventDefault();
/// Do something here
}
Odpowiedzi:
To jest funkcja curry
Najpierw sprawdź tę funkcję za pomocą dwóch parametrów…
const add = (x, y) => x + y
add(2, 3) //=> 5
Tutaj znów jest w curry…
const add = x => y => x + y
Oto ten sam 1 kod bez funkcji strzałek…
const add = function (x) {
return function (y) {
return x + y
}
}
Skupić się na return
Może to pomóc zobrazować to w inny sposób. Wiemy, że funkcje strzałek działają w ten sposób - zwróćmy szczególną uwagę na wartość zwracaną .
const f = someParam => returnValue
Nasza add
funkcja zwraca więc funkcję - możemy użyć nawiasów dla większej przejrzystości. Pogrubiony tekst jest zwracana wartość naszej funkcjiadd
const add = x => (y => x + y)
Innymi słowy, add
pewna liczba zwraca funkcję
add(2) // returns (y => 2 + y)
Wywoływanie funkcji curry
Aby więc skorzystać z naszej funkcji curry, musimy ją nazwać nieco inaczej…
add(2)(3) // returns 5
Jest tak, ponieważ pierwsze (zewnętrzne) wywołanie funkcji zwraca drugą (wewnętrzną) funkcję. Dopiero po wywołaniu drugiej funkcji uzyskujemy wynik. Jest to bardziej widoczne, jeśli rozdzielimy połączenia na dwóch liniach…
const add2 = add(2) // returns function(y) { return 2 + y }
add2(3) // returns 5
Zastosowanie naszego nowego zrozumienia do twojego kodu
powiązane: „Jaka jest różnica między wiązaniem, częściowym stosowaniem a curry?”
OK, teraz, gdy rozumiemy, jak to działa, spójrzmy na twój kod
handleChange = field => e => {
e.preventDefault()
/// Do something here
}
Zaczniemy od przedstawienia go bez użycia funkcji strzałek…
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
};
};
Ponieważ jednak funkcje strzałek wiążą się leksykalnie this
, w rzeczywistości wyglądałoby to mniej więcej tak…
handleChange = function(field) {
return function(e) {
e.preventDefault()
// Do something here
// return ...
}.bind(this)
}.bind(this)
Może teraz możemy zobaczyć, co to robi wyraźniej. handleChange
Funkcji tworzy funkcję określona field
. Jest to przydatna technika React, ponieważ musisz skonfigurować własne detektory na każdym wejściu, aby zaktualizować stan aplikacji. Korzystając z tej handleChange
funkcji, możemy wyeliminować cały zduplikowany kod, który spowodowałby skonfigurowanie change
detektorów dla każdego pola. Fajne!
1 Tutaj nie musiałem wiązać leksykalnie, this
ponieważ add
funkcja oryginalna nie używa żadnego kontekstu, więc nie jest ważne, aby zachować ją w tym przypadku.
Jeszcze więcej strzał
W razie potrzeby można sekwencjonować więcej niż dwie funkcje strzałek -
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
Funkcje curry potrafią zaskakiwać. Poniżej widzimy $
definicję funkcji curry z dwoma parametrami, ale w witrynie wywoławczej wydaje się, że możemy podać dowolną liczbę argumentów. Curry to abstrakcja arity -
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
Częściowe zastosowanie
Częściowe zastosowanie jest powiązaną koncepcją. Pozwala nam częściowo zastosować funkcje podobne do curry, z tą różnicą, że funkcja nie musi być zdefiniowana w formie curry -
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
Oto działające demo, z partial
którym możesz grać we własnej przeglądarce -
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">
$
został użyty do demonstracji koncepcji, ale możesz nazwać ją, jak chcesz. Przypadkowo jednak zupełnie niepowiązane, $
nie zostały wykorzystane w popularnych bibliotek takich jak jQuery, gdzie $
jest rodzajem globalnego punktu wejścia do całej biblioteki funkcji. Myślę, że był używany także w innych. Kolejnym, który zobaczysz, jest _
spopularyzowany w bibliotekach takich jak podkreślenie i lodash. Żaden symbol nie ma większego znaczenia niż inny; Państwo przypisać znaczenie dla Twojego programu. To jest po prostu poprawny JavaScript: D
$
, patrząc na to, jak jest używany. Jeśli pytasz o samą implementację, $
to funkcja, która otrzymuje wartość x
i zwraca nową funkcję k => ...
. Patrząc na treść zwróconej funkcji, widzimy, k (x)
więc wiemy, że k
musi to być również funkcja, i cokolwiek wynik tego k (x)
zostanie przywrócone $ (...)
, co, jak wiemy, zwraca inną k => ...
, i tak dalej ... Jeśli nadal jesteś utknąć, daj mi znać.
abc(1,2,3)
jest mniej niż idealny niż abc(1)(2)(3)
. Trudniej jest zrozumieć logikę kodu i trudniej odczytać funkcję abc, a trudniej odczytać wywołanie funkcji. Wcześniej musieliście tylko wiedzieć, co robi abc, teraz nie jesteście pewni, jakie funkcje bezimienne zwraca abc, i to dwukrotnie.
Zrozumienie dostępnych składni funkcji strzałek pozwoli ci zrozumieć, jakie zachowania wprowadzają, gdy są „powiązane”, jak w podanych przez ciebie przykładach.
Gdy funkcja strzałki jest zapisywana bez nawiasów blokowych, z wieloma parametrami lub bez nich, wyrażenie, które stanowi ciało funkcji, jest domyślnie zwracane. W twoim przykładzie to wyrażenie jest kolejną funkcją strzałki.
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()
} | | }
} | } | }
Kolejną zaletą pisania anonimowych funkcji przy użyciu składni strzałki jest to, że są one powiązane leksykalnie z zakresem, w którym są zdefiniowane. Z „Funkcje strzałek” w MDN :
Wyrażenie funkcja strzałka ma krótszą składnię porównaniu do wyrażeń funkcyjnych i leksykalnie wiąże tę wartość. Funkcje strzałek są zawsze anonimowe .
Jest to szczególnie istotne w twoim przykładzie, biorąc pod uwagę, że pochodzi z Reagujepodanie. Jak wskazał @naomik, w React często uzyskuje się dostęp do funkcji członka komponentu za pomocą this
. Na przykład:
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) | }
Ogólna wskazówka: jeśli pomyli Cię jakaś nowa składnia JS i sposób jej kompilacji, możesz sprawdzić babel . Na przykład skopiowanie kodu w Babel i wybranie ustawienia wstępnego es2015 da takie wyjście
handleChange = function handleChange(field) {
return function (e) {
e.preventDefault();
// Do something here
};
};
Pomyśl o tym w ten sposób, za każdym razem, gdy zobaczysz strzałkę, zamień ją na function
. function parameters
są zdefiniowane przed strzałką.
W twoim przykładzie:
field => // function(field){}
e => { e.preventDefault(); } // function(e){e.preventDefault();}
a następnie razem:
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
.
Krótkie i proste 🎈
Jest to funkcja, która zwraca inną funkcję napisaną w skrócie.
const handleChange = field => e => {
e.preventDefault()
// Do something here
}
// is equal to
function handleChange(field) {
return function(e) {
e.preventDefault()
// Do something here
}
}
Dlaczego ludzie to robią ❓
Czy napotkałeś, kiedy musisz napisać funkcję, którą można dostosować? A może musisz napisać funkcję zwrotną, która ma stałe parametry (argumenty), ale musisz przekazać więcej zmiennych do funkcji, ale unikać zmiennych globalnych? Jeśli Twoja odpowiedź brzmi „ tak ”, to po prostu jak to zrobić.
Na przykład mamy button
wywołanie zwrotne onClick. I musimy przejść id
do funkcji, ale onClick
akceptuje tylko jeden parametr event
, nie możemy przekazać dodatkowych parametrów w ten sposób:
const handleClick = (event, id) {
event.preventDefault()
// Dispatch some delete action by passing record id
}
To nie zadziała!
Dlatego tworzymy funkcję, która zwróci inną funkcję z własnym zakresem zmiennych bez zmiennych globalnych, ponieważ zmienne globalne są złe 😈.
Poniżej funkcja handleClick(props.id)}
zostanie wywołana i zwróci funkcję, która będzie miała id
swój zasięg! Bez względu na to, ile razy zostanie wciśnięty, identyfikatory nie będą się zmieniać ani zmieniać, są całkowicie odizolowane.
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
)
Przykładem twojego pytania jest przykład, curried function
który korzysta z arrow function
i maimplicit return
za pierwszy argument.
Funkcja strzałki wiąże to leksykalnie, tzn. Nie ma własnego this
argumentu, ale bierze this
wartość z otaczającego zakresu
Odpowiednikiem powyższego kodu byłoby
const handleChange = (field) {
return function(e) {
e.preventDefault();
/// Do something here
}.bind(this);
}.bind(this);
Jeszcze jedną rzeczą wartą odnotowania w twoim przykładzie jest to, że definiują handleChange
jako const lub funkcję. Prawdopodobnie używasz go jako części metody klasowej i używa onclass fields syntax
więc zamiast bezpośredniego wiązania zewnętrznej funkcji, powiążemy ją w konstruktorze klasy
class Something{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(field) {
return function(e) {
e.preventDefault();
// do something
}
}
}
Inną rzeczą, na którą należy zwrócić uwagę w tym przykładzie, jest różnica między zwrotem niejawnym i jawnym.
const abc = (field) => field * 2;
Powyżej znajduje się przykład niejawnego zwrotu, tj. przyjmuje pole wartości jako argument i zwraca wynikfield*2
który wyraźnie określa funkcję do zwrócenia
Aby uzyskać wyraźny zwrot, należy wyraźnie wskazać metodę zwrotu wartości
const abc = () => { return field*2; }
Inną rzeczą, na którą należy zwrócić uwagę w przypadku funkcji strzałek, jest to, że nie mają one własnych, arguments
ale dziedziczą to również z zakresu rodziców.
Na przykład, jeśli zdefiniujesz funkcję strzałki, np
const handleChange = () => {
console.log(arguments) // would give an error on running since arguments in undefined
}
Jako alternatywne funkcje strzałek podaj pozostałe parametry, których możesz użyć
const handleChange = (...args) => {
console.log(args);
}
Może nie jest to całkowicie powiązane, ale ponieważ wspomniane pytanie reaguje na przypadki (i ciągle wpadam na ten wątek SO): Istnieje jeden ważny aspekt funkcji podwójnej strzałki, który nie jest tutaj wyraźnie wymieniony. Tylko pierwsza „strzałka” (funkcja) zostaje nazwana (a zatem „rozróżnialna” w czasie wykonywania), kolejne strzałki są anonimowe iz punktu widzenia React są liczone jako „nowy” obiekt na każdym renderowaniu.
W ten sposób funkcja podwójnej strzałki spowoduje, że dowolny PureComponent będzie się ciągle powtarzał.
Przykład
Masz komponent nadrzędny z modułem obsługi zmian, takim jak:
handleChange = task => event => { ... operations which uses both task and event... };
oraz z renderowaniem takim jak:
{
tasks.map(task => <MyTask handleChange={this.handleChange(task)}/>
}
handleChange następnie używany na wejściu lub kliknięciu. I to wszystko działa i wygląda bardzo ładnie. ALE oznacza to, że każda zmiana, która spowoduje, że rodzic zrenderuje (jak zupełnie niezwiązana zmiana stanu), również zrenderuje CAŁĄ twoją MyTask, nawet jeśli są PureComponents.
Można to złagodzić na wiele sposobów, takich jak przekazywanie strzałki „najbardziej wysuniętej” i obiektu, którym chcesz ją nakarmić, lub pisanie niestandardowej funkcji powinno aktualizować lub powrót do podstaw, takich jak pisanie nazwanych funkcji (i wiązanie jej ręcznie ...)