Come forzare il rimontaggio sui componenti React?


103

Diciamo che ho un componente di visualizzazione che ha un rendering condizionale:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

MyInput ha un aspetto simile a questo:

class MyInput extends React.Component {

    ...

    render(){
        return (
            <div>
                <input name={this.props.name} 
                    ref="input" 
                    type="text" 
                    value={this.props.value || null}
                    onBlur={this.handleBlur.bind(this)}
                    onChange={this.handleTyping.bind(this)} />
            </div>
        );
    }
}

Diciamo che employedè vero. Ogni volta che lo cambio su false e l'altra vista esegue il rendering, solo unemployment-durationviene reinizializzato. Inoltre unemployment-reasonviene precompilato con il valore da job-title(se è stato fornito un valore prima della modifica della condizione).

Se cambio il markup nella seconda routine di rendering in qualcosa di simile:

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Sembra che tutto funzioni bene. Sembra che React non riesca a distinguere tra "titolo professionale" e "motivo della disoccupazione".

Per favore dimmi cosa sto sbagliando ...


1
Cosa c'è data-reactidsu ciascuno degli elementi MyInput(o input, come visto in DOM) prima e dopo il employedpassaggio?
Chris il

@ Chris Non sono riuscito a specificare che il componente MyInput rende l'input avvolto in un file <div>. Gli data-reactidattributi sembrano essere diversi sia nel div di avvolgimento che nel campo di input. job-titleinput ottiene id data-reactid=".0.1.1.0.1.0.1", mentre unemployment-reasoninput ottiene iddata-reactid=".0.1.1.0.1.2.1"
o01

1
e di cosa unemployment-duration?
Chris il

@ Chris Scusa, ho parlato troppo presto. Nel primo esempio (senza lo span "diff me") gli reactidattributi sono identici su job-titlee unemployment-reason, mentre nel secondo esempio (con lo span diff) sono diversi.
o01

@ Chris per unemployment-durationl' reactidattributo è sempre unico.
o01

Risposte:


108

Quello che probabilmente sta accadendo è che React pensa che solo una MyInput( unemployment-duration) viene aggiunta tra i rendering. In quanto tale, job-titlenon viene mai sostituito con unemployment-reason, motivo per cui i valori predefiniti vengono scambiati.

Quando React fa il diff, determinerà quali componenti sono nuovi e quali sono vecchi in base alla loro keyproprietà. Se nel codice non viene fornita alcuna chiave di questo tipo, ne genererà una propria.

Il motivo per cui l'ultimo frammento di codice che fornisci funziona è perché React ha essenzialmente bisogno di cambiare la gerarchia di tutti gli elementi sotto il genitore dive credo che ciò attiverebbe un nuovo rendering di tutti i figli (motivo per cui funziona). Se avessi aggiunto il spanin fondo invece che in alto, la gerarchia degli elementi precedenti non cambierebbe, e quegli elementi non sarebbero stati riprodotti nuovamente (e il problema persisteva).

Ecco cosa dice la documentazione ufficiale di React :

La situazione diventa più complicata quando i bambini vengono mescolati (come nei risultati di ricerca) o se nuovi componenti vengono aggiunti all'inizio dell'elenco (come negli stream). In questi casi in cui l'identità e lo stato di ogni figlio devono essere mantenuti attraverso i passaggi di rendering, è possibile identificare in modo univoco ogni figlio assegnandogli una chiave.

Quando React riconcilia i figli con chiave, assicurerà che ogni figlio con chiave verrà riordinato (invece di essere distrutto) o distrutto (invece di riutilizzato).

Dovresti essere in grado di risolvere questo problema fornendo keytu stesso un elemento unico al genitore divoa tutti gli MyInputelementi.

Per esempio:

render(){
    if (this.state.employed) {
        return (
            <div key="employed">
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div key="notEmployed">
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

O

render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput key="title" ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <MyInput key="reason" ref="unemployment-reason" name="unemployment-reason" />
                <MyInput key="duration" ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Ora, quando React esegue il diff, vedrà che divssono diversi e lo riprodurrà di nuovo includendo tutti i suoi figli (1 ° esempio). Nel 2 ° esempio, il diff avrà successo job-titlee unemployment-reasonpoiché ora hanno chiavi diverse.

Ovviamente puoi usare tutte le chiavi che desideri, purché siano uniche.


Aggiornamento agosto 2017

Per una migliore comprensione di come funzionano le chiavi in ​​React, consiglio vivamente di leggere la mia risposta a Comprendere le chiavi univoche in React.js .


Aggiornamento novembre 2017

Questo aggiornamento avrebbe dovuto essere pubblicato qualche tempo fa, ma l'uso di stringhe letterali in refè ora deprecato. Ad esempio ref="job-title"ora dovrebbe invece essere ref={(el) => this.jobTitleRef = el}(per esempio). Vedi la mia risposta all'avviso di ritiro utilizzando this.refs per maggiori informazioni.


10
it will determine which components are new and which are old based on their key property.- quella era ... la chiave ... per la mia comprensione
Larry

1
Grazie mille ! Avevo anche capito che tutti i componenti figli di una mappa erano stati rieseguiti con successo e non riuscivo a capire il motivo.
ValentinVoilean

un bel suggerimento è usare una variabile di stato (che cambia, come un id per es.) come chiave per il div!
Macilias

200

Cambia la chiave del componente.

<Component key="1" />
<Component key="2" />

Component verrà smontato e verrà montata una nuova istanza di Component poiché la chiave è stata modificata.

modifica : Documentato su di te probabilmente non è necessario uno stato derivato :

Quando una chiave cambia, React creerà una nuova istanza del componente invece di aggiornare quella corrente. Le chiavi vengono solitamente utilizzate per elenchi dinamici, ma sono utili anche qui.


45
Questo dovrebbe essere molto più ovvio nella documentazione di React. Questo ha ottenuto esattamente quello che stavo cercando, ma ci sono volute ore per trovarlo.
Paul

4
Ebbene, questo mi ha aiutato moltissimo! Grazie! Avevo bisogno di ripristinare un'animazione CSS, ma l'unico modo per farlo è forzare un nuovo rendering. Sono d'accordo che questo dovrebbe essere più ovvio nella documentazione di React
Alex. P.

@ Alex.P. Bello! Le animazioni CSS a volte possono essere complicate. Sarei interessato a vedere un esempio.
Alex K

1
Documentazione React che descrive questa tecnica: reactjs.org/blog/2018/06/07/…
GameSalutes,

Grazie. Sono così grato per questo. Ho provato a fornire numeri casuali a oggetti di scena non dichiarati come "randomNumber", ma l'unico oggetto che ha funzionato era la chiave.
ShameWare

5

Usa setStatenella tua vista per cambiare la employedproprietà dello stato. Questo è un esempio del motore di rendering React.

 someFunctionWhichChangeParamEmployed(isEmployed) {
      this.setState({
          employed: isEmployed
      });
 }

 getInitialState() {
      return {
          employed: true
      }
 },

 render(){
    if (this.state.employed) {
        return (
            <div>
                <MyInput ref="job-title" name="job-title" />
            </div>
        );
    } else {
        return (
            <div>
                <span>Diff me!</span>
                <MyInput ref="unemployment-reason" name="unemployment-reason" />
                <MyInput ref="unemployment-duration" name="unemployment-duration" />
            </div>
        );
    }
}

Lo sto già facendo. La variabile "impiegata" fa parte dello stato dei componenti di visualizzazione e viene modificata tramite la sua funzione setState. Scusa per non averlo spiegato.
o01

La variabile "impiegata" dovrebbe essere proprietà dello stato di React. Non è ovvio nel tuo esempio di codice.
Dmitriy

come si modifica this.state.employed? Mostra il codice, per favore. Sei sicuro di utilizzare setState al posto giusto nel tuo codice?
Dmitriy

Il componente di visualizzazione è un po 'più complesso di quanto mostra il mio esempio. Oltre ai componenti MyInput, rende anche un gruppo di radiobutton che controlla il valore di "impiegato" con un gestore onChange. Nel gestore onChange, ho impostato lo stato del componente di visualizzazione di conseguenza. La vista viene riprodotta correttamente quando il valore del gruppo di pulsanti di opzione cambia, ma React pensa che il primo componente MyInput sia lo stesso di prima della modifica di "impiegato".
o01

0

Sto lavorando a Crud per la mia app. Ecco come l'ho fatto Got Reactstrap come mia dipendenza.

import React, { useState, setState } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import firebase from 'firebase';
// import { LifeCrud } from '../CRUD/Crud';
import { Row, Card, Col, Button } from 'reactstrap';
import InsuranceActionInput from '../CRUD/InsuranceActionInput';

const LifeActionCreate = () => {
  let [newLifeActionLabel, setNewLifeActionLabel] = React.useState();

  const onCreate = e => {
    const db = firebase.firestore();

    db.collection('actions').add({
      label: newLifeActionLabel
    });
    alert('New Life Insurance Added');
    setNewLifeActionLabel('');
  };

  return (
    <Card style={{ padding: '15px' }}>
      <form onSubmit={onCreate}>
        <label>Name</label>
        <input
          value={newLifeActionLabel}
          onChange={e => {
            setNewLifeActionLabel(e.target.value);
          }}
          placeholder={'Name'}
        />

        <Button onClick={onCreate}>Create</Button>
      </form>
    </Card>
  );
};

Alcuni React Hooks lì dentro


Benvenuto in SO! Potresti rivedere come scrivere una buona risposta . Aggiungi qualche spiegazione a come hai risolto la domanda oltre alla semplice pubblicazione del codice.
displacedtexan il
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.