ReactJS - ¿Se llama el render cada vez que se llama "setState"?

503

¿Reacciona React todos los componentes y subcomponentes cada vez que setStatese llama?

Si es así, ¿por qué? Pensé que la idea era que React solo representaba tan poco como fuera necesario, cuando el estado cambiaba.

En el siguiente ejemplo simple, ambas clases se renderizan nuevamente cuando se hace clic en el texto, a pesar de que el estado no cambia en los clics posteriores, ya que el controlador onClick siempre establece stateel mismo valor:

this.setState({'test':'me'});

Hubiera esperado que los renders solo ocurrieran si los statedatos hubieran cambiado.

Aquí está el código del ejemplo, como un JS Fiddle y un fragmento incrustado:

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

[1]: http://jsfiddle.net/fp2tncmb/2/
Brad Parks
fuente
Tuve el mismo problema, no sé la solución exacta. Pero había limpiado los códigos no deseados del componente que comenzó a funcionar como de costumbre.
Jaison
Me gustaría señalar que en su ejemplo, porque la forma en que está diseñado su elemento no se basa únicamente en un estado único, llamar setState()incluso con datos ficticios hace que el elemento se represente de manera diferente, por lo que diría que sí. ¡Absolutamente debería intentar volver a renderizar su objeto cuando algo podría haber cambiado porque de lo contrario su demostración, suponiendo que fuera el comportamiento previsto, no funcionaría!
Tadhg McDonald-Jensen
Puede que tengas razón @ TadhgMcDonald-Jensen, pero según tengo entendido, React lo habría hecho la primera vez (ya que el estado cambia de nada a algo la primera vez), y nunca tuvo que volver a hacerlo. Sin embargo, estaba equivocado, por supuesto, ya que parece que React requiere que escribas tu propio shouldComponentUpdatemétodo, que supuse que una versión simple de él ya debe estar incluida en React. Parece que la versión predeterminada incluida en react simplemente devuelve true, lo que obliga al componente a volver a renderizarse cada vez.
Brad Parks el
Sí, pero solo necesita volver a renderizar en el DOM virtual, luego solo cambia el DOM real si el componente se representa de manera diferente. Las actualizaciones al DOM virtual generalmente son insignificantes (al menos en comparación con la modificación de cosas en la pantalla real), por lo que llamar a render cada vez que deba actualizarse y luego ver que no ha ocurrido ningún cambio no es muy costoso y más seguro que asumir que debería representar lo mismo.
Tadhg McDonald-Jensen

Respuestas:

570

¿React vuelve a representar todos los componentes y subcomponentes cada vez que se llama a setState?

Por defecto, sí.

Hay un método booleano shouldComponentUpdate (objeto nextProps, objeto nextState) , cada componente tiene este método y es responsable de determinar "¿debería actualizar el componente (ejecutar la función de renderizado )?" cada vez que cambia de estado o pasa nuevos accesorios del componente principal.

Puede escribir su propia implementación del método shouldComponentUpdate para su componente, pero la implementación predeterminada siempre devuelve verdadero, lo que significa que siempre vuelve a ejecutar la función de representación.

Cita de documentos oficiales http://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate

De forma predeterminada, shouldComponentUpdate siempre devuelve verdadero para evitar errores sutiles cuando el estado está mutado en su lugar, pero si tiene cuidado de tratar siempre el estado como inmutable y de solo lectura desde los accesorios y el estado en render (), entonces puede anular shouldComponentUpdate con Una implementación que compara los viejos accesorios y el estado con sus reemplazos.

Siguiente parte de tu pregunta:

Si es así, ¿por qué? Pensé que la idea era que React solo producía tan poco como fuera necesario, cuando el estado cambió.

Hay dos pasos de lo que podemos llamar "render":

  1. Renders virtuales DOM: cuando se llama al método de renderizado , devuelve una nueva estructura dom virtual del componente. Como mencioné antes, este método de representación se llama siempre cuando se llama a setState () , porque shouldComponentUpdate siempre devuelve verdadero por defecto. Entonces, por defecto, no hay optimización aquí en React.

  2. Representaciones nativas de DOM: React cambia los nodos DOM reales en su navegador solo si se modificaron en el DOM virtual y tan poco como sea necesario: esta es la gran característica de React que optimiza la mutación DOM real y hace que React sea rápido.

Petr
fuente
47
¡Gran explicación! Y lo único que realmente hace que React siga brillando en este caso es que no cambia el DOM real a menos que se haya cambiado el DOM virtual. Entonces, si mi función de procesamiento devuelve lo mismo 2 veces seguidas, el DOM real no cambia en absoluto ... ¡Gracias!
Brad Parks
1
Creo que @tungd tiene razón: vea su respuesta a continuación. También se trata de una relación padre-hijo entre componentes. Por ejemplo, si TimeInChild es un hermano de Main, entonces su render () no se llamará innecesariamente.
gvlax
2
Si su estado contiene un objeto javascript relativamente grande (~ 1k propiedades totales), que se representa como un gran árbol de componentes (~ 100 en total) ... si deja que las funciones de renderización construyan el dom virtual, o debería, antes de configurar el estado, compare el nuevo estado con el antiguo manualmente y solo llame setStatesi detecta que hay una diferencia. Si es así, ¿cómo hacer esto mejor? ¿Comparar las cadenas json, construir y comparar hashes de objetos, ...?
Vincent Sels
1
@Petr, pero aunque reaccione, reconstruya la dom virtual, si la dom virtual anterior es igual a la dom virtual nueva, la dom del navegador no se tocará, ¿no?
Jaskey
3
Además, considere usar React.PureComponent ( reactjs.org/docs/react-api.html#reactpurecomponent ). Solo se actualiza (se vuelve a generar) si el estado o los accesorios del componente realmente han cambiado. Tenga cuidado, sin embargo, que la comparación es superficial.
Debatador
105

No, React no procesa todo cuando el estado cambia.

  • Cada vez que un componente está sucio (su estado cambió), ese componente y sus elementos secundarios se vuelven a representar. Esto, en cierta medida, es volver a renderizar lo menos posible. El único momento en que no se llama render es cuando alguna rama se mueve a otra raíz, donde teóricamente no necesitamos volver a renderizar nada. En su ejemplo, TimeInChildes un componente secundario de Main, por lo que también se vuelve a representar cuando el estado de los Maincambios.

  • Reaccionar no compara los datos del estado. Cuando setStatese llama, marca el componente como sucio (lo que significa que debe volver a procesarse). Lo importante a tener en cuenta es que, aunque renderse llama al método del componente, el DOM real solo se actualiza si la salida es diferente del árbol DOM actual (también conocido como diferencia entre el árbol DOM virtual y el árbol DOM del documento). En su ejemplo, a pesar de que los statedatos no han cambiado, la hora del último cambio sí lo hizo, haciendo que el DOM virtual sea diferente del DOM del documento, por lo tanto, el HTML se actualiza.

tungd
fuente
Sí, esta es la respuesta correcta. Como experimento, modifique la última línea como React.renderComponent (<div> <Main /> <TimeInChild /> </div>, document.body) ; y elimine <TimeInChild /> del cuerpo de render () del componente Main . El render () de TimeInChild no se llamará de forma predeterminada porque ya no es un elemento secundario de Main .
gvlax
Gracias, cosas como esta son un poco complicadas, por eso los autores de React recomendaron que el render()método sea "puro", independiente del estado externo.
2015
3
@tungd, ¿qué significa some branch is moved to another root? ¿Cómo se llama branch? ¿Cómo se llama root?
Verde
2
Realmente prefiero la respuesta marcada como la correcta. Entiendo su interpretación de 'renderizar' en el lado nativo, 'real' ... pero si lo mira desde el lado reactivo nativo, debe decir que se vuelve a reproducir. Afortunadamente, es lo suficientemente inteligente como para determinar lo que realmente cambió y solo actualizar estas cosas. Esta respuesta puede ser confusa para los nuevos usuarios, primero dices que no, y luego explicas que las cosas se procesan ...
WiRa
1
@tungd u puede explicar por favor la pregunta de verdewhat does it mean some branch is moved to another root? What do you call branch? What do you call root?
madhu131313
7

Aunque se indica en muchas de las otras respuestas aquí, el componente debería:

  • implementar shouldComponentUpdatepara renderizar solo cuando el estado o las propiedades cambien

  • cambie a extender un PureComponent , que ya implementa shouldComponentUpdateinternamente un método para comparaciones superficiales.

Aquí hay un ejemplo que utiliza shouldComponentUpdate, que funciona solo para este caso de uso simple y con fines de demostración. Cuando se usa esto, el componente ya no se vuelve a representar en cada clic, y se representa cuando se muestra por primera vez, y después de hacer clic una vez.

var TimeInChild = React.createClass({
    render: function() {
        var t = new Date().getTime();

        return (
            <p>Time in child:{t}</p>
        );
    }
});

var Main = React.createClass({
    onTest: function() {
        this.setState({'test':'me'});
    },

    shouldComponentUpdate: function(nextProps, nextState) {
      if (this.state == null)
        return true;
  
      if (this.state.test == nextState.test)
        return false;
        
      return true;
  },

    render: function() {
        var currentTime = new Date().getTime();

        return (
            <div onClick={this.onTest}>
            <p>Time in main:{currentTime}</p>
            <p>Click me to update time</p>
            <TimeInChild/>
            </div>
        );
    }
});

ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

Brad Parks
fuente
6

Si. Llama al método render () cada vez que llamamos a setState cuando "shouldComponentUpdate" devuelve falso.

lakmal_sathyajith
fuente
77
Por favor sea más elaborado
Karthick Ramesh
3

Otra razón para la "actualización perdida" puede ser la siguiente:

  • Si se define el getDerivedStateFromProps estático , se vuelve a ejecutar en cada proceso de actualización de acuerdo con la documentación oficial https://reactjs.org/docs/react-component.html#updating .
  • así que si ese valor de estado proviene de accesorios al principio, se sobrescribe en cada actualización.

Si es el problema, entonces U puede evitar establecer el estado durante la actualización, debe verificar el valor del parámetro de estado de esta manera

static getDerivedStateFromProps(props: TimeCorrectionProps, state: TimeCorrectionState): TimeCorrectionState {
   return state ? state : {disable: false, timeCorrection: props.timeCorrection};
}

Otra solución es agregar una propiedad inicializada al estado y configurarla por primera vez (si el estado se inicializa a un valor no nulo).

Zoltán Krizsán
fuente
0

No todos los componentes.

el statecomponente de entrada se parece a la fuente de la cascada de estado de toda la aplicación.

Entonces, el cambio ocurre desde donde llamó setState. El árbol de rendersentonces se llama desde allí. Si ha utilizado un componente puro, renderse omitirá el.

Singhi John
fuente