ReactJS state vs prop

121

Esto puede estar pisando esa línea entre responsable y obstinado, pero estoy yendo y viniendo sobre cómo estructurar un componente ReactJS a medida que crece la complejidad y podría usar alguna dirección.

Viniendo de AngularJS, quiero pasar mi modelo al componente como una propiedad y hacer que el componente modifique el modelo directamente. ¿O debería dividir el modelo en varias statepropiedades y volver a compilarlo cuando lo envíe de nuevo? ¿Cuál es la forma ReactJS?

Tome el ejemplo de un editor de publicaciones de blog. Intentar modificar el modelo directamente termina pareciéndose a:

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>
    );
  }
});

Lo que parece mal.

¿Es más la forma de reaccionar para hacer que nuestro textmodelo sea propiedad statey compilarlo nuevamente en el modelo antes de guardarlo como:

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>
    );
  }
});

Esto no requiere una llamada this.forceUpdate(), pero a medida que el modelo crece, (una publicación puede tener un autor, un tema, etiquetas, comentarios, calificaciones, etc.) el componente comienza a complicarse mucho.

¿Es el primer método con ReactLink el camino a seguir?

nicholas
fuente

Respuestas:

64

Su segundo enfoque es más parecido. A React no le importan tanto los modelos, sino los valores y cómo fluyen a través de su aplicación. Idealmente, su modelo de publicación se almacenaría en un solo componente en la raíz. Luego, crea componentes secundarios que consumen partes del modelo.

Puede pasar las devoluciones de llamada a los hijos que necesitan modificar sus datos y llamarlos desde el componente hijo.

Modificar this.props o this.state directamente no es una buena idea, porque React no podrá detectar los cambios. Esto se debe a que React hace una comparación superficial de su soporte de publicación para determinar si ha cambiado.

Hice este jsfiddle para mostrar cómo los datos podrían fluir de un componente externo a uno interno.

El handleClickmétodo muestra 3 formas de (im) actualizar correctamente el estado:

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} />;
  }
});
jxg
fuente
Pero, ¿qué hacemos si tenemos un modelo opaco, con sus propias funciones de manipulación de estado? Por ejemplo, suponga que en lugar de un textcampo, tenemos un setText método que valida y algunas otras cosas. Puedo ver que el método (2) funciona si setTextes puro y devuelve una nueva instancia del modelo. Sin embargo, si setTextsolo actualiza el estado interno, aún deberíamos llamar forceUpdate, ¿verdad?
hugomg
1
Sí, puedes llamar forceUpdate, pero en ese momento estás "goteando" de React. Sería mejor pasar una setState()devolución de llamada al modelo opaco para evitar tener que activar manualmente los renders.
jxg
Todavía no estoy seguro de entender completamente. Entonces, ¿cualquier componente destinado a modificar datos debe hacer una copia profunda de los accesorios pasados? Luego, ¿modifica y envía esa copia en sentido ascendente para no modificar los datos originales? Eventualmente, el cambio fluirá hasta la raíz, donde se trata y se vuelve a procesar toda la aplicación. ¿Está bien?
Nicholas
97

Actualización 2016: React ha cambiado, y la explicación "accesorios vs estado" se volvió muy simple. Si un componente necesita cambiar datos, póngalo en un estado, de lo contrario en accesorios. Porque los accesorios son de solo lectura ahora.

¿Cuál es la diferencia exacta entre accesorios y estado?

Puedes encontrar una buena explicación aquí (versión completa)

Cambio de accesorios y estado

mrvol
fuente
1
En realidad, setProps () podría cambiar los accesorios dentro de un componente y desencadenar una nueva representación
WaiKit Kung
2
setPropsestá en desuso y no debe usarse. Reemplazar es volver a procesar el componente y dejar que React maneje las diferencias.
jdmichal
Y si estás buscando un video
explicativo
35

De React doc

los accesorios son inmutables: se pasan del padre y son "propiedad" del padre. Para implementar interacciones, introducimos un estado mutable en el componente. this.state es privado para el componente y se puede cambiar llamando a this.setState (). Cuando se actualiza el estado, el componente se vuelve a representar.

Desde TrySpace : cuando se actualizan los accesorios (o el estado) (a través de setProps / setState o parent), el componente también se vuelve a representar.

Fizer Khan
fuente
16

Una lectura de Thinking in React :

Repasemos cada uno y descubramos cuál es el estado. Simplemente haga tres preguntas sobre cada dato:

  1. ¿Se pasa de un padre a través de accesorios? Si es así, probablemente no sea estado.
  2. ¿Cambia con el tiempo? Si no, probablemente no sea estado.

  3. ¿Puedes calcularlo en función de cualquier otro estado o accesorios en tu componente? Si es así, no es estado.

onmyway133
fuente
13

No estoy seguro de responder a su pregunta, pero descubrí que, especialmente en una aplicación grande / en crecimiento, el patrón Contenedor / Componente funciona increíblemente bien.

Esencialmente tienes dos componentes React:

  • un componente de visualización "puro", que se ocupa del estilo y la interacción DOM;
  • un componente contenedor, que se ocupa de acceder / guardar datos externos, administrar el estado y representar el componente de visualización.

Ejemplo

Nota: este ejemplo es probablemente demasiado simple para ilustrar los beneficios de este patrón, ya que es bastante detallado para un caso tan sencillo.

/**
 * 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>
    );
  }
});

Beneficios

Al mantener separadas la lógica de visualización y la gestión de datos / estado, tiene un componente de visualización reutilizable que:

  • se puede iterar fácilmente con diferentes conjuntos de accesorios usando algo como react-component-playground
  • puede envolverse con un contenedor diferente para un comportamiento diferente (o combinarse con otros componentes para construir partes más grandes de su aplicación

También tiene un componente contenedor que se ocupa de todas las comunicaciones externas. Esto debería hacer que sea más fácil ser flexible sobre la forma en que accede a sus datos si realiza cambios serios más adelante *.

Este patrón también hace que escribir e implementar pruebas unitarias sea mucho más sencillo.

Después de haber iterado una gran aplicación React varias veces, descubrí que este patrón mantiene las cosas relativamente indoloras, especialmente cuando tiene componentes más grandes con estilos calculados o interacciones DOM complicadas.

* Lea sobre el patrón de flujo y eche un vistazo a Marty.js , que inspiró en gran medida esta respuesta (y he estado usando mucho últimamente) Redux (y react-redux ), que implementan este patrón extremadamente bien.

Nota para aquellos que lean esto en 2018 o más tarde:

React ha evolucionado bastante desde que se escribió esta respuesta, especialmente con la introducción de Hooks . Sin embargo, la lógica de administración de estado subyacente de este ejemplo sigue siendo la misma y, lo que es más importante, los beneficios que obtiene al mantener su estado y la lógica de presentación separados todavía se aplican de la misma manera.

Jim O'Brien
fuente
0

Creo que estás usando un antipatrón que Facebook ya ha explicado en este enlace.

Esto es lo que estás encontrando:

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 primera vez que se representa el componente interno, tendrá {foo: 'bar'} como valor de apoyo. Si el usuario hace clic en el ancla, el estado del componente principal se actualizará a {valor: {foo: 'barbar'}}, lo que desencadenará el proceso de representación del componente interno, que recibirá {foo: 'barbar'} como El nuevo valor para el accesorio.

El problema es que, dado que los componentes primarios y internos comparten una referencia al mismo objeto, cuando el objeto se muta en la línea 2 de la función onClick, cambiará la propiedad que tenía el componente interno. Entonces, cuando se inicia el proceso de renderizado y se invoca shouldComponentUpdate, this.props.value.foo será igual a nextProps.value.foo, porque de hecho, this.props.value hace referencia al mismo objeto que nextProps.value.

En consecuencia, dado que nos perderemos el cambio en la utilería y cortocircuitaremos el proceso de renderizado, la interfaz de usuario no se actualizará de 'bar' a 'barbar'

Alex Nguyen
fuente
¿Pueden publicar también el Innercomponentscódigo?
Abdullah Khan