Dlaczego rekwizyty JSX nie powinny używać funkcji strzałek ani bindować?

105

Używam programu Lint z moją aplikacją React i otrzymuję ten błąd:

error    JSX props should not use arrow functions        react/jsx-no-bind

I tutaj uruchamiam funkcję strzałki (wewnątrz onClick):

{this.state.photos.map(tile => (
  <span key={tile.img}>
    <Checkbox
      defaultChecked={tile.checked}
      onCheck={() => this.selectPicture(tile)}
      style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
    />
    <GridTile
      title={tile.title}
      subtitle={<span>by <b>{tile.author}</b></span>}
      actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
    >
      <img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
    </GridTile>
  </span>
))}

Czy to zła praktyka, której należy unikać? A jak najlepiej to zrobić?

KadoBOT
źródło

Odpowiedzi:

170

Dlaczego nie powinieneś używać wbudowanych funkcji strzałek w właściwościach JSX

Używanie funkcji strzałkowych lub powiązań w JSX to zła praktyka, która obniża wydajność, ponieważ funkcja jest odtwarzana przy każdym renderowaniu.

  1. Za każdym razem, gdy tworzona jest funkcja, poprzednia funkcja jest usuwana z pamięci. Rrenderowanie wielu elementów może powodować szarpanie w animacjach.

  2. Korzystanie z funkcji inline strzałki spowoduje PureComponentS i komponenty, które Zastosowanie shallowComparew shouldComponentUpdatemetodzie i tak rerender. Ponieważ właściwość funkcji strzałki jest odtwarzana za każdym razem, płytkie porównanie zidentyfikuje ją jako zmianę właściwości, a komponent ponownie się wyłączy.

Jak widać na poniższych 2 przykładach - gdy używamy funkcji inline arrow, <Button>komponent jest renderowany za każdym razem (konsola wyświetla tekst „render button”).

Przykład 1 - PureComponent bez wbudowanej obsługi

Przykład 2 - PureComponent z wbudowaną obsługą

Wiązanie metod thisbez wbudowanych funkcji strzałek

  1. Ręczne wiązanie metody w konstruktorze:

    class Button extends React.Component {
      constructor(props, context) {
        super(props, context);
    
        this.cb = this.cb.bind(this);
      }
    
      cb() {
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }
  2. Powiązanie metody przy użyciu pól klasy propozycji z funkcją strzałki. Ponieważ jest to propozycja etapu 3, musisz dodać ustawienie wstępne etapu 3 lub transformację właściwości klasy do konfiguracji babel.

    class Button extends React.Component {
      cb = () => { // the class property is initialized with an arrow function that binds this to the class
    
      }
    
      render() {
        return (
          <button onClick={ this.cb }>Click</button>
        );
      }
    }

Komponenty funkcyjne z wewnętrznymi wywołaniami zwrotnymi

Kiedy tworzymy funkcję wewnętrzną (na przykład procedurę obsługi zdarzeń) wewnątrz komponentu funkcji, funkcja ta zostanie odtworzona za każdym razem, gdy komponent jest renderowany. Jeśli funkcja zostanie przekazana jako właściwości (lub przez kontekst) do komponentu potomnego ( Buttonw tym przypadku), to dziecko również zostanie ponownie wyrenderowane.

Przykład 1 - składnik funkcji z wewnętrznym wywołaniem zwrotnym:

Aby rozwiązać ten problem, możemy zawinąć wywołanie zwrotne useCallback()zaczepem i ustawić zależności na pustą tablicę.

Uwaga:useState funkcja generowane przyjmuje funkcję Updater, który zapewnia aktualny stan. W ten sposób nie musimy ustawiać bieżącego stanu na zależność useCallback.

Przykład 2 - składnik funkcji z wewnętrznym wywołaniem zwrotnym opakowanym za pomocą metody useCallback:

Ori Drori
źródło
3
Jak to osiągnąć w przypadku komponentów bezstanowych?
lux
4
Komponenty bezstanowe (funkcji) nie mają this, więc nie ma nic do powiązania. Zwykle metody są dostarczane przez inteligentny komponent opakowujący.
Ori Drori
39
@OriDrori: Jak to działa, gdy musisz przekazać dane w wywołaniu zwrotnym? onClick={() => { onTodoClick(todo.id) }
adam-beck
4
@ adam-beck - dodaj go wewnątrz definicji metody wywołania zwrotnego w klasie cb() { onTodoClick(this.props.todo.id); }.
Ori Drori
2
@ adam-beck Myślę, że to jest sposób użycia useCallbackz wartością dynamiczną. stackoverflow.com/questions/55006061/…
Shota Tamura
9

Dzieje się tak, ponieważ funkcja strzałki najwyraźniej utworzy nową instancję funkcji przy każdym renderowaniu, jeśli zostanie użyta we właściwości JSX. Może to spowodować ogromne obciążenie dla garbage collectora, a także utrudnić przeglądarce optymalizację jakichkolwiek „gorących ścieżek”, ponieważ funkcje będą wyrzucane zamiast ponownie wykorzystywane.

Możesz zobaczyć całe wyjaśnienie i więcej informacji na https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Karl-Johan Sjögren
źródło
Nie tylko to. Tworzenie nowych instancji funkcji za każdym razem oznacza, że ​​stan jest modyfikowany, a po zmianie stanu składnika będzie on ponownie renderowany. Ponieważ jednym z głównych powodów używania Reacta jest renderowanie tylko elementów, które się zmieniają, użycie bindfunkcji lub strzałek jest tutaj strzeleniem sobie w stopę. Nie jest to jednak dobrze udokumentowane, szczególnie w przypadku pracy z maptablicami ping w ramach List itp.
hippietrail
„Tworzenie nowych instancji funkcji za każdym razem oznacza modyfikację stanu”. Co przez to rozumiesz? W pytaniu nie ma żadnego stanu
apieceofbart
4

Aby uniknąć tworzenia nowych funkcji z tymi samymi argumentami, możesz zapamiętać wynik wiązania funkcji, oto proste narzędzie o nazwie, memobindaby to zrobić: https://github.com/supnate/memobind

supNate
źródło
4

Używanie takich funkcji wbudowanych jest w porządku. Zasada lintingu jest nieaktualna.

Ta reguła pochodzi z czasów, gdy funkcje strzałkowe nie były tak powszechne, a ludzie używali .bind (this), który był powolny. Problem z wydajnością został rozwiązany w Chrome 49.

Zwróć uwagę, że nie przekazujesz funkcji inline jako właściwości do komponentu potomnego.

Ryan Florence, autor React Router, napisał o tym świetny artykuł:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

sbaechler
źródło
Czy możesz pokazać, jak napisać test jednostkowy komponentów z wbudowanymi funkcjami strzałek?
krankuba
1
@krankuba Nie tego dotyczyło to pytanie. Nadal można przekazywać funkcje anonimowe, które nie są zdefiniowane w tekście, ale nadal nie są testowalne.
sbaechler
-1

Możesz używać funkcji strzałkowych za pomocą biblioteki podręcznej obsługi reagowania , nie musisz się martwić o wydajność ponownego renderowania:

Uwaga: wewnętrznie buforuje funkcje strzałek według określonego klucza, nie musisz się martwić o ponowne renderowanie!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

Inne funkcje:

  • Nazwane programy obsługi
  • Obsługuj zdarzenia za pomocą funkcji strzałek
  • Dostęp do klucza, niestandardowych argumentów i oryginalnego zdarzenia
  • Wydajność renderowania komponentów
  • Niestandardowy kontekst dla programów obsługi
Ghominejad
źródło
Pytanie brzmiało, dlaczego nie możemy tego użyć. Nie jak go używać z innym hackiem.
kapil
-1

Dlaczego rekwizyty JSX nie powinny używać funkcji strzałek ani bindować?

Głównie dlatego, że funkcje wbudowane mogą zakłócić zapamiętywanie zoptymalizowanych komponentów:

Tradycyjnie problemy z wydajnością związane z funkcjami wbudowanymi w Reakcie były związane z tym, jak przekazywanie nowych wywołań zwrotnych przy każdym renderowaniu przerywa shouldComponentUpdateoptymalizacje w komponentach potomnych. ( dokumenty )

Mniej dotyczy dodatkowych kosztów tworzenia funkcji:

Problemy z wydajnością, które Function.prototype.bind zostały tutaj naprawione, a funkcje strzałek są albo natywne, albo są przenoszone przez babel do zwykłych funkcji; w obu przypadkach możemy założyć, że nie jest wolny. ( Szkolenie dotyczące reakcji )

Uważam, że ludzie twierdzący, że tworzenie funkcji jest kosztowne, zawsze byli źle poinformowani (zespół React nigdy tego nie powiedział). ( Tweetnij )

Kiedy react/jsx-no-bindreguła jest przydatna?

Chcesz mieć pewność, że zapamiętane komponenty działają zgodnie z przeznaczeniem:

  • React.memo (dla komponentów funkcyjnych)
  • PureComponentlub niestandardowe shouldComponentUpdate(dla komponentów klasowych)

Przestrzegając tej reguły, przekazywane są odniesienia do stabilnych obiektów funkcji. Dlatego powyższe komponenty mogą optymalizować wydajność, zapobiegając ponownemu renderowaniu, gdy poprzednie właściwości nie uległy zmianie.

Jak rozwiązać błąd ESLint?

Klasy: Zdefiniuj procedurę obsługi jako metodę lub właściwość klasy do thispowiązania.
Haczyki: użyj useCallback.

Kompromis

W wielu przypadkach funkcje wbudowane są bardzo wygodne w użyciu i absolutnie dobre pod względem wymagań wydajnościowych. Niestety, ta reguła nie może być ograniczona tylko do zapamiętanych typów komponentów. Jeśli nadal chcesz go używać w różnych miejscach, możesz na przykład wyłączyć go dla prostych węzłów DOM:

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
ford04
źródło