ReactJS - Il rendering viene chiamato ogni volta che viene chiamato "setState"?


503

React esegue nuovamente il rendering di tutti i componenti e componenti secondari ogni volta che setStateviene chiamato?

Se è così, perché? Pensavo che l'idea di React fosse resa minima quanto basta - quando lo stato è cambiato.

Nel seguente semplice esempio, entrambe le classi vengono visualizzate nuovamente quando si fa clic sul testo, nonostante il fatto che lo stato non cambi sui clic successivi, poiché il gestore onClick imposta sempre statelo stesso valore:

this.setState({'test':'me'});

Mi sarei aspettato che i rendering sarebbero avvenuti solo se i statedati fossero cambiati.

Ecco il codice dell'esempio, come JS Fiddle , e snippet incorporato:

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

[1]: http://jsfiddle.net/fp2tncmb/2/

Ho avuto lo stesso problema, non conosco la soluzione esatta. Ma avevo ripulito i codici indesiderati dal componente che ha iniziato a funzionare normalmente.
Jaison,

Vorrei sottolineare che nel tuo esempio, poiché il modo in cui il tuo elemento è progettato non si basa solo su uno stato univoco, la chiamata setState()anche con dati fittizi provoca il rendering dell'elemento in modo diverso, quindi direi di sì. Assolutamente dovrebbe provare a ri-renderizzare il tuo oggetto quando qualcosa potrebbe essere cambiato perché altrimenti la tua demo - supponendo che fosse il comportamento previsto - non funzionerebbe!
Tadhg McDonald-Jensen,

Potresti avere ragione @ TadhgMcDonald-Jensen - ma da quanto ho capito, React l'avrebbe reso la prima volta (poiché lo stato cambia dal nulla a qualcosa che la prima volta), quindi non ha mai dovuto eseguire nuovamente il rendering. Ovviamente, però, mi sbagliavo: siccome sembra che React richieda che tu scriva il tuo shouldComponentUpdatemetodo, che suppongo che una versione semplice di esso debba già essere inclusa in React stesso. Sembra che la versione predefinita inclusa in reag semplicemente ritorni true, il che forza il componente a eseguire nuovamente il rendering ogni volta.
Brad Parks,

Sì, ma deve solo eseguire nuovamente il rendering nel DOM virtuale, quindi modifica il DOM effettivo solo se il componente viene visualizzato in modo diverso. Gli aggiornamenti al DOM virtuale sono generalmente trascurabili (almeno rispetto alla modifica delle cose sullo schermo reale), quindi chiamare il rendering ogni volta che potrebbe essere necessario aggiornare, visto che nessun cambiamento è avvenuto non molto costoso e più sicuro di quanto si supponga che dovrebbe rendere lo stesso.
Tadhg McDonald-Jensen,

Risposte:


570

React esegue nuovamente il rendering di tutti i componenti e sottocomponenti ogni volta che viene chiamato setState?

Per impostazione predefinita, sì.

Esiste un metodo booleano shouldComponentUpdate (oggetto nextProps, oggetto nextState) , ogni componente ha questo metodo ed è responsabile di determinare "dovrebbe aggiornare il componente (eseguire la funzione di rendering )?" ogni volta che cambi stato o passi nuovi oggetti di scena dal componente genitore.

È possibile scrivere la propria implementazione del metodo shouldComponentUpdate per il proprio componente, ma l'implementazione predefinita restituisce sempre true, il che significa sempre rieseguire la funzione di rendering.

Citazione da documenti ufficiali http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate

Per impostazione predefinita, shouldComponentUpdate restituisce sempre true per evitare bug sottili quando lo stato è mutato sul posto, ma se stai attento a trattare sempre lo stato come immutabile e di sola lettura dai puntelli e dallo stato in render (), puoi sovrascrivere shouldComponentUpdate con un'implementazione che confronta i vecchi oggetti di scena e lo stato con i loro sostituti.

Parte successiva della tua domanda:

Se è così, perché? Ho pensato che l'idea di React fosse resa minima quanto basta - quando lo stato è cambiato.

Esistono due passaggi di ciò che potremmo chiamare "render":

  1. Rendering DOM virtuale: quando viene chiamato il metodo render , viene restituita una nuova struttura dom virtuale del componente. Come accennato in precedenza, questo metodo di rendering viene chiamato sempre quando si chiama setState () , poiché dovrebbeComponentUpdate restituisce sempre true per impostazione predefinita. Quindi, per impostazione predefinita, non c'è ottimizzazione qui in React.

  2. Rendering DOM nativo: React modifica i nodi DOM reali nel tuo browser solo se sono stati modificati nel DOM virtuale e quanto meno necessario: questa è la fantastica funzionalità di React che ottimizza la mutazione DOM reale e rende React veloce.


47
Grande spiegazione! E l'unica cosa che fa davvero brillare React in questo caso è che non cambia il vero DOM a meno che il DOM virtuale non sia stato modificato. Quindi, se la mia funzione di rendering restituisce la stessa cosa 2 volte di seguito, il DOM reale non viene cambiato affatto ... Grazie!
Brad Parks,

1
Penso che @tungd abbia ragione: vedi la sua risposta di seguito. È anche una questione di relazione genitore-figlio tra componenti. Ad esempio, se TimeInChild è un fratello di Main, il suo render () non verrà chiamato inutilmente.
gvlax,

2
Se il tuo stato contiene un oggetto javascript relativamente grande (~ 1k proprietà totali), che viene reso come un grande albero di componenti (~ 100 totale) ... dovresti lasciare che le funzioni di rendering costruiscano il dom virtuale, o dovresti, prima di impostare lo stato, confronta il nuovo stato con il vecchio manualmente e chiama solo setStatese rilevi che c'è una differenza? Se è così, come fare questo meglio - confrontare le stringhe json, costruire e confrontare gli hash degli oggetti, ...?
Vincent Sels,

1
@Petr, ma anche se reagisci ricostruisci il dom virtuale, se il vecchio dom virtuale è uguale al nuovo dom virtuale, il dom del browser non verrà toccato, vero?
Jaskey,

3
Inoltre, esamina l'utilizzo di React.PureComponent ( reazionijs.org/docs/react-api.html#reactpurecomponent ). Si aggiorna (ri-rendering) solo se lo stato o gli oggetti di scena del componente sono effettivamente cambiati. Attenzione, tuttavia, che il confronto è superficiale.
dibattente

105

No, React non esegue il rendering di tutto quando lo stato cambia.

  • Ogni volta che un componente è sporco (il suo stato è cambiato), quel componente e i suoi figli vengono ridistribuiti. Questo, in una certa misura, è di renderizzare il meno possibile. L'unica volta in cui non viene chiamato il rendering è quando un ramo viene spostato su un'altra radice, dove teoricamente non abbiamo bisogno di ri-renderizzare nulla. Nel tuo esempio, TimeInChildè un componente figlio di Main, quindi viene anche ridistribuito quando lo stato delle Mainmodifiche.

  • React non confronta i dati di stato. Quando setStateviene chiamato, contrassegna il componente come sporco (il che significa che deve essere ridistribuito). La cosa importante da notare è che, sebbene rendervenga chiamato il metodo del componente, il vero DOM viene aggiornato solo se l'output è diverso dall'albero DOM corrente (noto anche come differenza tra l'albero DOM virtuale e l'albero DOM del documento). Nel tuo esempio, anche se i statedati non sono cambiati, l'ora dell'ultima modifica è cambiata, rendendo Virtual DOM diverso dal DOM del documento, quindi perché l'HTML viene aggiornato.


Sì, questa è la risposta corretta. Come esperimento modifica l'ultima riga come React.renderComponent (<div> <Main /> <TimeInChild /> </div>, document.body) ; e rimuovi <TimeInChild /> dal corpo di render () del componente Main . Il render () di TimeInChild non verrà chiamato per impostazione predefinita perché non è più un figlio di Main .
gvlax,

Grazie, cose del genere sono un po 'complicate, ecco perché gli autori di React hanno raccomandato che il render()metodo sia "puro", indipendentemente dallo stato esterno.
tungd

3
@tungd, cosa significa some branch is moved to another root? Come si chiama branch? Come si chiama root?
Green

2
Preferisco davvero la risposta contrassegnata come quella corretta. Comprendo la tua interpretazione di "rendering" sul lato nativo, "reale", ... ma se lo guardi dal lato reattivo, devi dire che viene visualizzato di nuovo. Fortunatamente è abbastanza intelligente da determinare ciò che è realmente cambiato e aggiornare solo queste cose. Questa risposta potrebbe essere fonte di confusione per i nuovi utenti, prima dici di no e poi spieghi che le cose vengono rese ...
WiRa,

1
@tungd puoi per favore spiegare la domanda di Greenwhat does it mean some branch is moved to another root? What do you call branch? What do you call root?
madhu131313

7

Anche se è indicato in molte altre risposte qui, il componente dovrebbe:

  • implementare shouldComponentUpdateper il rendering solo quando lo stato o le proprietà cambiano

  • passare all'estensione di un PureComponent , che già implementa shouldComponentUpdateinternamente un metodo per confronti superficiali.

Ecco un esempio che utilizza shouldComponentUpdate, che funziona solo per questo semplice caso d'uso e scopi dimostrativi. Quando viene utilizzato, il componente non esegue più il rendering di se stesso su ogni clic e viene visualizzato quando viene visualizzato per la prima volta e dopo che è stato fatto clic una volta.

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    shouldComponentUpdate: function(nextProps, nextState) {
      if (this.state == null)
        return true;
  
      if (this.state.test == nextState.test)
        return false;
        
      return true;
  },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>



3

Un altro motivo per "aggiornamento perso" può essere il prossimo:

  • Se viene definito getDerivedStateFromProps statico , viene rieseguito in ogni processo di aggiornamento in base alla documentazione ufficiale https://reactjs.org/docs/react-component.html#updating .
  • quindi se quel valore di stato proviene da oggetti di scena all'inizio, viene sovrascritto in ogni aggiornamento.

Se il problema persiste, U può evitare di impostare lo stato durante l'aggiornamento, è necessario controllare il valore del parametro state in questo modo

static getDerivedStateFromProps(props: TimeCorrectionProps, state: TimeCorrectionState): TimeCorrectionState {
   return state ? state : {disable: false, timeCorrection: props.timeCorrection};
}

Un'altra soluzione è aggiungere una proprietà inizializzata allo stato e configurarla per la prima volta (se lo stato è inizializzato su un valore non nullo).


0

Non tutti i componenti.

il statecomponente in sembra la sorgente della cascata di stato dell'intera APP.

Quindi la modifica avviene da dove ha chiamato setState. L'albero di rendersallora viene chiamato da lì. Se hai utilizzato il componente puro, renderverrà ignorato.

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.