Perché gli oggetti di scena JSX non dovrebbero usare le funzioni freccia o bind?


103

Sto eseguendo lanugine con la mia app React e ricevo questo errore:

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

Ed è qui che sto eseguendo la funzione freccia (all'interno 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>
))}

È una cattiva pratica che dovrebbe essere evitata? E qual è il modo migliore per farlo?

Risposte:


170

Perché non dovresti usare le funzioni freccia in linea negli oggetti di scena JSX

Usare le funzioni freccia o l'associazione in JSX è una cattiva pratica che danneggia le prestazioni, perché la funzione viene ricreata ad ogni rendering.

  1. Ogni volta che viene creata una funzione, la funzione precedente viene raccolta in Garbage Collection. Il rendering di molti elementi potrebbe creare jank nelle animazioni.

  2. L'utilizzo di una funzione freccia in linea provocherà comunque il rendering di PureComponents e dei componenti utilizzati shallowComparenel shouldComponentUpdatemetodo. Poiché il sostegno della funzione freccia viene ricreato ogni volta, il confronto superficiale lo identificherà come una modifica a un sostegno e il componente verrà riprodotto nuovamente.

Come puoi vedere nei seguenti 2 esempi, quando usiamo la funzione freccia in linea, il <Button>componente viene rieseguito ogni volta (la console mostra il testo del "pulsante di rendering").

Esempio 1: PureComponent senza gestore inline

Esempio 2: PureComponent con gestore inline

Binding di metodi a thissenza funzioni freccia incorporate

  1. Associazione manuale del metodo nel costruttore:

    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. Associazione di un metodo utilizzando i campi della classe proposta con una funzione freccia. Poiché si tratta di una proposta della fase 3, è necessario aggiungere la preimpostazione della fase 3 o la trasformazione delle proprietà della classe alla configurazione di 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>
        );
      }
    }

Componenti funzione con callback interni

Quando creiamo una funzione interna (ad esempio un gestore di eventi) all'interno di un componente funzione, la funzione verrà ricreata ogni volta che il componente viene renderizzato. Se la funzione viene passata come oggetti di scena (o tramite contesto) a un componente figlio ( Buttonin questo caso), anche quel bambino verrà rieseguito.

Esempio 1 - Componente funzione con callback interno:

Per risolvere questo problema, possiamo racchiudere il callback con l' useCallback()hook e impostare le dipendenze su un array vuoto.

Nota: la useStatefunzione generata accetta una funzione di aggiornamento, che fornisce lo stato corrente. In questo modo, non è necessario impostare lo stato corrente come una dipendenza di useCallback.

Esempio 2 - Componente della funzione con una richiamata interna racchiusa in useCallback:


3
Come si ottiene questo risultato su componenti apolidi?
lux

4
I componenti senza stato (funzione) non hanno this, quindi non c'è niente da associare. Di solito i metodi sono forniti da un componente intelligente wrapper.
Ori Drori

39
@OriDrori: come funziona quando è necessario passare i dati nella richiamata? onClick={() => { onTodoClick(todo.id) }
adam-beck

4
@ adam-beck - aggiungilo all'interno della definizione del metodo di callback nella classe cb() { onTodoClick(this.props.todo.id); }.
Ori Drori

2
@ adam-beck Penso che questo sia come usare useCallbackcon valore dinamico. stackoverflow.com/questions/55006061/…
Shota Tamura

9

Questo perché una funzione freccia apparentemente creerà una nuova istanza della funzione su ogni rendering se usata in una proprietà JSX. Ciò potrebbe creare un enorme sforzo per il garbage collector e impedirà anche al browser di ottimizzare qualsiasi "percorso caldo" poiché le funzioni verranno eliminate invece di essere riutilizzate.

Puoi vedere l'intera spiegazione e qualche informazione in più su https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md


Non solo quello. La creazione di nuove istanze di funzione ogni volta significa che lo stato viene modificato e quando lo stato di un componente viene modificato verrà nuovamente renderizzato. Poiché uno dei motivi principali per utilizzare React è rendere solo gli elementi che cambiano, usare bindo le funzioni freccia qui è spararti ai piedi. Tuttavia, non è ben documentato, specialmente nel caso di lavorare con maparray di ping all'interno di elenchi, ecc.
hippietrail

"Creare le nuove istanze di funzione ogni volta significa che lo stato viene modificato" cosa intendi con questo? Non c'è alcuno stato nella domanda
apieceofbart il


4

L'uso di funzioni inline come questa va benissimo. La regola di lanugine è obsoleta.

Questa regola risale a un'epoca in cui le funzioni delle frecce non erano così comuni e le persone usavano .bind (this), che era lento. Il problema di prestazioni è stato risolto in Chrome 49.

Fai attenzione a non passare le funzioni inline come oggetti di scena a un componente figlio.

Ryan Florence, l'autore di React Router, ha scritto un ottimo pezzo su questo:

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


Puoi per favore mostrare come scrivere un test unitario su componenti con funzioni freccia in linea?
krankuba

1
@krankuba Non è di questo che si trattava. È ancora possibile passare funzioni anonime che non sono definite inline ma non sono ancora testabili.
sbaechler

-1

È possibile utilizzare le funzioni freccia utilizzando la libreria del gestore della cache di reazione , non c'è bisogno di preoccuparsi delle prestazioni di ri-rendering:

Nota: internamente memorizza nella cache le funzioni freccia con il tasto specificato, non c'è bisogno di preoccuparsi del ri-rendering!

render() {

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

}

Altre caratteristiche:

  • Gestori nominati
  • Gestisci gli eventi con le funzioni delle frecce
  • Accesso alla chiave, agli argomenti personalizzati e all'evento originale
  • Prestazioni di rendering dei componenti
  • Contesto personalizzato per i gestori

La domanda era perché non possiamo usarlo. Non come usarlo con qualche altro hack.
kapil

-1

Perché gli oggetti di scena JSX non dovrebbero usare le funzioni freccia o bind?

Principalmente, poiché le funzioni inline possono interrompere la memorizzazione dei componenti ottimizzati:

Tradizionalmente, i problemi di prestazioni delle funzioni inline in React sono stati correlati al modo in cui il passaggio di nuovi callback su ogni rendering interrompe le shouldComponentUpdateottimizzazioni nei componenti figli. ( documenti )

È meno sui costi aggiuntivi per la creazione di funzioni:

I problemi di prestazioni con sono Function.prototype.bind stati risolti qui e le funzioni freccia sono una cosa nativa o sono trasplate da babel in semplici funzioni; in entrambi i casi possiamo presumere che non sia lento. ( React Training )

Credo che le persone che affermano che la creazione di funzioni sia costosa siano sempre state male informate (il team di React non l'ha mai detto). ( Tweet )

Quando è react/jsx-no-bindutile la regola?

Vuoi assicurarti che i componenti memoizzati funzionino come previsto:

  • React.memo (per componenti funzionali)
  • PureComponento personalizzato shouldComponentUpdate(per componenti di classe)

Obbedendo a questa regola, vengono passati riferimenti a oggetti funzione stabili. Quindi i componenti di cui sopra possono ottimizzare le prestazioni prevenendo il rendering, quando gli oggetti di scena precedenti non sono cambiati.

Come risolvere l'errore ESLint?

Classi: definire il gestore come metodo o proprietà della classe per l' thisassociazione.
Ganci: utilizzare useCallback.

middleground

In molti casi, le funzioni inline sono molto comode da usare e assolutamente soddisfacenti in termini di requisiti di prestazioni. Sfortunatamente, questa regola non può essere limitata ai soli tipi di componenti memoizzati. Se vuoi ancora usarlo su tutta la linea, potresti ad esempio disabilitarlo per semplici nodi DOM:

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

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.