Reagir componente apátrida funcional, PureComponent, Component; quais são as diferenças e quando devemos usar o que?

188

Soube que, a partir do React v15.3.0 , temos uma nova classe base chamada PureComponent para estender com o PureRenderMixin embutido. O que eu entendo é que, sob o capô, isso emprega uma comparação superficial de adereços no interior shouldComponentUpdate.

Agora, temos três maneiras de definir um componente React:

  1. Componente sem estado funcional que não estende nenhuma classe
  2. Um componente que estende a PureComponentclasse
  3. Um componente normal que estende a Componentclasse

Algum tempo atrás, costumávamos chamar componentes sem estado como Pure Components, ou mesmo Dumb Components. Parece que toda a definição da palavra "puro" mudou no React.

Embora eu compreenda as diferenças básicas entre esses três, ainda não tenho certeza de quando escolher o que . Além disso, quais são os impactos e compensações de desempenho de cada um?


Atualização :

Estas são as perguntas que espero esclarecer:

  • Devo optar por definir meus componentes simples como funcionais (por uma questão de simplicidade) ou estender a PureComponentclasse (por uma questão de desempenho)?
  • É o aumento de desempenho que recebo uma troca real pela simplicidade que perdi?
  • Eu precisaria estender a Componentclasse normal quando sempre puder usar PureComponentpara obter melhor desempenho?
Yadhu Kiran
fonte

Respostas:

315

Como você decide, como escolhe entre esses três com base no propósito / tamanho / adereços / comportamento de nossos componentes?

Estender de React.PureComponentou para React.Componentum shouldComponentUpdatemétodo personalizado tem implicações no desempenho. O uso de componentes funcionais sem estado é uma opção "arquitetural" e ainda não possui benefícios de desempenho prontos para uso.

  • Para componentes simples, apenas para apresentação, que precisam ser reutilizados com facilidade, prefira componentes funcionais sem estado. Dessa forma, você tem certeza de que eles são dissociados da lógica real do aplicativo, que são fáceis de testar e que não têm efeitos colaterais inesperados. A exceção é se, por algum motivo, você tiver muitos deles ou se realmente precisar otimizar o método de renderização (como não é possível definir shouldComponentUpdateum componente funcional sem estado).

  • Estenda PureComponentse você souber que sua saída depende de props / state simples ("simples" significa que não há estruturas de dados aninhadas, pois o PureComponent realiza uma comparação superficial) E você precisa / pode obter algumas melhorias de desempenho.

  • Estenda Componente implemente você mesmo shouldComponentUpdatese precisar de alguns ganhos de desempenho, executando uma lógica de comparação personalizada entre adereços próximos / atuais e estado. Por exemplo, você pode executar rapidamente uma comparação profunda usando lodash # isEqual:

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

Além disso, implementar otimizações próprias shouldComponentUpdateou ampliadas PureComponentsão otimizações e, como de costume, você deve começar a investigar isso apenas se tiver problemas de desempenho ( evite otimizações prematuras ). Como regra geral, eu sempre tento fazer essas otimizações depois que o aplicativo está em um estado funcional, com a maioria dos recursos já implementados. É muito mais fácil focar nos problemas de desempenho quando eles realmente atrapalham.

Mais detalhes

Componentes apátridas funcionais:

Estes são definidos apenas usando uma função. Como não há estado interno para um componente sem estado, a saída (que é renderizada) depende apenas dos objetos fornecidos como entrada para esta função.

Prós:

  • A maneira mais simples possível de definir um componente no React. Se você não precisa gerenciar nenhum estado, por que se preocupar com classes e herança? Uma das principais diferenças entre uma função e uma classe é que, com a função, você tem certeza de que a saída depende apenas da entrada (não do histórico das execuções anteriores).

  • Idealmente, no seu aplicativo, você deve ter o maior número possível de componentes sem estado, porque isso normalmente significa que você moveu sua lógica para fora da camada de visualização e a moveu para algo como redux, o que significa que você pode testar sua lógica real sem precisar renderizar nada (muito mais fácil de testar, mais reutilizável etc.).

Contras:

  • Nenhum método de ciclo de vida. Você não tem como definir componentDidMounte outros amigos. Normalmente, você faz isso em um componente pai mais alto na hierarquia para poder transformar todos os filhos em sem estado.

  • Não há como controlar manualmente quando uma nova renderização é necessária, pois você não pode definir shouldComponentUpdate. Uma nova renderização acontece toda vez que o componente recebe novos adereços (nenhuma maneira de comparação superficial etc.). No futuro, o React poderá otimizar automaticamente os componentes sem estado, por enquanto existem algumas bibliotecas que você pode usar. Como componentes sem estado são apenas funções, basicamente é o problema clássico da "memorização de funções".

  • Não há suporte para referências: https://github.com/facebook/react/issues/4936

Um componente que estende a classe PureComponent VS Um componente normal que estende a classe Component:

Reagir costumava ter um que PureRenderMixinvocê poderia anexar a uma classe definida usando React.createClasssintaxe. O mixin simplesmente definiria uma shouldComponentUpdatecomparação superficial entre os próximos objetos e o próximo estado para verificar se alguma coisa mudou. Se nada mudar, não será necessário executar uma nova renderização.

Se você deseja usar a sintaxe ES6, não pode usar mixins. Portanto, por conveniência, o React introduziu uma PureComponentclasse da qual você pode herdar em vez de usar Component. PureComponentapenas implementa shouldComponentUpdateda mesma maneira que o PureRendererMixin. É principalmente uma questão de conveniência, para que você não precise implementá-lo por conta própria, pois uma comparação superficial entre o estado atual / próximo e os adereços é provavelmente o cenário mais comum que pode oferecer ganhos rápidos de desempenho.

Exemplo:

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

Como você pode ver, a saída depende de props.imageUrle props.username. Se em um componente pai você renderizar <UserAvatar username="fabio" imageUrl="http://foo.com/fabio.jpg" />com os mesmos adereços, o React chamariarender toda vez, mesmo que a saída seja exatamente a mesma. Lembre-se, no entanto, que o React implementa o dom diffing, para que o DOM não seja realmente atualizado. Ainda assim, executar o dom diffing pode ser caro, portanto, nesse cenário, seria um desperdício.

Se o UserAvatarcomponente se estender PureComponent, é realizada uma comparação superficial. E como props e nextProps são os mesmos, rendernão serão chamados.

Notas sobre a definição de "puro" no React:

Em geral, uma "função pura" é uma função que avalia sempre o mesmo resultado, com a mesma entrada. A saída (para React, é o que é retornado pelo rendermétodo) não depende de nenhum histórico / estado e não possui efeitos colaterais (operações que alteram o "mundo" fora da função).

No React, os componentes sem estado não são necessariamente componentes puros de acordo com a definição acima, se você chamar "sem estado" um componente que nunca chama this.setStatee que não usa this.state.

De fato, em a PureComponent, você ainda pode executar efeitos colaterais durante os métodos do ciclo de vida. Por exemplo, você pode enviar uma solicitação ajax para dentro componentDidMountou executar algum cálculo do DOM para ajustar dinamicamente a altura de uma div dentro render.

A definição de "componentes mudos" tem um significado mais "prático" (pelo menos no meu entendimento): um componente idiota "é informado" do que fazer por um componente pai por meio de adereços e não sabe como fazer as coisas, mas usa adereços retornos de chamada.

Exemplo de um "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>
    }
}

Exemplo de um "burro" AvatarComponent:

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

No final, eu diria que "burro", "sem estado" e "puro" são conceitos bem diferentes que às vezes podem se sobrepor, mas não necessariamente, dependendo principalmente do seu caso de uso.

fabio.sussetto
fonte
1
Eu realmente aprecio sua resposta e o conhecimento que você compartilhou. Mas minha verdadeira pergunta é quando devemos escolher o que? . Para o mesmo exemplo que você mencionou na sua resposta, como devo defini-lo? Deve ser um componente sem estado funcional (se sim, por quê?) Ou estender PureComponent (por quê?) Ou estender a classe Component (novamente por que?). Como você decide, como escolhe entre esses três com base na finalidade / tamanho / adereços / comportamento de nossos componentes?
Yadhu Kiran
1
Sem problemas. Para o componente sem estado funcional, há uma lista de prós / contras que você pode considerar para decidir se isso seria um bom ajuste. Isso responde você primeiro ponto? Vou tentar abordar a questão da escolha um pouco mais.
Fabio.sussetto
2
Os componentes funcionais são sempre renderizados novamente quando o componente pai é atualizado, mesmo que não seja utilizado props. exemplo .
AlexM
1
Essa é uma das respostas mais abrangentes que já li há algum tempo. Ótimo trabalho. Um comentário sobre a primeira frase: Ao estender PureComponent, você não deve implementar shouldComponentUpdate(). Você deve receber um aviso se fizer isso realmente.
jjramos
1
Para obter ganhos reais de desempenho, tente usar PureComponentcomponentes que possuem propriedades de objeto / matriz aninhadas. Claro que você precisa estar ciente do que está acontecendo. Se eu entendi corretamente, se você não está alterando props / state diretamente (que o React tenta impedir que você faça com avisos) ou por meio de uma biblioteca externa, você deve usar bem, em PureComponentvez de Componentpraticamente todo lugar ... com a exceção de componentes muito simples em que ele pode realmente ser mais rápido não usá-lo - veja news.ycombinator.com/item?id=14418576
Matt Browne
28

Eu não sou um gênio sobre reagir, mas pelo meu entendimento, podemos usar cada componente nas seguintes situações

  1. Componente sem estado - esse é o componente que não possui ciclo de vida; portanto, esses componentes devem ser usados ​​na renderização do elemento repetido do componente pai, como na renderização da lista de texto que apenas exibe as informações e não possui nenhuma ação a ser executada.

  2. Componente puro - esses são os itens que possuem ciclo de vida e sempre retornam o mesmo resultado quando um conjunto específico de adereços é fornecido. Esses componentes podem ser usados ​​ao exibir uma lista de resultados ou dados de objetos específicos que não possuem elementos filhos complexos e usados ​​para executar operações que impactam apenas a si mesmas. essa lista de cartões de usuário ou de produtos (informações básicas do produto) e a única ação que o usuário pode executar é clicar para exibir a página de detalhes ou adicionar ao carrinho.

  3. Componentes normais ou componentes complexos - usei o termo componente complexo porque esses geralmente são os componentes no nível da página e consistem em muitos componentes filhos e, já que cada filho pode se comportar de maneira única e única, para que você não tenha 100% de certeza de que renderize o mesmo resultado em um determinado estado. Como eu disse, geralmente estes devem ser usados ​​como componentes de contêineres

abhirathore2006
fonte
1
Essa abordagem pode funcionar, mas você pode estar perdendo grandes ganhos de desempenho. O uso PureComponentde componentes e componentes no nível raiz perto do topo da sua hierarquia geralmente é onde você verá os maiores ganhos de desempenho. É claro que você precisa evitar a mutação de objetos e declarar diretamente que componentes puros funcionem corretamente, mas a mutação direta de objetos é um antipadrão no React de qualquer maneira.
Matt Brown
5
  • React.Componenté o componente "normal" padrão. Você os declara usando a classpalavra-chave e extends React.Component. Pense neles como uma classe, com métodos de ciclos de vida, manipuladores de eventos e quaisquer outros métodos.

  • React.PureComponenté um React.Componentque implementa shouldComponentUpdate()com uma função que faz uma comparação superficial de seus propse state. Você deve usar forceUpdate()se souber que o componente possui adereços ou dados aninhados de estado que foram alterados e deseja renderizar novamente. Portanto, eles não são ótimos se você precisar de componentes para renderizar novamente quando matrizes ou objetos que você passar como adereços ou definir sua alteração de estado.

  • Componentes funcionais são aqueles que não têm funções de ciclo de vida. Eles são supostamente apátridas, mas são tão legais e limpos que agora temos ganchos (desde o React 16.8) para que você ainda possa ter um estado. Então eu acho que eles são apenas "componentes limpos".

JackyJohnson
fonte