Stato ReactJS vs prop


121

Questo potrebbe essere il confine tra responsabile e supponente, ma vado avanti e indietro su come strutturare un componente ReactJS man mano che la complessità cresce e potrebbe usare una direzione.

Provenendo da AngularJS, voglio passare il mio modello nel componente come proprietà e fare in modo che il componente modifichi direttamente il modello. O dovrei suddividere il modello in varie stateproprietà e ricompilarlo insieme durante l'invio a monte? Qual è il metodo ReactJS?

Prendi l'esempio di un editor di post sul blog. Cercare di modificare direttamente il modello finisce per sembrare:

var PostEditor = React.createClass({
  updateText: function(e) {
    var text = e.target.value;
    this.props.post.text = text;
    this.forceUpdate();
  },
  render: function() {
    return (
      <input value={this.props.post.text} onChange={this.updateText}/>
      <button onClick={this.props.post.save}/>Save</button>
    );
  }
});

Il che sembra sbagliato.

È più il modo React per rendere la textproprietà del nostro modello statee ricompilarlo nel modello prima di salvare come:

var PostEditor = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },
  componentWillMount: function() {
    this.setState({
      text: this.props.post.text
    });
  },
  updateText: function(e) {
    this.setState({
      text: e.target.value
    });
  },
  savePost: function() {
    this.props.post.text = this.state.text;
    this.props.post.save();
  },
  render: function() {
    return (
      <input value={this.state.text} onChange={this.updateText}/>
      <button onClick={this.savePost}/>Save</button>
    );
  }
});

Ciò non richiede una chiamata a this.forceUpdate(), ma man mano che il modello cresce (un post può avere un autore, un oggetto, tag, commenti, valutazioni, ecc ...) il componente inizia a diventare davvero complicato.

Il primo metodo con ReactLink è la strada da percorrere?

Risposte:


64

Il tuo secondo approccio è più simile. React non si preoccupa tanto dei modelli quanto dei valori e di come fluiscono attraverso la tua app. Idealmente, il tuo modello di post sarebbe archiviato in un singolo componente alla radice. Quindi si creano componenti figlio che consumano parti del modello.

Puoi passare i callback ai figli che devono modificare i tuoi dati e chiamarli dal componente figlio.

Modificare direttamente this.props o this.state non è una buona idea, perché React non sarà in grado di rilevare le modifiche. Questo perché React fa un confronto superficiale del puntello del tuo post per determinare se è cambiato.

Ho creato questo jsfiddle per mostrare come i dati potrebbero fluire da un componente esterno a uno interno.

Il handleClickmetodo mostra 3 modi per (im) aggiornare correttamente lo stato:

var Outer = React.createClass({

  getInitialState: function() {
    return {data: {value: 'at first, it works'}};
  },

  handleClick: function () {

    // 1. This doesn't work, render is not triggered.
    // Never set state directly because the updated values
    // can still be read, which can lead to unexpected behavior.

    this.state.data.value = 'but React will never know!';

    // 2. This works, because we use setState

    var newData = {value: 'it works 2'};
    this.setState({data: newData});

    // 3. Alternatively you can use React's immutability helpers
    // to update more complex models.
    // Read more: http://facebook.github.io/react/docs/update.html

    var newState = React.addons.update(this.state, {
      data: {value: {$set: 'it works'}}
    });
    this.setState(newState);
 },

  render: function() {
      return <Inner data={this.state.data} handleClick={this.handleClick} />;
  }
});

Ma cosa facciamo se abbiamo un modello opaco, con le sue funzioni di manipolazione dello stato? Ad esempio, supponiamo che invece di un textcampo, abbiamo un setText metodo che esegue la convalida e alcune altre cose. Riesco a vedere il metodo (2) che funziona se setTextè puro e restituisce una nuova istanza del modello. Tuttavia, se si setTextaggiorna solo lo stato interiore, dovremmo comunque chiamare forceUpdate, giusto?
hugomg

1
Sì, potresti chiamare forceUpdate, ma a quel punto stai "trapelando" da React. Potrebbe essere meglio passare una setState()richiamata al modello opaco per evitare di dover attivare manualmente i ri-rendering.
jxg

Non sono ancora sicuro di aver compreso appieno. Quindi qualsiasi componente destinato a modificare i dati deve eseguire una copia completa degli oggetti di scena passati? Quindi modificare e inviare quella copia a monte in modo da non modificare i dati originali? Alla fine la modifica fluirà fino alla radice, dove verrà gestita e l'intera app verrà riesaminata? È giusto?
nicholas

97

Aggiornamento 2016: React è cambiato e la spiegazione "props vs state" è diventata molto semplice. Se un componente deve modificare i dati, mettilo in uno stato, altrimenti in props. Perché gli oggetti di scena ora sono di sola lettura .

Qual è la differenza esatta tra oggetti di scena e stato?

Puoi trovare una buona spiegazione qui (versione completa)

Cambiare gli oggetti di scena e lo stato


1
effettivamente setProps () potrebbe cambiare gli oggetti di scena all'interno di un componente e attivare un nuovo rendering
WaiKit Kung

2
setPropsè deprecato e non deve essere utilizzato. La sostituzione consiste nel rieseguire il rendering del componente e lasciare che React gestisca le differenze.
jdmichal

E se stai cercando un video esplicativo: youtube.com/watch?v=qh3dYM6Keuw
jaisonDavis

35

Da React doc

gli oggetti di scena sono immutabili: sono passati dal genitore e sono "di proprietà" del genitore. Per implementare le interazioni, introduciamo lo stato mutabile al componente. this.state è privato per il componente e può essere modificato chiamando this.setState (). Quando lo stato viene aggiornato, il componente esegue nuovamente il rendering.

Da TrySpace : quando gli oggetti di scena (o lo stato) vengono aggiornati (tramite setProps / setState o parent) anche il componente viene riprodotto.


16

Una lettura da Thinking in React :

Esaminiamo ognuno di essi e scopriamo quale è lo stato. Poni semplicemente tre domande su ciascun dato:

  1. Viene trasmesso da un genitore tramite oggetti di scena? Se è così, probabilmente non è stato.
  2. Cambia nel tempo? In caso contrario, probabilmente non è stato.

  3. Puoi calcolarlo in base a qualsiasi altro stato o props nel tuo componente? Se è così, non è stato.


13

Non sono sicuro di rispondere alla tua domanda, ma ho scoperto che, specialmente in un'applicazione di grandi dimensioni / in crescita, il pattern Container / Component funziona incredibilmente bene.

Essenzialmente hai due componenti React:

  • un componente di visualizzazione "puro", che si occupa dello stile e dell'interazione DOM;
  • un componente contenitore, che si occupa dell'accesso / salvataggio dei dati esterni, della gestione dello stato e del rendering del componente di visualizzazione.

Esempio

NB Questo esempio è probabilmente troppo semplice per illustrare i vantaggi di questo modello, poiché è abbastanza prolisso per un caso così semplice.

/**
 * Container Component
 *
 *  - Manages component state
 *  - Does plumbing of data fetching/saving
 */

var PostEditorContainer = React.createClass({
  getInitialState: function() {
    return {
      text: ""
    };
  },

  componentWillMount: function() {
    this.setState({
      text: getPostText()
    });
  },

  updateText: function(text) {
    this.setState({
      text: text
    });
  },

  savePost: function() {
    savePostText(this.state.text);
  },

  render: function() {
    return (
      <PostEditor
        text={this.state.text}
        onChange={this.updateText.bind(this)}
        onSave={this.savePost.bind(this)}
      />
    );
  }
});


/**
 * Pure Display Component
 *
 *  - Calculates styling based on passed properties
 *  - Often just a render method
 *  - Uses methods passed in from container to announce changes
 */

var PostEditor = React.createClass({
  render: function() {
    return (
      <div>
        <input type="text" value={this.props.text} onChange={this.props.onChange} />
        <button type="button" onClick={this.props.onSave} />
      </div>
    );
  }
});

Benefici

Mantenendo separate la logica di visualizzazione e la gestione dei dati / stato, si dispone di un componente di visualizzazione riutilizzabile che:

  • può essere facilmente iterato con diversi set di oggetti di scena usando qualcosa di simile a react-component-playground
  • può essere racchiuso in un contenitore diverso per un comportamento diverso (o combinato con altri componenti per costruire parti più grandi della tua applicazione

Hai anche un componente contenitore che si occupa di tutte le comunicazioni esterne. Ciò dovrebbe rendere più facile essere flessibili sul modo in cui accedi ai tuoi dati se apporti modifiche importanti in seguito *.

Questo modello rende anche la scrittura e l'implementazione di unit test molto più semplici.

Dopo aver iterato alcune volte una grande app React, ho scoperto che questo modello mantiene le cose relativamente indolori, specialmente quando hai componenti più grandi con stili calcolati o complicate interazioni DOM.

* Leggi il modello di flusso e dai un'occhiata a Marty.js , che ha ampiamente ispirato questa risposta (e ho usato molto ultimamente) Redux (e react-redux ), che implementano questo modello estremamente bene.

Nota per coloro che leggono questo nel 2018 o successivamente:

React si è evoluto un po 'da quando è stata scritta questa risposta, specialmente con l'introduzione di Hooks . Tuttavia, la logica di gestione dello stato sottostante di questo esempio rimane la stessa e, cosa più importante, i vantaggi che si ottengono mantenendo separati lo stato e la logica di presentazione si applicano allo stesso modo.


0

Penso che tu stia usando un anti-pattern che Facebook ha già spiegato a questo link

Ecco cosa stai trovando:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

La prima volta che il componente interno viene renderizzato, avrà {foo: 'bar'} come valore prop. Se l'utente fa clic sull'ancora, lo stato del componente genitore verrà aggiornato a {value: {foo: 'barbar'}}, attivando il processo di ri-rendering del componente interno, che riceverà {foo: 'barbar'} come il nuovo valore per il puntello.

Il problema è che poiché i componenti genitore e interno condividono un riferimento allo stesso oggetto, quando l'oggetto viene mutato sulla riga 2 della funzione onClick, il sostegno del componente interno cambierà. Quindi, quando viene avviato il processo di ri-rendering e shouldComponentUpdate viene richiamato, this.props.value.foo sarà uguale a nextProps.value.foo, perché in realtà this.props.value fa riferimento allo stesso oggetto di nextProps.value.

Di conseguenza, poiché mancheremo la modifica sull'elica e cortocircuiteremo il processo di rendering, l'interfaccia utente non verrà aggiornata da "bar" a "barbar"


Puoi anche inserire il Innercomponentscodice?
Abdullah Khan
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.