So vermeiden Sie Bindungs- oder Inline-Pfeilfunktionen innerhalb der Rendermethode

Lesezeit: 7 Minuten

So vermeiden Sie Bindungs oder Inline Pfeilfunktionen innerhalb der Rendermethode
Mayank Shukla

Wir sollten die Methodenbindung innerhalb des Renderns vermeiden, da beim erneuten Rendern die neuen Methoden erstellt werden, anstatt die alte zu verwenden, was sich auf die Leistung auswirkt.

Also für die Szenarien wie diese:

<input onChange = { this._handleChange.bind(this) } ...../>

Wir können binden _handleChange Methode entweder im Konstruktor:

this._handleChange = this._handleChange.bind(this);

Oder wir können verwenden Eigenschaftsinitialisierer-Syntax:

_handleChange = () => {....}

Betrachten wir nun den Fall, in dem wir einige zusätzliche Parameter übergeben möchten, sagen wir in einer einfachen Aufgaben-App, bei einem Klick auf ein Element muss ich das Element aus dem Array löschen, dafür muss ich entweder den Elementindex oder den Aufgabennamen in jedem übergeben onClick-Methode:

todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)

Gehen Sie für den Moment davon aus, dass Todo-Namen eindeutig sind.

Gem DOC:

Das Problem bei dieser Syntax besteht darin, dass bei jedem Rendern der Komponente ein anderer Callback erstellt wird.

Frage:

Wie kann man diese Art der Bindung innerhalb der Rendermethode vermeiden oder was sind die Alternativen dazu?

Bitte geben Sie eine Referenz oder ein Beispiel an, danke.

So vermeiden Sie Bindungs oder Inline Pfeilfunktionen innerhalb der Rendermethode
Shubham Khatri

Zuerst: Eine einfache Lösung besteht darin, eine Komponente für den Inhalt innerhalb einer Kartenfunktion zu erstellen und die Werte als Requisiten zu übergeben. Wenn Sie die Funktion von der untergeordneten Komponente aufrufen, können Sie den Wert an die als Requisiten übergebene Funktion übergeben.

Elternteil

deleteTodo = (val) => {
    console.log(val)
}
todos.map(el => 
    <MyComponent val={el} onClick={this.deleteTodo}/> 

)

MeineKomponente

class MyComponent extends React.Component {
    deleteTodo = () => {
        this.props.onClick(this.props.val);
    }
    render() {
       return <div  onClick={this.deleteTodo}> {this.props.val} </div>
    }
}

Beispielausschnitt

class Parent extends React.Component {
     _deleteTodo = (val) => {
        console.log(val)
    }
    render() {
        var todos = ['a', 'b', 'c'];
        return (
           <div>{todos.map(el => 
             <MyComponent key={el} val={el} onClick={this._deleteTodo}/> 
        
           )}</div>
        )
    }
    
   
}

class MyComponent extends React.Component {
        _deleteTodo = () => {
                     console.log('here');   this.props.onClick(this.props.val);
        }
        render() {
           return <div onClick={this._deleteTodo}> {this.props.val} </div>
        }
    }
    
ReactDOM.render(<Parent/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

BEARBEITEN:

Sekunde: Der andere Ansatz wäre, memoize zu verwenden und eine Funktion zurückzugeben

constructor() {
    super();
    this._deleteTodoListener = _.memoize(
                   this._deleteTodo, (element) => {
                        return element.hashCode();
                    }
              )
}

_deleteTodo = (element) => {
   //delete handling here
}

und verwenden Sie es wie

todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)

PS Dies ist jedoch keine beste Lösung und führt immer noch dazu, dass mehrere Funktionen erstellt werden, ist aber immer noch eine Verbesserung gegenüber dem ursprünglichen Fall.

Dritter: Eine geeignetere Lösung hierfür ist jedoch das Hinzufügen von an attribute zum obersten div und erhalte den Wert von event wie

_deleteTodo = (e) => {
     console.log(e.currentTarget.getAttribute('data-value'));

 }

 todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)

In diesem Fall werden die Attribute jedoch mithilfe der toString-Methode in Zeichenfolgen konvertiert, und daher wird ein Objekt konvertiert [Object Object] und und Array wie ["1" , "2", "3"] als "1, 2, 3"

  • Ja, das können wir tun, aber damit müssen wir separate Komponenten erstellen und die Eltern-Kind-Beziehung einfügen. Ich denke, das wird nicht so skalierbar sein, da wir diese Art der Bindung in großen Anwendungen früher an mehreren Stellen durchgeführt haben.

    – Mayank Shukla

    12. Juli 2017 um 9:38 Uhr


  • Ich habe auch damit zu kämpfen, und meine Schlussfolgerung ist, dass, wenn diese Neuerstellung von Funktionen Ihre Anwendung verlangsamt (was … ich denke … passieren könnte, wenn Sie einen ausreichend großen Datensatz haben, der viel neu gerendert wird ), sollten Sie diesem Ansatz für diese Komponenten folgen. Ansonsten ist es nicht wirklich ein Problem für die Leistung und kann daher getrost ignoriert werden.

    – Kris Selbekk

    12. Juli 2017 um 9:41 Uhr

  • Ja, aber das, wie Sie vermeiden können, was Sie wollen, und Skalierbarkeit sollte hier kein Problem sein

    – Shubham Khatri

    12. Juli 2017 um 9:42 Uhr


  • In Ihren ersten beiden Codeblöcken verwenden Sie beim Erstellen der Klasseneigenschaft deleteTodoaber wenn Sie darauf verweisen, verwenden Sie _deleteTodo. Ist das ein Fehler oder hat der Unterstrich hier einen besonderen Zweck?

    – kojow7

    12. Februar 2018 um 15:38 Uhr

  • @akshaykishore, Sie können in einem solchen Fall den dritten Ansatz verwenden, anstatt den Index an onClick zu übergeben

    – Shubham Khatri

    28. Juni 2019 um 7:42 Uhr

Wie kann man diese Art der Bindung innerhalb der Rendermethode vermeiden oder was sind die Alternativen dazu?

Wenn Sie sich für das erneute Rendern interessieren shouldComponentUpdate und PureComponent sind Ihre Freunde und helfen Ihnen bei der Optimierung des Renderings.

Sie müssen die “Child”-Komponente aus der “Parent”-Komponente extrahieren und immer dieselben Props und Implements übergeben shouldComponentUpdate oder verwenden PureComponent. Was wir wollen, ist ein Fall, wenn wir ein Kind entfernen, andere Kinder sollten nicht erneut gerendert werden.

Beispiel

import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';

class Product extends PureComponent {
  render() {
    const { id, name, onDelete } = this.props;

    console.log(`<Product id=${id} /> render()`);
    return (
      <li>
        {id} - {name}
        <button onClick={() => onDelete(id)}>Delete</button>
      </li>
    );
  }
}

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      products: [
        { id: 1, name: 'Foo' },
        { id: 2, name: 'Bar' },
      ],
    };

    this.handleDelete = this.handleDelete.bind(this);
  }

  handleDelete(productId) {
    this.setState(prevState => ({
      products: prevState.products.filter(product => product.id !== productId),
    }));
  }

  render() {
    console.log(`<App /> render()`);
    return (
      <div>
        <h1>Products</h1>
        <ul>
          {
            this.state.products.map(product => (
              <Product 
                key={product.id}
                onDelete={this.handleDelete}
                {...product}
              />
            ))
          }
        </ul>
      </div>
    ); 
  }
}

render(<App />, document.getElementById('root'));

Demo: https://codesandbox.io/s/99nZGlyZ

Erwartetes Verhalten

  • <App /> render()
  • <Product id=1... render()
  • <Product id=2... render()

Wenn wir entfernen <Product id=2 ... nur <App /> wird neu gerendert.

  • machen()

Um diese Nachrichten in der Demo anzuzeigen, öffnen Sie die Entwicklertools-Konsole.

Die gleiche Technik wird verwendet und im Artikel beschrieben: Reagieren ist langsam, Reagieren ist schnell: Optimierung von React-Apps in der Praxis von Francois Zaninotto.

  • danke für den Vorschlag, aber ich denke, die Verwendung eines eindeutigen Schlüssels wird dieses Problem lösen Was wir wollen, ist ein Fall, wenn wir ein Kind entfernen, andere Kinder sollten nicht erneut gerendert werden da ich nur ein einzelnes div mit text rendern möchte. Dieser Ansatz wird eine große Rolle spielen, wenn die Komponente groß ist und wir ein erneutes Rendern vermeiden möchten.

    – Mayank Shukla

    12. Juli 2017 um 10:02 Uhr


  • Verwenden key Eigenschaft löst dieses Problem nicht, siehe: codesandbox.io/s/xVZ7pL6E auch wenn Sie verwenden key Eigenschaft, render() von anderen <Product /> werden auch genannt. Die einzige Änderung zwischen Demo und diesem Link ist Product extends Component anstatt PureComponent.

    – Dawid Karabin

    12. Juli 2017 um 10:46 Uhr


Dokumentation regt zur Nutzung an Datenattribute und von innen darauf zugreifen evt.target.dataset:

_deleteTodo = (evt) => {
  const elementToDelete = evt.target.dataset.el;
  this.setState(prevState => ({
    todos: prevState.todos.filter(el => el !== elementToDelete)
  }))
}

// and from render:

todos.map(
  el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
)

Ebenfalls Hinweis dass dies nur bei Leistungsproblemen sinnvoll ist:

Ist es in Ordnung, Pfeilfunktionen in Rendermethoden zu verwenden?

Im Allgemeinen ja, es ist in Ordnung, und es ist oft der einfachste Weg, Parameter an Callback-Funktionen zu übergeben.

Wenn Sie Leistungsprobleme haben, optimieren Sie auf jeden Fall!

  • Da Ihre Antwort jetzt aus dem Jahr 2018 stammt, können Sie heute einfach “React Hook” verwenden, um sie zu teilen.

    – Isaak

    13. November 2018 um 9:28 Uhr

  • Redest du von useCallback?

    – strelets

    13. November 2018 um 9:30 Uhr

1646329517 489 So vermeiden Sie Bindungs oder Inline Pfeilfunktionen innerhalb der Rendermethode
grebenyuksv

Diese Antwort https://stackoverflow.com/a/45053753/2808062 ist definitiv erschöpfend, aber ich würde sagen, dass das Bekämpfen übermäßiger Re-Renderings, anstatt nur den winzigen Callback neu zu erstellen, Ihnen mehr Leistungsverbesserungen bringen würde. Das wird normalerweise durch die Implementierung einer richtigen erreicht shouldComponentUpdate in der untergeordneten Komponente.

Selbst wenn die Requisiten genau gleich sind, wird der folgende Code untergeordnete Elemente immer noch neu rendern, es sei denn, sie verhindern dies selbst shouldComponentUpdate (Sie könnten es von erben PureComponent):

handleChildClick = itemId => {}

render() {
    return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
}

Nachweisen: https://jsfiddle.net/69z2wepo/92281/.

Um ein erneutes Rendern zu vermeiden, muss die untergeordnete Komponente implementiert werden shouldComponentUpdate ohnehin. Jetzt ist die einzig vernünftige Implementierung das vollständige Ignorieren onClick unabhängig davon, ob es sich geändert hat:

shouldComponentUpdate(nextProps) {
    return this.props.array !== nextProps.array;
}

925860cookie-checkSo vermeiden Sie Bindungs- oder Inline-Pfeilfunktionen innerhalb der Rendermethode

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy