Puoi forzare il rendering di un componente React senza chiamare setState?


690

Ho un oggetto osservabile esterno (al componente) su cui voglio ascoltare le modifiche. Quando l'oggetto viene aggiornato, emette eventi di modifica e quindi voglio eseguire nuovamente il rendering del componente quando viene rilevata una modifica.

Con un livello superiore React.renderquesto è stato possibile, ma all'interno di un componente non funziona (il che ha un senso poiché il rendermetodo restituisce solo un oggetto).

Ecco un esempio di codice:

export default class MyComponent extends React.Component {

  handleButtonClick() {
    this.render();
  }

  render() {
    return (
      <div>
        {Math.random()}
        <button onClick={this.handleButtonClick.bind(this)}>
          Click me
        </button>
      </div>
    )
  }
}

Fare clic sul pulsante chiama internamente this.render(), ma non è ciò che causa effettivamente il rendering (puoi vederlo in azione perché il testo creato da {Math.random()}non cambia). Tuttavia, se chiamo semplicemente this.setState()invece di this.render(), funziona benissimo.

Quindi suppongo che la mia domanda sia: i componenti di React devono avere uno stato per poter eseguire nuovamente il rendering? C'è un modo per forzare l'aggiornamento del componente su richiesta senza cambiare lo stato?


7
la risposta accettata dice che this.forceUpdate()è la soluzione giusta mentre il resto di tutte le risposte e diversi commenti sono contrari all'utilizzo forceUpdate(). Andrà quindi bene dire che la domanda non ha ancora ottenuto una soluzione / risposta adeguata?
adi,

7
La risposta esclusa ha risposto alla mia domanda in quel momento. È tecnicamente la risposta che stavo cercando e penso ancora la risposta giusta. Le altre risposte che penso siano buone informazioni supplementari per le persone con la stessa domanda di cui essere consapevoli.
Philip Walton,

È interessante notare che NON È NECESSARIO NULLA NELLO STATO a parte l'inizializzazione su un oggetto semplice, quindi la chiamata a this.setState ({}) attiva solo un nuovo rendering. React è fantastico ma anche strano a volte. Pertanto, è possibile eseguire il ciclo diretto dei dati di un archivio quando si attiva una modifica senza tubature aggiuntive o si preoccupano dei dati per istanza del componente.
Jason Sebring,

Nel complesso, direi di sì. Se stai usando l'aggiornamento forzato, questo è per l'aggiornamento di componenti in cui potrebbero dipendere da modifiche esterne alla gestione dello stato della tua app. Non riesco a pensare a un buon esempio di questo. Utile sapere però.
Daniel,

Risposte:


765

Nel tuo componente, puoi chiamare this.forceUpdate()per forzare un rerender.

Documentazione: https://facebook.github.io/react/docs/component-api.html


170
Un altro modo è this.setState (this.state);
kar

25
L'uso di forceupdate è sconsigliato, vedere l'ultima risposta.
Varand Pezeshkian,

42
Sebbene this.forceUpdate()non sia un'ottima soluzione al problema dei richiedenti, è la risposta corretta alla domanda posta nel titolo. Anche se generalmente dovrebbe essere evitato, ci sono situazioni in cui forceUpdate è una buona soluzione.
ArneHugo,

8
@kar In realtà, è possibile implementare una logica aggiuntiva nel shouldComponentUpdate()rendere la soluzione inutile.
Qwerty,

4
Dimensione massima dello stack di chiamate superata
IntoTheDeep,

326

forceUpdatedovrebbe essere evitato perché si discosta dalla mentalità di React. I documenti di React citano un esempio di quando forceUpdatepotrebbe essere usato:

Per impostazione predefinita, quando cambiano lo stato o gli oggetti di scena del componente, il componente verrà sottoposto a rendering nuovamente. Tuttavia, se questi cambiano implicitamente (es: i dati in profondità all'interno di un oggetto cambiano senza cambiare l'oggetto stesso) o se il metodo render () dipende da alcuni altri dati, puoi dire a React che deve rieseguire render () chiamando aggiornamento forzato().

Tuttavia, vorrei proporre l'idea che anche con oggetti profondamente annidati, forceUpdatenon è necessario. Utilizzando un'origine dati immutabile, le modifiche al monitoraggio diventano economiche; una modifica comporterà sempre un nuovo oggetto, quindi dobbiamo solo verificare se il riferimento all'oggetto è cambiato. Puoi utilizzare la libreria Immutable JS per implementare oggetti di dati immutabili nella tua app.

Normalmente dovresti cercare di evitare tutti gli usi di forceUpdate () e leggere solo da this.props e this.state in render (). Ciò rende il componente "puro" e l'applicazione molto più semplice ed efficiente. aggiornamento forzato()

La modifica della chiave dell'elemento che si desidera riprodurre nuovamente funzionerà. Imposta il sostegno chiave sul tuo elemento tramite state e quindi quando vuoi aggiornare set state per avere una nuova chiave.

<Element key={this.state.key} /> 

Quindi si verifica una modifica e si reimposta la chiave

this.setState({ key: Math.random() });

Voglio notare che questo sostituirà l'elemento su cui sta cambiando la chiave. Un esempio di dove questo potrebbe essere utile è quando si dispone di un campo di input del file che si desidera ripristinare dopo un caricamento dell'immagine.

Mentre la vera risposta alla domanda del PO sarebbe forceUpdate()che ho trovato questa soluzione utile in diverse situazioni. Voglio anche notare che se ti ritrovi a utilizzare forceUpdatepotresti voler rivedere il tuo codice e vedere se c'è un altro modo di fare le cose.

NOTA 1-9-2019:

Quanto sopra (cambiando la chiave) sostituirà completamente l'elemento. Se ti ritrovi ad aggiornare la chiave per apportare modifiche, probabilmente hai un problema in qualche altra parte del codice. L'uso di Math.random()in chiave ricrea l'elemento con ogni rendering. NON consiglierei di aggiornare la chiave in questo modo, in quanto reagisce usando la chiave per determinare il modo migliore per eseguire nuovamente il rendering delle cose.


16
Sarei interessato a sapere perché questo ha ottenuto un downvote? Mi sono sbattuto la testa nel tentativo di convincere gli screen reader a rispondere agli avvisi di aria e questa è l'unica tecnica che ho scoperto che funziona in modo affidabile. Se nella tua IU c'è qualcosa che genera lo stesso avviso ogni volta che viene cliccato, per impostazione predefinita il reagente non esegue il rendering del DOM in modo che lo screen reader non annunci la modifica. L'impostazione di una chiave univoca fa funzionare. Forse il downvote è stato perché implica ancora l'impostazione dello stato. Ma forzare un nuovo rendering su DOM impostando la chiave è d'oro!
Martin Bayly

2
Mi è piaciuta molto questa risposta perché - 1: l'uso di forceupdate () è sconsigliato e 2: in questo modo è molto utile per i componenti di terze parti che sono stati installati tramite npm, il che significa che non sono i propri componenti. Ad esempio React-resizable-and-mobile è un componente di terze parti che utilizzo, ma non reagisce al mio setstate di componenti. questa è un'ottima soluzione.
Varand Pezeshkian,

81
Questo è un trucco e un abuso di key. Innanzitutto, l'intenzione non è chiara. Un lettore del tuo codice dovrà lavorare sodo per capire perché lo stai utilizzando in keyquesto modo. Secondo, questo non è più puro di forceUpdate. "Reazione pura" significa che la presentazione visiva del componente dipende al 100% dal suo stato, quindi se si modifica uno stato, si aggiorna. Se tuttavia, hai alcuni oggetti profondamente annidati (lo scenario in cui i forceUpdatedocumenti citano un motivo per usarlo), allora l'utilizzo forceUpdatelo chiarisce. Terzo, Math.random()è ... casuale. Teoricamente, potrebbe generare lo stesso numero casuale.
Atomkirk,

8
@xtraSimplicity Non posso essere d'accordo sul fatto che ciò sia conforme al modo di fare le cose di React. forceUpdateè il modo di reagire per farlo.
Rob Grant,

3
@Robert Grant, guardando indietro, sono d'accordo. La complessità aggiunta non ne vale davvero la pena.
XtraSimplicity,

80

In realtà, forceUpdate()è l'unica soluzione corretta in quanto setState()potrebbe non innescare un nuovo rendering se viene implementata una logica aggiuntiva shouldComponentUpdate()o quando semplicemente ritorna false.

aggiornamento forzato()

La chiamata forceUpdate()comporterà la render()chiamata sul componente, saltando shouldComponentUpdate(). Di Più...

setState ()

setState()attiverà sempre un nuovo rendering a meno che non sia implementata la logica di rendering condizionale shouldComponentUpdate(). Di Più...


forceUpdate() può essere chiamato dall'interno del componente da this.forceUpdate()


Hook: Come posso forzare il rendering del componente con gli hook in React?


A proposito: stai mutando lo stato o le tue proprietà nidificate non si propagano?


1
una cosa interessante da notare è che le asserzioni di stato al di fuori di setState come this.state.foo = 'bar' non attiveranno il metodo del ciclo di vita del rendering
lfender6445

4
@ lfender6445 Anche l'assegnazione dello stato direttamente this.stateall'esterno del costruttore dovrebbe generare un errore. Potrebbe non averlo fatto nel 2016; ma sono abbastanza sicuro che lo faccia ora.
Tyler,

Ho aggiunto alcuni collegamenti per aggirare le assegnazioni dirette di stato.
Qwerty,

36

Ho evitato forceUpdatefacendo quanto segue

MODO ERRATO : non utilizzare l'indice come chiave

this.state.rows.map((item, index) =>
   <MyComponent cell={item} key={index} />
)

MODO CORRETTO : Usa l'id dei dati come chiave, può essere un po 'di guida ecc

this.state.rows.map((item) =>
   <MyComponent item={item} key={item.id} />
)

quindi facendo tale miglioramento del codice il tuo componente sarà UNICO e renderizzato in modo naturale


this.state.rows.map((item) => <MyComponent item={item} key={item.id} /> )
Tal,

Grazie mille per avermi spinto nella giusta direzione. L'uso dell'indice come chiave era davvero il mio problema.
RoiEX,

Per giustificare il mio voto negativo, nonostante sia una buona pratica utilizzare questo approccio, questa risposta non è correlata alla domanda.
Micnic,

34

Quando si desidera comunicare due componenti di React, che non sono vincolati da una relazione (padre-figlio), è consigliabile utilizzare Flux o architetture simili.

Quello che vuoi fare è ascoltare i cambiamenti dell'archivio componenti osservabile , che contiene il modello e la sua interfaccia, e salvare i dati che fanno sì che il rendering cambi come statein MyComponent. Quando l'archivio invia i nuovi dati, si modifica lo stato del componente, che attiva automaticamente il rendering.

Normalmente dovresti cercare di evitare l'uso forceUpdate(). Dalla documentazione:

Normalmente dovresti cercare di evitare tutti gli usi di forceUpdate () e leggere solo da this.props e this.state in render (). Questo rende la tua applicazione molto più semplice ed efficiente


2
Qual è lo stato della memoria di uno stato del componente? Se ho cinque componenti in una pagina e tutti ascoltano un singolo negozio, ho i dati cinque volte in memoria o sono riferimenti? E tutti quegli ascoltatori non si sommano velocemente? Perché è meglio "gocciolare" piuttosto che passare semplicemente i dati al bersaglio?
AJFarkas,

5
In realtà i consigli sono di passare i dati del negozio ai componenti del componente e di utilizzare lo stato del componente solo per cose come lo stato di scorrimento e altre cose minori specifiche dell'interfaccia utente. Se si utilizza Redux (lo consiglio), è possibile utilizzare la funzione di connessione da react-reduxper mappare automaticamente lo stato del negozio ai puntelli dei componenti ogni volta che è necessario sulla base di una funzione di mappatura fornita.
Stijn de Witt,

1
@AJFarka lo stato verrebbe assegnato ai dati di un negozio che è comunque solo un puntatore ad esso, quindi la memoria non è un problema a meno che non si stia clonando.
Jason Sebring,

4
"forceUpdate () dovrebbe essere sempre evitato" non è corretto. La documentazione dice chiaramente: "Normalmente dovresti cercare di evitare tutti gli usi di forceUpdate ()". Esistono casi d'uso validi diforceUpdate
AJP

2
@AJP hai perfettamente ragione, era un pregiudizio personale a parlare :) Ho modificato la risposta.
gcedo,

18

usa ganci o HOC fai la tua scelta

Utilizzando gli hook o il modello HOC (componente di ordine superiore) , puoi avere aggiornamenti automatici quando cambiano i tuoi negozi. Questo è un approccio molto leggero senza un framework.

modo di utilizzare HookStore per gestire gli aggiornamenti del negozio

interface ISimpleStore {
  on: (ev: string, fn: () => void) => void;
  off: (ev: string, fn: () => void) => void;
}

export default function useStore<T extends ISimpleStore>(store: T) {
  const [storeState, setStoreState] = useState({store});
  useEffect(() => {
    const onChange = () => {
      setStoreState({store});
    }
    store.on('change', onChange);
    return () => {
      store.off('change', onChange);
    }
  }, []);
  return storeState.store;
}

withStores HOC gestisce gli aggiornamenti del negozio

export default function (...stores: SimpleStore[]) {
  return function (WrappedComponent: React.ComponentType<any>) {
    return class WithStore extends PureComponent<{}, {lastUpdated: number}> {
      constructor(props: React.ComponentProps<any>) {
        super(props);
        this.state = {
          lastUpdated: Date.now(),
        };
        this.stores = stores;
      }

      private stores?: SimpleStore[];

      private onChange = () => {
        this.setState({lastUpdated: Date.now()});
      };

      componentDidMount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            // each store has a common change event to subscribe to
            store.on('change', this.onChange);
          });
      };

      componentWillUnmount = () => {
        this.stores &&
          this.stores.forEach((store) => {
            store.off('change', this.onChange);
          });
      };

      render() {
        return (
          <WrappedComponent
            lastUpdated={this.state.lastUpdated}
            {...this.props}
          />
        );
      }
    };
  };
}

Classe SimpleStore

import AsyncStorage from '@react-native-community/async-storage';
import ee, {Emitter} from 'event-emitter';

interface SimpleStoreArgs {
  key?: string;
  defaultState?: {[key: string]: any};
}

export default class SimpleStore {
  constructor({key, defaultState}: SimpleStoreArgs) {
    if (key) {
      this.key = key;
      // hydrate here if you want w/ localState or AsyncStorage
    }
    if (defaultState) {
      this._state = {...defaultState, loaded: false};
    } else {
      this._state = {loaded: true};
    }
  }
  protected key: string = '';
  protected _state: {[key: string]: any} = {};
  protected eventEmitter: Emitter = ee({});
  public setState(newState: {[key: string]: any}) {
    this._state = {...this._state, ...newState};
    this.eventEmitter.emit('change');
    if (this.key) {
      // store on client w/ localState or AsyncStorage
    }
  }
  public get state() {
    return this._state;
  }
  public on(ev: string, fn:() => void) {
    this.eventEmitter.on(ev, fn);
  }
  public off(ev: string, fn:() => void) {
    this.eventEmitter.off(ev, fn);
  }
  public get loaded(): boolean {
    return !!this._state.loaded;
  }
}

Come usare

Nel caso di ganci:

// use inside function like so
const someState = useStore(myStore);
someState.myProp = 'something';

Nel caso di HOC:

// inside your code get/set your store and stuff just updates
const val = myStore.myProp;
myOtherStore.myProp = 'something';
// return your wrapped component like so
export default withStores(myStore)(MyComponent);

ASSICURARSI Per esportare i tuoi negozi come singleton per ottenere i vantaggi del cambiamento globale in questo modo:

class MyStore extends SimpleStore {
  public get someProp() {
    return this._state.someProp || '';
  }
  public set someProp(value: string) {
    this.setState({...this._state, someProp: value});
  }
}
// this is a singleton
const myStore = new MyStore();
export {myStore};

Questo approccio è piuttosto semplice e funziona per me. Lavoro anche in team di grandi dimensioni e utilizzo Redux e MobX e trovo che anche quelli siano buoni ma solo un sacco di piatti. Personalmente mi piace il mio approccio personale perché ho sempre odiato molto codice per qualcosa che può essere semplice quando ne hai bisogno.


2
Penso che ci sia un errore di battitura nel tuo esempio di classe Conservare nel metodo di aggiornamento dover scrivere la prima riga del metodo come segue: this._data = {...this._data, ...newData}.
Norbert,

2
React scoraggia l'eredità a favore della composizione. reazionejs.org/docs/composition-vs-inheritance.html
Fred

1
Mi chiedo solo chiarezza / leggibilità, in che modo this.setState (this.state) potrebbe essere migliore di this.forceUpdate ()?
Zoman,

17

Quindi suppongo che la mia domanda sia: i componenti di React devono avere uno stato per poter eseguire nuovamente il rendering? C'è un modo per forzare l'aggiornamento del componente su richiesta senza cambiare lo stato?

Le altre risposte hanno cercato di illustrare come potresti, ma il punto è che non dovresti . Anche la soluzione caotica di cambiare la chiave manca il punto. Il potere di React sta rinunciando al controllo della gestione manuale di quando qualcosa dovrebbe essere visualizzato, e invece riguarda solo te stesso su come qualcosa dovrebbe mappare sugli input. Quindi fornire flusso di input.

Se devi forzare manualmente il re-rendering, quasi sicuramente non stai facendo qualcosa di giusto.


@ art-solopov, a quale problema ti riferisci? Quali documenti li presentano? E supponendo che ti riferisca a oggetti nidificati nello stato, la risposta è utilizzare una struttura diversa per memorizzare il tuo stato. Questo è chiaramente un modo non ottimale di gestire lo stato in React. Inoltre, non dovresti gestire lo stato separatamente. Stai perdendo uno dei maggiori vantaggi di React se non lavori con il sistema di gestione dello stato. La gestione manuale degli aggiornamenti è un anti-pattern.
Kyle Baker,

Per favore, puoi controllare questa domanda stackoverflow.com/questions/61277292/… ?
HuLu ViCa,

4

Potresti farlo in un paio di modi:

1. Usa il forceUpdate()metodo:

Ci sono alcuni problemi che possono verificarsi durante l'utilizzo del forceUpdate()metodo. Un esempio è che ignora il shouldComponentUpdate()metodo e shouldComponentUpdate()restituirà nuovamente la vista indipendentemente dal fatto che restituisca false. Per questo motivo l'uso di forceUpdate () dovrebbe essere evitato quando possibile.

2. Passando this.state al setState()metodo

La seguente riga di codice risolve il problema con l'esempio precedente:

this.setState(this.state);

In realtà tutto ciò che sta facendo è sovrascrivere lo stato corrente con lo stato corrente che innesca un nuovo rendering. Questo non è necessariamente il modo migliore per fare le cose, ma supera alcuni dei problemi che potresti incontrare usando il metodo forceUpdate ().


2
Poiché setState è in batch, è probabilmente più sicuro fare:this.setState(prevState => prevState);
Mark Galloway il

1
Non sono proprio sicuro del perché sia ​​un "problema tecnico". Il nome del metodo ('forceUpdate') non potrebbe essere più chiaro: forzare sempre l'aggiornamento.
Zoman,

4

Esistono alcuni modi per eseguire nuovamente il rendering del componente:

La soluzione più semplice è utilizzare il metodo forceUpdate ():

this.forceUpdate()

Un'altra soluzione è quella di creare una chiave non utilizzata nello stato (nonUsedKey) e chiamare la funzione setState con l'aggiornamento di questo nonUsedKey:

this.setState({ nonUsedKey: Date.now() } );

O riscrivi tutto lo stato corrente:

this.setState(this.state);

Il cambio dei puntelli fornisce anche il rendering del componente.


3

Per completezza, è possibile raggiungere questo obiettivo anche nei componenti funzionali:

const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
// ...
forceUpdate();

Oppure, come gancio riutilizzabile:

const useForceUpdate = () => {
  const [, updateState] = useState();
  return useCallback(() => updateState({}), []);
}
// const forceUpdate = useForceUpdate();

Vedi: https://stackoverflow.com/a/53215514/2692307

Si noti che l'utilizzo di un meccanismo di aggiornamento forzato è ancora una cattiva pratica in quanto va contro la mentalità di reazione, quindi dovrebbe essere comunque evitato.


2

Possiamo usare this.forceUpdate () come di seguito.

       class MyComponent extends React.Component {



      handleButtonClick = ()=>{
          this.forceUpdate();
     }


 render() {

   return (
     <div>
      {Math.random()}
        <button  onClick={this.handleButtonClick}>
        Click me
        </button>
     </div>
    )
  }
}

 ReactDOM.render(<MyComponent /> , mountNode);

L'elemento 'Math.random' nel DOM viene aggiornato solo anche se si utilizza setState per eseguire il rendering di nuovo del componente.

Tutte le risposte qui sono corrette integrando la domanda per la comprensione .. come sappiamo per ri-renderizzare un componente senza usare setState ({}) è usando forceUpdate ().

Il codice precedente viene eseguito con setState come di seguito.

 class MyComponent extends React.Component {



             handleButtonClick = ()=>{
                this.setState({ });
              }


        render() {
         return (
  <div>
    {Math.random()}
    <button  onClick={this.handleButtonClick}>
      Click me
    </button>
  </div>
)
  }
 }

ReactDOM.render(<MyComponent /> , mountNode);

1

Solo un'altra risposta per eseguire il backup della risposta accettata :-)

React scoraggia l'uso di forceUpdate()perché generalmente hanno un approccio molto "questo è l'unico modo di farlo" verso la programmazione funzionale. Questo va bene in molti casi, ma molti sviluppatori React hanno uno sfondo OO e, con quell'approccio, è perfettamente OK ascoltare un oggetto osservabile.

E se lo fai, probabilmente sai che DEVI ripetere il rendering quando l'osservabile "spara", e come tale, DOVREBBE usare forceUpdate()ed è in realtà un vantaggio che shouldComponentUpdate()NON è coinvolto qui.

Strumenti come MobX, che utilizza un approccio OO, lo sta effettivamente facendo sotto la superficie (in realtà MobX chiama render()direttamente)


0

Ho trovato il modo migliore per evitare forceUpdate (). Un modo per forzare il re-rendering è aggiungere la dipendenza di render () su una variabile esterna temporanea e modificare il valore di quella variabile come e quando necessario.

Ecco un esempio di codice:

class Example extends Component{
   constructor(props){
      this.state = {temp:0};

      this.forceChange = this.forceChange.bind(this);
   }

   forceChange(){
      this.setState(prevState => ({
          temp: prevState.temp++
      })); 
   }

   render(){
      return(
         <div>{this.state.temp &&
             <div>
                  ... add code here ...
             </div>}
         </div>
      )
   }
}

Chiamare this.forceChange () quando è necessario forzare il rendering.


0

ES6 - Includo un esempio che mi è stato utile:

In una "breve istruzione if" puoi passare una funzione vuota in questo modo:

isReady ? ()=>{} : onClick

Questo sembra essere l'approccio più breve.

()=>{}



0

Un altro modo sta chiamando setState, E preservare Stato:

this.setState(prevState=>({...prevState}));


0

forceUpdate (), ma ogni volta che ho mai sentito qualcuno parlarne, è stato seguito che non dovresti mai usarlo.


-1

È possibile utilizzare forceUpdate () per un controllo più dettagliato ( forceUpdate () ).


1
Per curiosità, perché lasciare questa risposta se per 4 anni le persone l'hanno già offerta come possibile soluzione in questo thread?
Byron Coetsee,
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.