Come fa un componente connesso a redux a sapere quando eseguire nuovamente il rendering?


94

Probabilmente mi sto perdendo qualcosa di molto ovvio e vorrei chiarirmi.

Ecco la mia comprensione.
In una componente di reazione ingenua, abbiamo states& props. L'aggiornamento statecon setStateesegue il rendering dell'intero componente. propssono per lo più di sola lettura e aggiornarli non ha senso.

In un componente di reazione che si iscrive a un redux store, tramite qualcosa di simile store.subscribe(render), ovviamente viene eseguito nuovamente il rendering ogni volta che il negozio viene aggiornato.

react-redux ha un helper connect()che inietta parte dell'albero degli stati (che è di interesse per il componente) e actionCreators per quanto propsriguarda il componente, di solito tramite qualcosa di simile

const TodoListComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

Ma con la consapevolezza che a setStateè essenziale per TodoListComponentreagire al cambiamento dell'albero di stato di redux (ri-rendering), non riesco a trovare alcun codice stateo setStaterelativo codice nel TodoListfile del componente. Si legge qualcosa del genere:

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

Qualcuno può indicarmi la giusta direzione su ciò che mi manca?

PS Sto seguendo l'esempio della lista delle cose da fare in bundle con il pacchetto redux .

Risposte:


116

La connectfunzione genera un componente wrapper che si iscrive al negozio. Quando viene inviata un'azione, viene notificato il callback del componente wrapper. Quindi esegue la mapStatefunzione e confronta in modo superficiale l'oggetto risultato di questa volta con l'oggetto risultato dell'ultima volta (quindi se si dovesse riscrivere un campo di redux store con lo stesso valore, non si attiverebbe un nuovo rendering). Se i risultati sono diversi, passa i risultati al tuo componente "reale" come oggetti di scena.

Dan Abramov ha scritto una fantastica versione semplificata di connectat ( connect.js ) che illustra l'idea di base, sebbene non mostri alcun lavoro di ottimizzazione. Ho anche collegamenti a una serie di articoli sulle prestazioni di Redux che discutono alcune idee correlate.

aggiornare

React-Redux v6.0.0 ha apportato alcune importanti modifiche interne al modo in cui i componenti connessi ricevono i dati dallo store.

Come parte di ciò, ho scritto un post che spiega come funzionano l' connectAPI e i suoi interni e come sono cambiati nel tempo:

Redux idiomatico: la storia e l'implementazione di React-Redux


Grazie! Sei la stessa persona che mi ha aiutato l'altro giorno @ HN - news.ycombinator.com/item?id=12307621 con link utili? Piacere di conoscerti :)
Joe Lewis

4
Yup, che sono io :) spendo modo troppo tempo alla ricerca di discussioni su Redux on-line - Reddit, HN, SO, Medium, e vari altri luoghi. Felice di aiutare! (Inoltre, Cordiali saluti, consiglio vivamente i canali di chat di Reactiflux su Discord. Molte persone sono in giro e rispondono alle domande. Di solito sono online lì la sera US EST. Il link per l' invito è su REeactiflux.com .)
markerikson

Supponendo che questo abbia risposto alla domanda, ti dispiace accettare la risposta? :)
markerikson

Sta confrontando gli oggetti stessi o le proprietà di quegli oggetti prima / dopo? Se è il secondo, controlla solo il primo livello, è questo che intendi per "superficiale?"
Andy

Sì, un mezzo "poco profondo controllo di uguaglianza" per confrontare i primi campi di livello di ogni oggetto: prev.a === current.a && prev.b === current.b && ...... Ciò presuppone che qualsiasi modifica ai dati comporterà nuovi riferimenti, il che significa che sono necessari aggiornamenti dei dati immutabili invece della mutazione diretta.
markerikson

14

La mia risposta è un po 'fuori campo. Fa luce su un problema che mi ha portato a questo post. Nel mio caso sembrava che l'app non fosse di nuovo rendering, anche se ha ricevuto nuovi oggetti di scena.
Gli sviluppatori di React hanno avuto una risposta a questa domanda spesso posta in modo tale che se lo (negozio) fosse stato modificato, il 99% delle volte questa è la ragione per cui non reagirebbe nuovamente. Eppure niente sull'altro 1%. La mutazione non era il caso qui.


TLDR;

componentWillReceivePropsè il modo in cui statepuò essere sincronizzato con il nuovo props.

Caso Bordo: Una volta stategli aggiornamenti, quindi l'applicazione non ri-renderizzare!


Si scopre che se la tua app utilizza solo stateper visualizzare i suoi elementi, propspuò aggiornarsi, ma statenon lo farà, quindi nessun riesame.

Avevo stateche dipendeva da propsricevuto da Redux store. I dati di cui avevo bisogno non erano ancora nel negozio, quindi li ho recuperati da componentDidMount, come è corretto. Ho recuperato gli oggetti di scena, quando il mio riduttore ha aggiornato il negozio, perché il mio componente è collegato tramite mapStateToProps. Ma la pagina non è stata visualizzata e lo stato era ancora pieno di stringhe vuote.

Un esempio di ciò è dire che un utente ha caricato una pagina di "modifica articolo" da un URL salvato. Hai accesso a postIddall'URL, ma le informazioni non sono storeancora presenti, quindi le recuperi. Gli elementi sulla tua pagina sono componenti controllati, quindi tutti i dati che stai visualizzando sono contenuti state.

Utilizzando redux, i dati sono stati recuperati, l'archivio è stato aggiornato e il componente è stato modificato connect, ma l'app non ha riflesso le modifiche. A uno sguardo più attento, propssono stati ricevuti, ma l'app non è stata aggiornata. statenon è stato aggiornato.

Bene, propsaggiornerà e propagherà, ma statenon lo farà. Devi dire specificamente statedi aggiornare.

Non puoi farlo render()e sono componentDidMountgià finiti i suoi cicli.

componentWillReceivePropsè dove aggiorni le stateproprietà che dipendono da un propvalore modificato .

Utilizzo di esempio:

componentWillReceiveProps(nextProps){
  if (this.props.post.category !== nextProps.post.category){
    this.setState({
      title: nextProps.post.title,
      body: nextProps.post.body,
      category: nextProps.post.category,
    })
  }      
}

Devo ringraziare questo articolo che mi ha illuminato sulla soluzione che dozzine di altri post, blog e repo non hanno menzionato. Chiunque abbia avuto difficoltà a trovare una risposta a questo problema evidentemente oscuro, eccola:

Metodi del ciclo di vita dei componenti ReactJs - Un'immersione profonda

componentWillReceivePropsè dove ti aggiornerai stateper restare sincronizzato con gli propsaggiornamenti.

Una volta che stategli aggiornamenti, quindi campi a seconda state do ri-renderizzare!


3
D'ora in React 16.3poi, dovresti usare il getDerivedStateFromPropsinvece di componentWillReceiveProps. Ma in realtà, non dovresti nemmeno fare tutto ciò come articolato qui
kyw

non funziona, ogni volta che lo stato è cambiato, componentWillReceiveProps funzionerà così tanti casi che provo che non funziona. In particolare, oggetti di scena nidificati o complessi a più livelli, quindi devo assegnare il componente con un ID e attivare la modifica per esso e aggiornare lo stato per rieseguire il rendering di nuovi dati
May'Habit

0

Questa risposta è una sintesi dell'articolo di Brian Vaughn intitolato You Probably Don't Need Derived State (7 giugno 2018).

Lo stato derivante dagli oggetti di scena è un anti-pattern in tutte le sue forme. Compreso l'utilizzo del vecchio componentWillReceivePropse del nuovo getDerivedStateFromProps.

Invece di derivare lo stato dagli oggetti di scena, considera le seguenti soluzioni.

Due consigli sulle migliori pratiche

Raccomandazione 1. Componente completamente controllato
function EmailInput(props) {
  return <input onChange={props.onChange} value={props.email} />;
}
Raccomandazione 2. Componente completamente non controllato con chiave
// parent class
class EmailInput extends Component {
  state = { email: this.props.defaultEmail };

  handleChange = event => {
    this.setState({ email: event.target.value });
  };

  render() {
    return <input onChange={this.handleChange} value={this.state.email} />;
  }
}

// child instance
<EmailInput
  defaultEmail={this.props.user.email}
  key={this.props.user.id}
/>

Due alternative se, per qualsiasi motivo, i consigli non funzionano per la tua situazione.

Alternativa 1: reimposta il componente non controllato con un prop ID
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}
Alternativa 2: reimposta il componente non controllato con un metodo di istanza
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail
  };

  resetEmailForNewUser(newEmail) {
    this.setState({ email: newEmail });
  }

  // ...
}

-2

come so, l'unica cosa che fa il redux, al cambio dello stato del negozio sta chiamando componentWillRecieveProps se il tuo componente dipende da uno stato mutato e quindi dovresti forzare il tuo componente ad aggiornare il suo è così

1-store State change-2-call (componentWillRecieveProps (() => {3-component state change}))

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.