React mantiene l'ordine per gli aggiornamenti di stato?


140

So che React può eseguire aggiornamenti di stato in modo asincrono e in batch per l'ottimizzazione delle prestazioni. Pertanto non puoi mai fidarti dello stato da aggiornare dopo aver chiamato setState. Ma ci si può fidare Reagire ad aggiornare lo stato nello stesso ordine, come setStateviene chiamato per

  1. lo stesso componente?
  2. componenti diversi?

Valuta di fare clic sul pulsante nei seguenti esempi:

1. Esiste mai la possibilità che a sia falso eb che sia vero per:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Esiste mai la possibilità che a sia falso eb che sia vero per:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Tieni presente che si tratta di semplificazioni estreme del mio caso d'uso. Mi rendo conto di poterlo fare diversamente, ad esempio aggiornando contemporaneamente entrambi i parametri di stato nell'esempio 1, nonché eseguendo il secondo aggiornamento di stato in una richiamata al primo aggiornamento di stato nell'esempio 2. Tuttavia, questa non è la mia domanda, e mi interessa solo se esiste un modo ben definito in cui React esegue questi aggiornamenti di stato, nient'altro.

Ogni risposta supportata dalla documentazione è molto apprezzata.



3
non sembra una domanda insensata, puoi anche porre quella domanda sui problemi di github della pagina di reazione, dan abramov di solito è abbastanza utile lì. Quando avessi avuto domande così difficili, avrei chiesto e lui avrebbe risposto. Il cattivo è che questo tipo di problemi non sono condivisi pubblicamente come nei documenti ufficiali (in modo che anche altri possano accedervi facilmente). Sento anche che i documenti ufficiali di React mancano di un'ampia copertura di alcuni argomenti come l'argomento della tua domanda, ecc.
giorgim,

Ad esempio, prendi questo: github.com/facebook/react/issues/11793 , credo che le cose discusse in questo numero sarebbero utili per molti sviluppatori, ma quelle cose non sono nei documenti ufficiali, perché la gente di FB lo considera avanzato. Lo stesso vale per altre cose forse. Penserei che un articolo ufficiale intitolato qualcosa come "gestione statale in modo approfondito" o "insidie ​​della gestione statale" che esplora tutti i casi angolari di gestione statale come nella tua domanda non sarebbe male. forse possiamo spingere gli sviluppatori di FB ad estendere la documentazione con queste cose :)
giorgim,

C'è un link ad un grande articolo sul medium nella mia domanda. Dovrebbe coprire il 95% dei casi d'uso statali. :)
Michal,

2
@Michal ma quell'articolo non risponde ancora a questa domanda IMHO
giorgim,

Risposte:


336

Lavoro su React.

TLDR:

Ma puoi fidarti di React per aggiornare lo stato nello stesso ordine richiesto per setState

  • lo stesso componente?

Sì.

  • componenti diversi?

Sì.

Il ordine degli aggiornamenti è sempre rispettato. Se vedi uno stato intermedio "tra" o meno dipende dal fatto che tu sia dentro o meno in un batch.

Attualmente (React 16 e precedenti), solo gli aggiornamenti all'interno dei gestori di eventi React sono raggruppati per impostazione predefinita . Esiste un'API instabile per forzare il batch al di fuori dei gestori di eventi in rari casi in cui è necessario.

Nelle versioni future (probabilmente React 17 e successive), React raggrupperà tutti gli aggiornamenti per impostazione predefinita, quindi non dovrai pensarci. Come sempre, annunceremo eventuali modifiche al riguardo sul blog React e nelle note di rilascio.


La chiave per capire questo è che, indipendentemente dal numero di setState()chiamate in quanti componenti fai all'interno di un gestore di eventi React , alla fine dell'evento produrranno solo un nuovo rendering . Questo è fondamentale per una buona prestazione in applicazioni di grandi dimensioni perché se Childe Parentogni chiamata setState()durante la gestione di un evento click, non si desidera eseguire il rendering di nuovoChild due volte.

In entrambi i tuoi esempi, setState() chiamate avvengono all'interno di un gestore eventi React. Pertanto, vengono sempre uniti insieme alla fine dell'evento (e non si vede lo stato intermedio).

Gli aggiornamenti vengono sempre uniti in modo superficiale nell'ordine in cui si verificano . Quindi, se il primo aggiornamento è {a: 10}, il secondo è {b: 20}e il terzo è {a: 30}, lo stato di rendering sarà {a: 30, b: 20}. L'aggiornamento più recente alla stessa chiave di stato (ad es. Comea nel mio esempio) "vince sempre".

L' this.stateoggetto viene aggiornato quando eseguiamo nuovamente il rendering dell'interfaccia utente alla fine del batch. Pertanto, se è necessario aggiornare lo stato in base a uno stato precedente (come l'incremento di un contatore), è necessario utilizzare la setState(fn)versione funzionale che fornisce lo stato precedente, invece di leggere da this.state. Se sei curioso del ragionamento per questo, l'ho spiegato in modo approfondito in questo commento .


Nel tuo esempio, non vedremmo lo "stato intermedio" perché siamo all'interno di un gestore eventi React in cui è abilitato il batch (perché React "sa" quando stiamo uscendo da quell'evento).

Tuttavia, sia in React 16 che nelle versioni precedenti, non esiste ancora un batch per impostazione predefinita al di fuori dei gestori di eventi React . Quindi se nel tuo esempio avessimo un gestore di risposte AJAX invece di handleClick, ognuno setState()verrebbe elaborato immediatamente in tempo reale. In questo caso, sì, si dovrebbe vedere uno stato intermedio:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

Ci rendiamo conto che è scomodo che il comportamento sia diverso a seconda che tu sia o meno in un gestore di eventi . Ciò cambierà in una futura versione di React che impacchetterà tutti gli aggiornamenti per impostazione predefinita (e fornirà un'API opt-in per scaricare le modifiche in modo sincrono). Fino a quando non cambiamo il comportamento predefinito (potenzialmente in React 17), c'è un'API che puoi usare per forzare il batch :

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

I gestori di eventi internamente di React sono tutti inclusi, unstable_batchedUpdatesmotivo per cui sono raggruppati per impostazione predefinita. Si noti che il wrapping di un aggiornamento in unstable_batchedUpdatesdue volte non ha alcun effetto. Gli aggiornamenti vengono scaricati quando si esce dalla unstable_batchedUpdateschiamata più esterna .

Tale API è "instabile", nel senso che la rimuoveremo quando il batch è già abilitato per impostazione predefinita. Tuttavia, non lo rimuoveremo in una versione minore, quindi puoi tranquillamente fare affidamento su di esso fino a React 17 se è necessario forzare il batch in alcuni casi al di fuori dei gestori di eventi React.


Per riassumere, questo è un argomento confuso perché React solo i batch all'interno dei gestori di eventi per impostazione predefinita. Questo cambierà nelle versioni future e il comportamento sarà più semplice allora. Ma la soluzione non è quella di raggruppare meno , ma di raggruppare di più per impostazione predefinita. Questo è quello che faremo.


1
Un modo per "ottenere sempre l'ordine giusto" è quello di creare un oggetto temporaneo, assegnare i diversi valori (ad es. obj.a = true; obj.b = true) E poi alla fine basta this.setState(obj). Questo è sicuro indipendentemente dal fatto che ci si trovi all'interno di un gestore eventi o meno. Potrebbe essere un bel trucco se ti ritrovi spesso a commettere l'errore di impostare lo stato più volte al di fuori dei gestori di eventi.
Chris,

Quindi non possiamo davvero fare affidamento sul fatto che il batch sia limitato a un solo gestore di eventi - come hai chiarito, almeno perché non sarà presto così. Quindi dovremmo usare setState con la funzione di aggiornamento per ottenere l'accesso allo stato più recente, giusto? E se avessi bisogno di usare alcuni state.filter per creare un XHR per leggere alcuni dati e poi metterli in stato? Sembra che dovrò mettere un XHR con callback differito (e quindi un effetto collaterale) in un programma di aggiornamento. È considerata una buona pratica, allora?
Maksim Gumerov,

1
E a proposito, ciò significa anche che non dovremmo leggere affatto da this.state; l'unico modo ragionevole per leggere alcuni state.X è leggerlo nella funzione di aggiornamento, dal suo argomento. E anche scrivere su this.state non è sicuro. Allora perché consentire l'accesso a this.state? Forse dovrebbero fare domande autonome, ma soprattutto sto solo cercando di capire se ho avuto la spiegazione giusta.
Maksim Gumerov,

10
questa risposta dovrebbe essere aggiunta alla documentazione di reazioni
Deen John

2
Potresti chiarire in questo post se "Gestore eventi React" include componentDidUpdatee altri callback del ciclo di vita a partire da React 16? Grazie in anticipo!
Ivan

6

Questa è in realtà una domanda piuttosto interessante ma la risposta non dovrebbe essere troppo complicata. C'è questo fantastico articolo su supporto che ha una risposta.

1) Se lo fai

this.setState({ a: true });
this.setState({ b: true });

Non penso che ci sarà una situazione in cui aci sarà truee bsarà a falsecausa del batch .

Tuttavia, se bdipende daa allora, potrebbe effettivamente esserci una situazione in cui non si otterrebbe lo stato previsto.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

Dopo che tutte le chiamate sopra sono state elaborate this.state.value saranno state saranno 1, non 3 come ci si aspetterebbe.

Questo è menzionato nell'articolo: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

Questo ci darà this.state.value === 3


Che cosa succede se this.state.valueviene aggiornato sia nei gestori di eventi (dove setStateè in batch) che nei callback AJAX (dove nonsetState è in batch). Nei gestori di eventi userei il per essere sempre sicuro di aggiornare lo stato usando lo stato attualmente aggiornato fornito dalla funzione. Devo usare con una funzione di aggiornamento all'interno del codice del callback AJAX anche se so che non è batch? Potresti chiarire l'uso all'interno di un callback AJAX con o senza l'utilizzo di una funzione di aggiornamento? Grazie! updater functionsetStatesetState
tonix,

@Michal, ciao Michal volevo solo fare una semplice domanda, è vero che se abbiamo this.setState ({valore: 0}); this.setState ({value: this.state.value + 1}); il primo setState verrà ignorato e verrà eseguito solo il secondo setState?
Dickens,

@ Dickens Credo che entrambi setStatesarebbero stati eseguiti, ma l'ultimo avrebbe vinto.
Michal,

3

È possibile eseguire il batch di più chiamate durante lo stesso ciclo. Ad esempio, se si tenta di incrementare una quantità di articoli più di una volta nello stesso ciclo, ciò comporterà l'equivalente di:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html


3

come nel doc

setState () accoda le modifiche allo stato del componente e dice a React che questo componente e i suoi figli devono essere renderizzati nuovamente con lo stato aggiornato. Questo è il metodo principale utilizzato per aggiornare l'interfaccia utente in risposta ai gestori di eventi e alle risposte del server.

preformerà la modifica come in coda ( FIFO : First In First Out) la prima chiamata sarà la prima a preformare


ciao Ali, volevo solo fare una semplice domanda, è vero che se abbiamo this.setState ({valore: 0}); this.setState ({value: this.state.value + 1}); il primo setState verrà ignorato e verrà eseguito solo il secondo setState?
Dickens,
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.