Warum sollten JSX-Requisiten keine Pfeilfunktionen verwenden oder binden?

103

Ich verwende mit meiner React-App Flusen und erhalte folgende Fehlermeldung:

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

Und hier führe ich die Pfeilfunktion (innen onClick) aus:

{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>
))}

Ist dies eine schlechte Praxis, die vermieden werden sollte? Und wie geht das am besten?

KadoBOT
quelle

Antworten:

170

Warum Sie in JSX-Requisiten keine Inline-Pfeilfunktionen verwenden sollten

Die Verwendung von Pfeilfunktionen oder das Binden in JSX ist eine schlechte Vorgehensweise, die die Leistung beeinträchtigt, da die Funktion bei jedem Rendern neu erstellt wird.

  1. Immer wenn eine Funktion erstellt wird, wird die vorherige Funktion durch Müll gesammelt. Das erneute Rendern vieler Elemente kann zu einem Ruck in Animationen führen.

  2. Die Verwendung einer Inline- Pfeilfunktion führt dazu, dass PureComponents und Komponenten, die shallowComparein der shouldComponentUpdateMethode verwendet werden, ohnehin neu gerendert werden. Da die Pfeilfunktionsstütze jedes Mal neu erstellt wird, wird sie beim flachen Vergleich als Änderung an einer Stütze identifiziert, und die Komponente wird erneut gerendert.

Wie Sie in den folgenden 2 Beispielen sehen können, wird die <Button>Komponente bei Verwendung der Inline- Pfeilfunktion jedes Mal neu gerendert (auf der Konsole wird der Text der Schaltfläche "Rendern" angezeigt).

Beispiel 1 - PureComponent ohne Inline-Handler

Beispiel 2 - PureComponent mit Inline-Handler

Bindungsmethoden thisohne Inlining-Pfeilfunktionen

  1. Manuelles Binden der Methode im Konstruktor:

    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. Binden einer Methode unter Verwendung der Vorschlagsklassenfelder mit einer Pfeilfunktion. Da es sich um einen Vorschlag für Stufe 3 handelt, müssen Sie Ihrer Babel-Konfiguration die Voreinstellung für Stufe 3 oder die Transformation der Klasseneigenschaften hinzufügen .

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

Funktionskomponenten mit inneren Rückrufen

Wenn wir eine innere Funktion (z. B. Ereignishandler) innerhalb einer Funktionskomponente erstellen, wird die Funktion jedes Mal neu erstellt, wenn die Komponente gerendert wird. Wenn die Funktion als Requisiten (oder über den Kontext) an eine untergeordnete Komponente übergeben wird ( Buttonin diesem Fall), wird dieses untergeordnete Element ebenfalls neu gerendert.

Beispiel 1 - Funktionskomponente mit innerem Rückruf:

Um dieses Problem zu lösen, können wir den Rückruf mit dem useCallback()Hook umschließen und die Abhängigkeiten auf ein leeres Array setzen.

Hinweis: Die useStategenerierte Funktion akzeptiert eine Updater-Funktion, die den aktuellen Status bereitstellt. Auf diese Weise müssen wir für den aktuellen Status keine Abhängigkeit von festlegen useCallback.

Beispiel 2 - Funktionskomponente mit einem inneren Rückruf, der mit useCallback umschlossen ist:

Ori Drori
quelle
3
Wie erreichen Sie dies bei zustandslosen Bauteilen?
Lux
4
Zustandslose (Funktions-) Komponenten haben keine this, daher gibt es nichts zu binden. Normalerweise werden die Methoden von einer Wrapper-Smart-Komponente bereitgestellt.
Ori Drori
39
@OriDrori: Wie funktioniert das, wenn Sie Daten im Rückruf übergeben müssen? onClick={() => { onTodoClick(todo.id) }
Adam-Beck
4
@ adam-beck - füge es in die Definition der Rückrufmethode in der Klasse ein cb() { onTodoClick(this.props.todo.id); }.
Ori Drori
2
@ adam-beck Ich denke, das ist, wie man useCallbackmit dynamischem Wert verwendet. stackoverflow.com/questions/55006061/…
Shota Tamura
9

Dies liegt daran, dass eine Pfeilfunktion anscheinend bei jedem Rendern eine neue Instanz der Funktion erstellt, wenn sie in einer JSX-Eigenschaft verwendet wird. Dies kann den Garbage Collector stark belasten und den Browser daran hindern, "Hot Paths" zu optimieren, da Funktionen weggeworfen und nicht wiederverwendet werden.

Die gesamte Erklärung und weitere Informationen finden Sie unter https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Karl-Johan Sjögren
quelle
Nicht nur das. Das Erstellen der neuen Funktionsinstanzen bedeutet jedes Mal, dass der Status geändert wird. Wenn der Status einer Komponente geändert wird, wird er erneut gerendert. Da einer der Hauptgründe für die Verwendung von React darin besteht, nur Elemente zu rendern, die sich ändern, bindschießen Sie mit oder mit den Pfeilfunktionen in den Fuß. Es ist jedoch nicht gut dokumentiert, insbesondere im Fall der Arbeit mit mapPing-Arrays in Listen usw.
Hippietrail
"Das Erstellen der neuen Funktionsinstanzen jedes Mal bedeutet, dass der Status geändert wird" Was meinen Sie damit? Es gibt überhaupt keinen Zustand in der Frage
pro Stück Bart
4

Um zu vermeiden, dass neue Funktionen mit denselben Argumenten erstellt werden, können Sie sich das Ergebnis der Funktionsbindung merken. Hier ist ein einfaches Dienstprogramm mit dem Namen memobind merken. https://github.com/supnate/memobind

supNate
quelle
4

Die Verwendung solcher Inline-Funktionen ist vollkommen in Ordnung. Die Flusenregel ist veraltet.

Diese Regel stammt aus einer Zeit, in der Pfeilfunktionen nicht so häufig waren und die Benutzer .bind (this) verwendeten, was früher langsam war. Das Leistungsproblem wurde in Chrome 49 behoben.

Achten Sie darauf, dass Sie keine Inline-Funktionen als Requisiten an eine untergeordnete Komponente übergeben.

Ryan Florence, der Autor von React Router, hat ein großartiges Stück darüber geschrieben:

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

sbaechler
quelle
Können Sie bitte zeigen, wie ein Komponententest für Komponenten mit Inline-Pfeilfunktionen geschrieben wird?
krankuba
1
@krankuba Darum ging es in dieser Frage nicht. Sie können weiterhin anonyme Funktionen übergeben, die nicht inline definiert, aber noch nicht testbar sind.
Sbaechler
-1

Sie können Pfeilfunktionen mithilfe der React-Cached-Handler- Bibliothek verwenden, ohne sich um die Leistung beim erneuten Rendern sorgen zu müssen:

Hinweis: Intern werden Ihre Pfeilfunktionen mit der angegebenen Taste zwischengespeichert. Sie müssen sich keine Gedanken über ein erneutes Rendern machen!

render() {

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

}

Andere Eigenschaften:

  • Benannte Handler
  • Behandeln Sie Ereignisse mit Pfeilfunktionen
  • Zugriff auf den Schlüssel, benutzerdefinierte Argumente und das ursprüngliche Ereignis
  • Leistung beim Rendern von Komponenten
  • Benutzerdefinierter Kontext für Handler
Ghominejad
quelle
Die Frage war, warum wir es nicht benutzen können. Nicht, wie man es mit einem anderen Hack benutzt.
Kapil
-1

Warum sollten JSX-Requisiten keine Pfeilfunktionen verwenden oder binden?

Meistens, weil Inline-Funktionen das Auswendiglernen optimierter Komponenten unterbrechen können:

Traditionell hängen Leistungsprobleme bei Inline-Funktionen in React damit zusammen, dass das Übergeben neuer Rückrufe bei jedem Rendering shouldComponentUpdateOptimierungen in untergeordneten Komponenten unterbricht. ( docs )

Es geht weniger um zusätzliche Kosten für die Funktionserstellung:

Leistungsprobleme mit Function.prototype.bind hier behoben und Pfeilfunktionen sind entweder eine native Sache oder werden von babel auf einfache Funktionen übertragen; In beiden Fällen können wir davon ausgehen, dass es nicht langsam ist. ( Reaktionstraining )

Ich glaube, dass Leute, die behaupten, die Erstellung von Funktionen sei teuer, immer falsch informiert wurden (das React-Team hat dies nie gesagt). ( Tweet )

Wann ist der react/jsx-no-bind Regel sinnvoll?

Sie möchten sicherstellen, dass gespeicherte Komponenten wie vorgesehen funktionieren:

  • React.memo (für Funktionskomponenten)
  • PureComponentoder benutzerdefiniert shouldComponentUpdate(für Klassenkomponenten)

Unter Einhaltung dieser Regel werden stabile Funktionsobjektreferenzen übergeben. Daher können die oben genannten Komponenten die Leistung optimieren, indem sie ein erneutes Rendern verhindern, wenn sich die vorherigen Requisiten nicht geändert haben.

Wie löse ich den ESLint-Fehler?

Klassen: Definieren Sie den Handler als Methode oder Klasseneigenschaft für die thisBindung.
Haken: Verwenden useCallback.

Mittelgrund

In vielen Fällen sind Inline-Funktionen sehr bequem zu bedienen und hinsichtlich der Leistungsanforderungen absolut in Ordnung. Leider kann diese Regel nicht nur auf gespeicherte Komponententypen beschränkt werden. Wenn Sie es weiterhin allgemein verwenden möchten, können Sie es beispielsweise für einfache DOM-Knoten deaktivieren :

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

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
ford04
quelle