Scopri perché un componente React sta eseguendo nuovamente il rendering


156

Esiste un approccio sistematico al debug che sta causando il rendering di un componente in React? Ho messo un semplice console.log () per vedere quante volte viene eseguito il rendering, ma ho problemi a capire cosa sta causando il rendering del componente più volte, ovvero (4 volte) nel mio caso. Esiste uno strumento che mostra una sequenza temporale e / o tutti i rendering e l'ordine dell'albero dei componenti?


Forse potresti usare shouldComponentUpdateper disabilitare l'aggiornamento automatico dei componenti e quindi iniziare la tua traccia da lì. Maggiori informazioni possono essere trovate qui: facebook.github.io/react/docs/optimizing-performance.html
Reza Sadr

La risposta di @jpdelatorre è corretta. In generale, uno dei punti di forza di React è che puoi facilmente rintracciare il flusso di dati nella catena guardando il codice. L' estensione React DevTools può aiutarti in questo. Inoltre, ho un elenco di strumenti utili per visualizzare / rintracciare il re-rendering dei componenti di React come parte del mio catalogo di addon Redux , e una serie di articoli su [Monitoraggio delle prestazioni di React] (htt
markerikson,

Risposte:


254

Se vuoi un breve frammento senza dipendenze esterne, lo trovo utile

componentDidUpdate(prevProps, prevState) {
  Object.entries(this.props).forEach(([key, val]) =>
    prevProps[key] !== val && console.log(`Prop '${key}' changed`)
  );
  if (this.state) {
    Object.entries(this.state).forEach(([key, val]) =>
      prevState[key] !== val && console.log(`State '${key}' changed`)
    );
  }
}

Ecco un piccolo hook che uso per tracciare gli aggiornamenti ai componenti delle funzioni

function useTraceUpdate(props) {
  const prev = useRef(props);
  useEffect(() => {
    const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
      if (prev.current[k] !== v) {
        ps[k] = [prev.current[k], v];
      }
      return ps;
    }, {});
    if (Object.keys(changedProps).length > 0) {
      console.log('Changed props:', changedProps);
    }
    prev.current = props;
  });
}

// Usage
function MyComponent(props) {
  useTraceUpdate(props);
  return <div>{props.children}</div>;
}

5
@ yarden.refaeli Non vedo motivo per avere un blocco if. Breve e conciso.
Isaac,

Insieme a questo, se scopri che un pezzo di stato è in fase di aggiornamento e non è ovvio dove o perché, puoi sovrascrivere il setStatemetodo (in un componente di classe) con setState(...args) { super.setState(...args) }e quindi impostare un punto di interruzione nel tuo debugger che potrai quindi per ritornare alla funzione che imposta lo stato.
redbmk,

Come uso esattamente la funzione hook? Dove dovrei chiamare esattamente useTraceUpdatedopo averlo definito come l'hai scritto?
Damon,

In un componente di funzione, puoi usarlo in questo modo function MyComponent(props) { useTraceUpdate(props); }e registrerà ogni volta che cambiano oggetti di scena
Jacob Rask,

1
@DawsonB probabilmente non hai alcun stato in quel componente, quindi this.statenon è definito.
Jacob Rask,

67

Ecco alcuni casi in cui un componente React eseguirà nuovamente il rendering.

  • Render del componente padre
  • Chiamata this.setState()all'interno del componente. In tal modo, i seguenti metodi del ciclo di vita dei componenti shouldComponentUpdate> componentWillUpdate> render>componentDidUpdate
  • Cambiamenti nei componenti props. In tal modo, componentWillReceiveProps> shouldComponentUpdate> componentWillUpdate> render> componentDidUpdate( connectmodalità di react-reduxinnesco questo quando ci sono cambiamenti applicabili nel negozio Redux)
  • chiamata this.forceUpdateche è simile athis.setState

Puoi ridurre al minimo il rendering del componente implementando un controllo all'interno del tuo shouldComponentUpdatee restituendolo falsese non è necessario.

Un altro modo è utilizzare React.PureComponent componenti senza stato. I componenti puri e senza stato vengono ri-renderizzati solo quando ci sono modifiche ai suoi oggetti di scena.


6
Nitpick: "stateless" significa semplicemente qualsiasi componente che non utilizza lo stato, sia esso definito con la sintassi della classe o la sintassi funzionale. Inoltre, i componenti funzionali vengono sempre ri-renderizzati. È necessario utilizzare shouldComponentUpdateo estendere React.PureComponentper imporre solo il rendering di nuovo al momento del cambiamento.
Markerikson,

1
Hai ragione sul componente senza stato / funzionale esegue sempre il rendering di nuovo. Aggiornerò la mia risposta.
jpdelatorre,

puoi chiarire quando e perché i componenti funzionali vengono sempre re-renderizzati? Uso parecchi componenti funzionali nella mia app.
jasan,

Quindi, anche se usi il modo funzionale di creare il tuo componente, ad esempio const MyComponent = (props) => <h1>Hello {props.name}</h1>;(che è un componente senza stato). Verrà eseguito nuovamente il rendering ogni volta che viene eseguito il rendering del componente padre.
jpdelatorre,

2
Questa è sicuramente un'ottima risposta, ma non risponde alla vera domanda, - Come rintracciare ciò che ha innescato un nuovo rendering. La risposta di Jacob R sembra promettente nel dare la risposta al vero problema.
Sanuj,

10

La risposta di @ jpdelatorre è ottima nell'evidenziare le ragioni generali per cui un componente React potrebbe ricalcolare.

Volevo solo immergermi un po 'più in profondità in un'istanza: quando i puntelli cambiano . Risolvere i problemi che causano il rendering di un componente React è un problema comune e nella mia esperienza molte volte rintracciare questo problema implica determinare quali oggetti di scena stanno cambiando .

Riattiva i componenti riattivi ogni volta che ricevono nuovi oggetti di scena. Possono ricevere nuovi oggetti di scena come:

<MyComponent prop1={currentPosition} prop2={myVariable} />

o se MyComponentè collegato a un negozio redux:

function mapStateToProps (state) {
  return {
    prop3: state.data.get('savedName'),
    prop4: state.data.get('userCount')
  }
}

Sempre il valore di prop1, prop2, prop3, o prop4modifiche MyComponentsi ri-rendering. Con 4 oggetti di scena non è troppo difficile rintracciare quali oggetti di scena stanno cambiando inserendo un console.log(this.props)a quell'inizio del renderblocco. Tuttavia, con componenti più complicati e sempre più oggetti di scena questo metodo è insostenibile.

Ecco un approccio utile (usando lodash per comodità) per determinare quali modifiche all'elica stanno causando il rendering di un componente:

componentWillReceiveProps (nextProps) {
  const changedProps = _.reduce(this.props, function (result, value, key) {
    return _.isEqual(value, nextProps[key])
      ? result
      : result.concat(key)
  }, [])
  console.log('changedProps: ', changedProps)
}

L'aggiunta di questo frammento al componente può aiutare a rivelare il colpevole causando ritrasformazioni discutibili e molte volte ciò aiuta a far luce sui dati non necessari che vengono convogliati nei componenti.


3
Ora viene chiamato UNSAFE_componentWillReceiveProps(nextProps)ed è obsoleto. "Questo ciclo di vita è stato precedentemente denominato componentWillReceiveProps. Quel nome continuerà a funzionare fino alla versione 17." Dalla documentazione di React .
Emile Bergeron,

1
Puoi ottenere lo stesso risultato con componentDidUpdate, che è probabilmente migliore, dato che vuoi solo scoprire cosa ha causato l'aggiornamento effettivo di un componente.
vedi più nitido il

5

Strano nessuno ha dato quella risposta ma la trovo molto utile, soprattutto perché i cambi di oggetti di scena sono quasi sempre profondamente annidati.

Ganci fanboys:

import deep_diff from "deep-diff";
const withPropsChecker = WrappedComponent => {
  return props => {
    const prevProps = useRef(props);
    useEffect(() => {
      const diff = deep_diff.diff(prevProps.current, props);
      if (diff) {
        console.log(diff);
      }
      prevProps.current = props;
    });
    return <WrappedComponent {...props} />;
  };
};

"Vecchi" fan della scuola:

import deep_diff from "deep-diff";
componentDidUpdate(prevProps, prevState) {
      const diff = deep_diff.diff(prevProps, this.props);
      if (diff) {
        console.log(diff);
      }
}

PS Preferisco ancora usare HOC (componente di ordine superiore) perché a volte hai distrutto i tuoi oggetti di scena in alto e la soluzione di Jacob non si adatta bene

Disclaimer: nessuna affiliazione con il proprietario del pacchetto. Fare clic su decine di volte per cercare di individuare la differenza negli oggetti profondamente annidati è un dolore.



2

Utilizzando ganci e componenti funzionali, non solo il cambio di oggetti di scena può causare un rendering. Quello che ho iniziato a usare è un registro piuttosto manuale. Mi ho aiutato molto Potresti trovare utile anche questo.

Ho incollato questa parte nel file del componente:

const keys = {};
const checkDep = (map, key, ref, extra) => {
  if (keys[key] === undefined) {
    keys[key] = {key: key};
    return;
  }
  const stored = map.current.get(keys[key]);

  if (stored === undefined) {
    map.current.set(keys[key], ref);
  } else if (ref !== stored) {
    console.log(
      'Ref ' + keys[key].key + ' changed',
      extra ?? '',
      JSON.stringify({stored}).substring(0, 45),
      JSON.stringify({now: ref}).substring(0, 45),
    );
    map.current.set(keys[key], ref);
  }
};

All'inizio del metodo tengo un riferimento a WeakMap:

const refs = useRef(new WeakMap());

Quindi dopo ogni chiamata "sospetta" (oggetti di scena, ganci) scrivo:

const example = useExampleHook();
checkDep(refs, 'example ', example);

1

Le risposte di cui sopra sono molto utili, nel caso in cui qualcuno stia cercando un metodo specifico per rilevare la causa del rerender, ho trovato molto utile questa libreria redux-logger .

Quello che puoi fare è aggiungere la libreria e abilitare la differenza tra stato (è presente nei documenti) come:

const logger = createLogger({
    diff: true,
});

E aggiungi il middleware nel negozio.

Quindi inserire a console.log()nella funzione di rendering del componente che si desidera verificare.

Quindi puoi eseguire la tua app e controllare i log della console. Ovunque c'è un log poco prima che ti mostri la differenza tra lo stato (nextProps and this.props)e puoi decidere se il rendering è davvero necessario lìinserisci qui la descrizione dell'immagine

Sarà simile all'immagine sopra insieme al tasto diff.

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.