Reaccionar componente funcional sin estado, Componente puro, Componente; ¿Cuáles son las diferencias y cuándo debemos usar qué?

189

Llegué a saber que desde React v15.3.0 , tenemos una nueva clase base llamada PureComponent para extender con PureRenderMixin incorporado. Lo que entiendo es que, bajo el capó, esto emplea una comparación superficial de accesorios dentro shouldComponentUpdate.

Ahora tenemos 3 formas de definir un componente Reaccionar:

  1. Componente sin estado funcional que no extiende ninguna clase
  2. Un componente que extiende la PureComponentclase.
  3. Un componente normal que extiende la Componentclase.

Hace algún tiempo, solíamos llamar a componentes sin estado como Componentes Puros, o incluso Componentes Dumb. Parece que toda la definición de la palabra "puro" ahora ha cambiado en React.

Aunque entiendo las diferencias básicas entre estos tres, todavía no estoy seguro de cuándo elegir qué . ¿Cuáles son los impactos en el rendimiento y las compensaciones de cada uno?


Actualización :

Estas son las preguntas que espero aclarar:

  • ¿Debo elegir definir mis componentes simples como funcionales (en aras de la simplicidad) o extender la PureComponentclase (en aras del rendimiento)?
  • ¿Es el aumento de rendimiento que obtengo una verdadera compensación por la simplicidad que perdí?
  • ¿Alguna vez necesitaría extender la Componentclase normal cuando siempre puedo usar PureComponentpara un mejor rendimiento?
Yadhu Kiran
fuente

Respuestas:

315

¿Cómo decide, cómo elige entre estos tres en función del propósito / tamaño / accesorios / comportamiento de nuestros componentes?

Extender desde React.PureComponento React.Componentcon un shouldComponentUpdatemétodo personalizado tiene implicaciones de rendimiento. El uso de componentes funcionales sin estado es una opción "arquitectónica" y aún no tiene beneficios de rendimiento listos para usar.

  • Para componentes simples, solo presentacionales que necesitan ser reutilizados fácilmente, prefiera componentes funcionales sin estado. De esta manera, está seguro de que están desconectados de la lógica real de la aplicación, que son fáciles de probar y que no tienen efectos secundarios inesperados. La excepción es si por alguna razón tiene muchos de ellos o si realmente necesita optimizar su método de representación (ya que no puede definir shouldComponentUpdateun componente funcional sin estado).

  • Extienda PureComponentsi sabe que su salida depende de accesorios / estado simples ("simple" significa que no hay estructuras de datos anidadas, ya que PureComponent realiza una comparación superficial) Y necesita / puede obtener algunas mejoras de rendimiento.

  • Extienda Componente implemente el suyo shouldComponentUpdatesi necesita algunas mejoras de rendimiento al realizar una lógica de comparación personalizada entre los accesorios y el estado siguiente / actual. Por ejemplo, puede realizar rápidamente una comparación profunda usando lodash # isEqual:

    class MyComponent extends Component {
        shouldComponentUpdate (nextProps, nextState) {
            return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
        }
    }

Además, implementar las suyas propias shouldComponentUpdateo ampliarlas PureComponentson optimizaciones, y como de costumbre, debe comenzar a investigar eso solo si tiene problemas de rendimiento ( evite optimizaciones prematuras ). Como regla general, siempre trato de hacer estas optimizaciones después de que la aplicación está funcionando, con la mayoría de las características ya implementadas. Es mucho más fácil concentrarse en los problemas de rendimiento cuando realmente se interponen en el camino.

Más detalles

Componentes sin estado funcionales:

Estos se definen simplemente usando una función. Como no hay un estado interno para un componente sin estado, la salida (lo que se procesa) solo depende de los accesorios proporcionados como entrada para esta función.

Pros:

  • La forma más simple posible de definir un componente en React. Si no necesita administrar ningún estado, ¿por qué molestarse con las clases y la herencia? Una de las principales diferencias entre una función y una clase es que con la función está seguro de que la salida depende solo de la entrada (no de ningún historial de ejecuciones anteriores).

  • Idealmente en su aplicación, debe intentar tener tantos componentes sin estado como sea posible, porque eso normalmente significa que movió su lógica fuera de la capa de vista y la movió a algo como redux, lo que significa que puede probar su lógica real sin tener que renderizar nada (mucho más fácil de probar, más reutilizable, etc.).

Contras:

  • No hay métodos de ciclo de vida. No tienes una forma de definir componentDidMounty otros amigos. Normalmente lo haces dentro de un componente padre más alto en la jerarquía para que puedas convertir a todos los hijos en apátridas.

  • No hay forma de controlar manualmente cuando se necesita una nueva representación, ya que no se puede definir shouldComponentUpdate. Un renderizado ocurre cada vez que el componente recibe nuevos accesorios (no hay forma de comparar superficialmente, etc.). En el futuro, React podría optimizar automáticamente los componentes sin estado, por ahora hay algunas bibliotecas que puede usar. Dado que los componentes sin estado son solo funciones, básicamente es el problema clásico de la "memorización de funciones".

  • Las referencias no son compatibles: https://github.com/facebook/react/issues/4936

Un componente que extiende la clase PureComponent VS Un componente normal que extiende la clase Componente:

React solía tener un PureRenderMixinque podía asociar a una clase definida usando la React.createClasssintaxis. El mixin simplemente definiría una shouldComponentUpdaterealización de una comparación superficial entre los siguientes accesorios y el siguiente estado para verificar si algo ha cambiado. Si nada cambia, entonces no hay necesidad de realizar una nueva representación.

Si desea usar la sintaxis de ES6, no puede usar mixins. Entonces, por conveniencia, React introdujo una PureComponentclase de la que puede heredar en lugar de usar Component. PureComponentsolo se implementa shouldComponentUpdatede la misma manera que el PureRendererMixin. Es principalmente una cuestión de conveniencia, por lo que no tiene que implementarlo usted mismo, ya que una comparación superficial entre el estado actual / próximo y los accesorios es probablemente el escenario más común que le puede dar algunas ganancias rápidas de rendimiento.

Ejemplo:

class UserAvatar extends Component {
    render() {
       return <div><img src={this.props.imageUrl} /> {{ this.props.username }} </div>
    }
} 

Como puede ver, la salida depende de props.imageUrly props.username. Si en un componente principal se procesa <UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" />con los mismos accesorios, React llamará rendersiempre, incluso si el resultado es exactamente el mismo. Sin embargo, recuerde que React implementa dom diffing, por lo que el DOM no se actualizaría realmente. Aún así, realizar la diferenciación de dom puede ser costoso, por lo que en este escenario sería un desperdicio.

Si el UserAvatarcomponente se extiende en su PureComponentlugar, se realiza una comparación superficial. Y debido a que los accesorios y nextProps son iguales, renderno se llamarán en absoluto.

Notas sobre la definición de "puro" en React:

En general, una "función pura" es una función que evalúa siempre el mismo resultado dada la misma entrada. La salida (para React, eso es lo que devuelve el rendermétodo) no depende de ningún historial / estado y no tiene efectos secundarios (operaciones que cambian el "mundo" fuera de la función).

En React, los componentes sin estado no son necesariamente componentes puros según la definición anterior si llama "sin estado" a un componente que nunca llama this.setStatey que no utiliza this.state.

De hecho, en a PureComponent, aún puede realizar efectos secundarios durante los métodos del ciclo de vida. Por ejemplo, podría enviar una solicitud ajax dentro componentDidMounto podría realizar algunos cálculos DOM para ajustar dinámicamente la altura de un div dentro render.

La definición de "Componentes tontos" tiene un significado más "práctico" (al menos en mi entendimiento): un componente tonto "se le dice" qué hacer por un componente padre a través de accesorios, y no sabe cómo hacer las cosas, pero usa accesorios devoluciones de llamada en su lugar.

Ejemplo de un "inteligente" AvatarComponent:

class AvatarComponent extends Component {
    expandAvatar () {
        this.setState({ loading: true });
        sendAjaxRequest(...).then(() => {
            this.setState({ loading: false });
        });
    }        

    render () {
        <div onClick={this.expandAvatar}>
            <img src={this.props.username} />
        </div>
    }
}

Ejemplo de un "tonto" AvatarComponent:

class AvatarComponent extends Component {
    render () {
        <div onClick={this.props.onExpandAvatar}>
            {this.props.loading && <div className="spinner" />}
            <img src={this.props.username} />
        </div>
    }
}

Al final, diría que "tonto", "sin estado" y "puro" son conceptos muy diferentes que a veces pueden superponerse, pero no necesariamente, dependiendo principalmente de su caso de uso.

fabio.sussetto
fuente
1
Realmente aprecio tu respuesta y el conocimiento que has compartido. Pero mi verdadera pregunta es cuándo debemos elegir qué. . Para el mismo ejemplo que mencionaste en tu respuesta, ¿cómo debo definirlo? ¿Debería ser un componente funcional sin estado (si es así, ¿por qué?), O extender PureComponent (¿por qué?) O extender la clase Componente (¿nuevamente por qué?). ¿Cómo decide, cómo elige entre estos tres en función del propósito / tamaño / accesorios / comportamiento de nuestros componentes?
Yadhu Kiran el
1
No hay problema. Para el componente funcional sin estado, hay una lista de pros / contras que puedo considerar para decidir si sería una buena opción. ¿Eso responde tu primer punto? Voy a tratar de abordar la pregunta de elección un poco más.
fabio.sussetto
2
Los componentes funcionales siempre se vuelven a representar cuando el componente principal se actualiza, incluso si no se usan propsen absoluto. ejemplo .
AlexM
1
Esta es una de las respuestas más completas que he leído en bastante tiempo. Buen trabajo. Un comentario sobre la primera oración: al extender PureComponent, no debe implementar shouldComponentUpdate(). Debería ver una advertencia si hace esto realmente.
jjramos
1
Para obtener ganancias de rendimiento reales, debe intentar utilizar PureComponentcomponentes que TIENEN propiedades de objeto / matriz anidadas. Por supuesto, debes estar al tanto de lo que está sucediendo. Si entiendo correctamente, si no está mutando accesorios / estado directamente (lo que React intenta evitar que haga con advertencias) o mediante una biblioteca externa, entonces debería estar bien en PureComponentlugar de usarlo Componentcasi en todas partes ... con la excepción de componentes muy simples donde en realidad puede ser más rápido NO usarlo - vea news.ycombinator.com/item?id=14418576
Matt Browne
28

No soy un genio sobre reaccionar, pero según tengo entendido, podemos usar cada componente en las siguientes situaciones

  1. Componente sin estado: este es el componente que no tiene ciclo de vida, por lo que esos componentes deben usarse para representar el elemento repetido del componente principal, como la lista de texto que solo muestra la información y no tiene ninguna acción que realizar.

  2. Componente puro: estos son los elementos que tienen un ciclo de vida y siempre devolverán el mismo resultado cuando se proporciona un conjunto específico de accesorios. Esos componentes se pueden usar cuando se muestra una lista de resultados o datos de un objeto específico que no tiene elementos secundarios complejos y se utilizan para realizar operaciones que solo se afectan a sí mismas. como mostrar una lista de tarjetas de usuario o una lista de tarjetas de productos (información básica del producto) y la única acción que el usuario puede realizar es hacer clic para ver la página de detalles o agregar al carrito.

  3. Componentes normales o componentes complejos: utilicé el término componente complejo porque generalmente son componentes de nivel de página y consisten en muchos componentes secundarios y dado que cada uno de los elementos secundarios puede comportarse de una manera única y única, por lo que no puede estar 100% seguro de que lo hará renderizar el mismo resultado en un estado dado. Como dije generalmente, estos deberían usarse como componentes del contenedor

abhirathore2006
fuente
1
Este enfoque podría funcionar, pero podría estar perdiendo grandes ganancias de rendimiento. El uso PureComponenten componentes de nivel raíz y componentes cerca de la parte superior de su jerarquía generalmente es donde vería las mayores ganancias de rendimiento. Por supuesto, debe evitar los accesorios de mutación y el estado directamente para que los componentes puros funcionen correctamente, pero de todos modos, la mutación de objetos directamente es un antipatrón en React.
Matt Browne
5
  • React.Componentes el componente "normal" predeterminado. Los declaras usando la classpalabra clave y extends React.Component. Piense en ellos como una clase, con métodos de ciclos de vida, controladores de eventos y cualquier método.

  • React.PureComponentes un React.Componentque implementa shouldComponentUpdate()con una función que hace una comparación superficial de su propsy state. Debe usarlo forceUpdate()si sabe que el componente tiene accesorios o datos anidados de estado que cambiaron y desea volver a renderizar. Por lo tanto, no son excelentes si necesita componentes para volver a renderizar cuando las matrices u objetos que pasa como accesorios o configurados en su estado cambian.

  • Los componentes funcionales son aquellos que no tienen funciones de ciclo de vida. Supuestamente son apátridas, pero son tan agradables y limpios que ahora tenemos ganchos (desde React 16.8) para que aún pueda tener un estado. Así que supongo que son solo "componentes limpios".

JackyJohnson
fuente