Co oznacza wiele funkcji strzałek w javascript?

472

Czytam sporo reactkodu i widzę takie rzeczy, których nie rozumiem:

handleChange = field => e => {
  e.preventDefault();
  /// Do something here
}
jhamm
źródło
11
Dla zabawy Kyle Simpson umieścił wszystkie ścieżki decyzyjne dla strzałek w tym schemacie . Źródło: Jego komentarz do posta na blogu Mozilla Hacks zatytułowanego ES6 In Depth: Funkcje strzałek
gfullam
Ponieważ są świetne odpowiedzi, a teraz nagroda. Czy możesz wyjaśnić, czego nie rozumiesz, czego nie obejmują poniższe odpowiedzi.
Michael Warner,
5
Adres URL schematu blokowego funkcji strzałek jest teraz uszkodzony, ponieważ pojawiło się nowe wydanie książki. Działający adres URL znajduje się na raw.githubusercontent.com/getify/You-Dont-Know-JS/1st-ed/…
Dhiraj Gupta

Odpowiedzi:

831

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 addfunkcja 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, addpewna 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. handleChangeFunkcji 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 handleChangefunkcji, możemy wyeliminować cały zduplikowany kod, który spowodowałby skonfigurowanie changedetektorów dla każdego pola. Fajne!

1 Tutaj nie musiałem wiązać leksykalnie, thisponieważ addfunkcja 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 partialktó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">

Dziękuję Ci
źródło
2
To jest znakomite! Jak często jednak ktoś faktycznie przypisuje „$”? Czy może to jest pseudonim? Przebacz mi moją ignorancję w stosunku do ostatniego, po prostu ciekawy, ponieważ nie widzę, aby symbol zbyt często dostał zadanie w innych językach.
Caperneoignis
7
@Caperneoignis $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
Dziękuję
1
święte frijoli, fajna odpowiedź. żałuję, że
opera
2
@Blake Możesz lepiej zrozumieć $, patrząc na to, jak jest używany. Jeśli pytasz o samą implementację, $to funkcja, która otrzymuje wartość xi zwraca nową funkcję k => .... Patrząc na treść zwróconej funkcji, widzimy, k (x)więc wiemy, że kmusi 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ć.
Dziękuję
2
podczas gdy ta odpowiedź wyjaśniała, jak to działa i jakie są wzorce tej techniki. Wydaje mi się, że nie ma nic konkretnego, dlaczego jest to lepsze rozwiązanie w każdym scenariuszu. W jakiej sytuacji 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.
Muhammad Umer,
57

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 wartość. Funkcje strzałek są zawsze anonimowe .

Jest to szczególnie istotne w twoim przykładzie, biorąc pod uwagę, że pochodzi z podanie. 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)             |  }
sdgluck
źródło
53

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

babel

Rahil Ahmad
źródło
42

Pomyśl o tym w ten sposób, za każdym razem, gdy zobaczysz strzałkę, zamień ją na function.
function parameterssą 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(); 
    };
}

Z dokumentów :

// 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
LifeQuery
źródło
6
Nie zapomnij wspomnieć o powiązaniu leksykalnym this.
Dziękuję
30

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 buttonwywołanie zwrotne onClick. I musimy przejść iddo funkcji, ale onClickakceptuje 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 idswó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
)
sułtan
źródło
2

Przykładem twojego pytania jest przykład, curried functionktóry korzysta z arrow functioni maimplicit return za pierwszy argument.

Funkcja strzałki wiąże to leksykalnie, tzn. Nie ma własnego thisargumentu, ale bierze thiswartość 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, argumentsale 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);
}
Shubham Khatri
źródło
1

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

Don Kartacs
źródło