Qual a diferença entre os componentes funcionais do React e os componentes baseados em classe? Já há algum tempo, a resposta tradicional a essa pergunta é: "O uso de classes permite que você use um grande número de recursos de componentes, por exemplo, estado". Agora, com o advento dos
ganchos , essa resposta não reflete mais o verdadeiro estado das coisas.
Você pode ter ouvido falar que um desses tipos de componentes tem melhor desempenho que o outro. Mas qual? A maioria dos benchmarks que testam isso tem
falhas , então eu tiraria
conclusões com base em seus resultados com muita cautela. O desempenho depende principalmente do que acontece no código, e não se componentes funcionais ou componentes baseados em classe são selecionados para implementar determinados recursos. Nosso estudo mostrou que a diferença de desempenho entre os diferentes tipos de componentes é insignificante. No entanto, deve-se notar que as estratégias de otimização usadas para trabalhar com elas
diferem um pouco.

De qualquer forma,
não recomendo reescrever componentes existentes usando novas tecnologias, se não houver boas razões para isso, e se você não se importar de estar entre aqueles que começaram a usar essas tecnologias antes de todos os outros. Os ganchos ainda são uma nova tecnologia (a mesma que a biblioteca React foi em 2014), e algumas “práticas recomendadas” para sua aplicação ainda não foram incluídas nos manuais do React.
Para onde finalmente chegamos? Existem diferenças fundamentais entre os componentes funcionais do React e os componentes baseados em classes? Claro, existem essas diferenças. Essas são diferenças no modelo mental do uso de tais componentes. Neste artigo, considerarei a diferença mais séria. Existe desde que, em 2015, os componentes funcionais apareceram, mas geralmente é ignorado. Consiste no fato de que os componentes funcionais capturam valores renderizados. Vamos falar sobre o que isso realmente significa.
Note-se que este material não constitui uma tentativa de avaliar componentes de diferentes tipos. Acabei de descrever a diferença entre os dois modelos de programação no React. Se você quiser saber mais sobre o uso de componentes funcionais à luz das inovações, consulte
esta lista de perguntas e respostas sobre ganchos.
Quais são os recursos do código de componentes com base em funções e classes?
Considere este componente:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Ele exibe um botão que, pressionando a função
setTimeout
, simula uma solicitação de rede e exibe uma caixa de mensagem confirmando a operação. Por exemplo, se '
props.user
'Dan'
estiver armazenado em
props.user
, na janela de mensagens, após três segundos,
'Followed Dan'
será exibido.
Observe que não importa se funções de seta ou declarações de função são usadas aqui. Uma construção da
function handleClick()
formulário
function handleClick()
funcionará exatamente da mesma maneira.
Como reescrever esse componente como uma classe? Se você apenas refizer o código examinado, convertendo-o no código de um componente com base em uma classe, obterá o seguinte:
class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); }; handleClick = () => { setTimeout(this.showMessage, 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
É geralmente aceito que dois desses fragmentos de código são equivalentes. E os desenvolvedores geralmente são completamente livres, no curso da refatoração de código, se transformam em outro, sem pensar nas possíveis consequências.
Esses trechos de código parecem equivalentesNo entanto, há uma pequena diferença entre esses trechos de código. Dê uma olhada neles. Veja a diferença? Por exemplo, eu não a vi imediatamente.
Além disso, consideraremos essa diferença, portanto, para aqueles que desejam entender a essência do que está acontecendo, como um exemplo prático desse código.
Antes de continuarmos, gostaria de enfatizar que a diferença em questão não tem nada a ver com os ganchos do React. Nos exemplos anteriores, a propósito, ganchos nem sequer são usados. É sobre a diferença entre funções e classes no React. E se você planeja usar muitos componentes funcionais em seus aplicativos React, convém entender essa diferença.
De fato, ilustraremos a diferença entre funções e classes pelo exemplo de um erro frequentemente encontrado nos aplicativos React.
O erro comum nos aplicativos React.
Abra
a página de exemplo que exibe uma lista que permite selecionar perfis de usuário e dois botões
Follow
exibidos pelos
ProfilePageClass
e
ProfilePageClass
, funcionais e baseados na classe, cujo código é mostrado acima.
Tente, para cada um desses botões, executar a seguinte sequência de ações:
- Clique no botão
- Altere o perfil selecionado antes de decorridos 3 segundos após clicar no botão.
- Leia o texto exibido na caixa de mensagem.
Feito isso, você notará os seguintes recursos:
- Quando você clica no botão formado pelo componente funcional com o perfil
Dan
selecionado e depois muda para o perfil Sophie
, 'Followed Dan'
será exibido na caixa de mensagem. - Se você fizer o mesmo com um botão formado por um componente com base em uma classe,
'Followed Sophie'
será exibida.
Recursos de componentes baseados em classeNeste exemplo, o comportamento do componente funcional está correto. Se eu me inscrevi no perfil de alguém e depois mudei para outro perfil, meu componente não deve duvidar de qual perfil eu me inscrevi. Obviamente, a implementação do mecanismo em questão com base no uso de classes contém um erro (a propósito, você definitivamente deve se tornar um assinante do
Sofia ).
Causas de mau funcionamento de componentes com base em classe
Por que um componente baseado em classe se comporta dessa maneira? Para entender isso, vamos dar uma olhada no método
showMessage
em nossa classe:
class ProfilePage extends React.Component { showMessage = () => { alert('Followed ' + this.props.user); };
Este método lê dados deste
this.props.user
. As propriedades no React são imutáveis, portanto não são alteradas. No entanto,
this
, como sempre, é uma entidade mutável.
De fato, o objetivo de ter
this
em uma classe reside na capacidade de mudar isso. A própria biblioteca do React realiza periodicamente
this
mutações, o que permite trabalhar com as versões mais recentes do método
render
e dos métodos do ciclo de vida dos componentes.
Como resultado, se o nosso componente for renderizado novamente durante a execução da solicitação, o
this.props
será alterado. Depois disso, o método
showMessage
lerá o valor do
user
da entidade de
props
"muito nova".
Isso permite que você faça uma observação interessante sobre as interfaces do usuário. Se dissermos que a interface do usuário, conceitualmente, é uma função do estado atual do aplicativo, os manipuladores de eventos fazem parte dos resultados da renderização - assim como os resultados visíveis da renderização. Nossos manipuladores de eventos "pertencem" a uma operação de renderização específica, juntamente com propriedades e estados específicos.
No entanto, agendar um tempo limite cujo retorno de chamada
this.props
lê viola essa conexão. O
showMessage
showMessage não
showMessage
"vinculado" a nenhuma operação de renderização específica; como resultado, "perde" as propriedades corretas. A leitura de dados
this
interrompe esta conexão.
Como, por meio de componentes baseados em classe, resolver o problema?
Imagine que não há componentes funcionais no React. Como então resolver este problema?
Precisamos de algum mecanismo para "restaurar" a conexão entre o método
render
com as propriedades corretas e o
showMessage
showMessage, que lê dados das propriedades. Esse mecanismo deve estar localizado em algum lugar onde a essência dos
props
com os dados corretos é perdida.
Uma maneira de fazer isso é ler
this.props
antecipadamente no manipulador de eventos e, em seguida, passar explicitamente o que foi lido para a função de retorno de chamada usada em
setTimeout
:
class ProfilePage extends React.Component { showMessage = (user) => { alert('Followed ' + user); }; handleClick = () => { const {user} = this.props; setTimeout(() => this.showMessage(user), 3000); }; render() { return <button onClick={this.handleClick}>Follow</button>; } }
Essa abordagem está
funcionando . Mas as construções adicionais usadas aqui, com o tempo, levarão a um aumento no volume do código e ao fato de que a probabilidade de erros nele aumentará. E se precisarmos de mais do que uma única propriedade? E se também precisarmos trabalhar com o estado? Se o método
showMessage
outro método e esse método ler
this.props.something
ou
this.state.something
, encontraremos o mesmo problema novamente. E, para resolvê-lo, teríamos que passar
this.props
e
this.state
como argumentos para todos os métodos chamados de
showMessage
.
Se for verdade, isso destruirá todas as conveniências que o uso de componentes com base em classes oferece. É difícil lembrar que trabalhar com métodos dessa maneira é difícil, é difícil automatizar, como resultado, os desenvolvedores geralmente, em vez de usar métodos semelhantes, concordam que há erros em seus projetos.
Da mesma forma, a incorporação do código de
alert
no
handleClick
não resolve um problema mais global. Precisamos estruturar o código para que ele possa ser dividido em vários métodos, mas também para que possamos ler as propriedades e o estado que correspondem à operação de renderização associada a uma chamada específica. A propósito, esse problema nem se aplica exclusivamente ao React. Você pode reproduzi-lo em qualquer biblioteca para desenvolver interfaces de usuário, que coloca dados em objetos mutáveis como
this
.
Talvez, para resolver esse problema, você possa vincular métodos a
this
no construtor?
class ProfilePage extends React.Component { constructor(props) { super(props); this.showMessage = this.showMessage.bind(this); this.handleClick = this.handleClick.bind(this); } showMessage() { alert('Followed ' + this.props.user); } handleClick() { setTimeout(this.showMessage, 3000); } render() { return <button onClick={this.handleClick}>Follow</button>; } }
Mas isso não resolve o nosso problema. Lembre-se de que estamos lendo os dados
this.props
tarde demais, e não na sintaxe usada! No entanto, esse problema será resolvido se confiarmos no fechamento do JavaScript.
Os desenvolvedores geralmente tentam evitar fechamentos, pois não é
fácil pensar em valores que, com o tempo, não podem sofrer mutações. Mas as propriedades do React são imutáveis! (Ou, no mínimo, isso é altamente recomendado). Isso permite que você pare de perceber os fechamentos como algo pelo qual o programador pode, como eles dizem, "dar um tiro no próprio pé".
Isso significa que, se você "bloquear" as propriedades ou o estado de uma operação de renderização específica no fechamento, sempre poderá contar com eles para não serem alterados.
class ProfilePage extends React.Component { render() {
Como você pode ver, aqui "capturamos" as propriedades durante a chamada para o método
render
.
Propriedades capturadas pela chamada de renderizaçãoCom essa abordagem, é garantido que qualquer código que esteja no método de
render
(incluindo
showMessage
) veja as propriedades capturadas durante uma chamada específica para esse método. Como resultado, o React não será mais capaz de nos impedir de fazer o que precisamos.
No método de
render
, você pode descrever quantas funções auxiliares desejar e todas elas poderão usar as propriedades e o estado “capturados”. Foi assim que o fechamento resolveu nosso problema.
Análise da solução do problema usando o fechamento
O que acabamos de chegar nos permite
resolver o problema , mas esse código parece estranho. Por que uma classe é necessária, se as funções são declaradas dentro do método
render
, e não como métodos de classe?
De fato, podemos simplificar esse código se livrando do "shell" na forma de uma classe que o rodeia:
function ProfilePage(props) { const showMessage = () => { alert('Followed ' + props.user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Aqui, como no exemplo anterior, as propriedades são capturadas na função, pois o React as transmite a ela como argumento. Diferente
this
, o React nunca
props
objeto de
props
.
Isso se torna um pouco mais óbvio se os
props
destruídos na declaração da função:
function ProfilePage({ user }) { const showMessage = () => { alert('Followed ' + user); }; const handleClick = () => { setTimeout(showMessage, 3000); }; return ( <button onClick={handleClick}>Follow</button> ); }
Quando o componente pai
ProfilePage
com outras propriedades, o React chama a função
ProfilePage
. Mas o manipulador de eventos que já foi chamado pertence à chamada anterior dessa função, esta chamada usa seu próprio valor de
user
e seu próprio
showMessage
showMessage, que lê esse valor. Tudo isso permanece intocado.
É por isso que, na versão
original do nosso exemplo, ao trabalhar com um componente funcional, selecionar outro perfil depois de clicar no botão correspondente antes da exibição da mensagem não muda nada. Se um perfil da
Sophie
foi selecionado antes de clicar no botão,
'Followed Sophie'
será exibida na janela de mensagem, aconteça o que acontecer.
Usando um componente funcionalEsse comportamento está correto (
a propósito, você também pode se inscrever no
Sunil ).
Agora descobrimos qual é a grande diferença entre funções e classes no React. Como já mencionado, estamos falando do fato de que componentes funcionais capturam valores. Agora vamos falar sobre ganchos.
Hooks
Ao usar ganchos, o princípio de "capturar valores" se estende ao estado. Considere o seguinte exemplo:
function MessageThread() { const [message, setMessage] = useState(''); const showMessage = () => { alert('You said: ' + message); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); }; return ( <> <input value={message} onChange={handleMessageChange} /> <button onClick={handleSendClick}>Send</button> </> ); }
→
Aqui você pode experimentar com ele
Embora este não seja um exemplo exemplar de uma interface de aplicativo de mensagens, este projeto ilustra a mesma idéia: se um usuário enviou uma mensagem, o componente não deve ficar confuso sobre qual mensagem foi enviada. A constante de
message
desse componente funcional captura o estado que "pertence" ao componente que torna o navegador o manipulador de cliques do botão que ele chama. Como resultado, a
message
armazena o que estava no campo de entrada no momento do clique no botão
Send
.
O problema de capturar propriedades e estados por componentes funcionais
Sabemos que os componentes funcionais no React, por padrão, capturam propriedades e estados. Mas e se precisarmos ler os dados mais recentes de propriedades ou estados que não pertencem a uma chamada de função específica? E se quisermos “
lê-los do futuro ”?
Nos componentes baseados em classe, isso pode ser feito simplesmente referindo-se a
this.props
ou
this.state
, pois é uma entidade mutável. Sua mudança está envolvida no React. Os componentes funcionais também podem trabalhar com valores mutáveis que são compartilhados por todos os componentes. Esses valores são chamados
ref
:
function MyComponent() { const ref = useRef(null);
No entanto, o programador precisa gerenciar esses valores independentemente.
A essência de
ref
desempenha o mesmo
papel que os campos de uma instância de uma classe. Esta é uma "saída de emergência" para um mundo imperativo mutável. Você pode estar familiarizado com o conceito de referências DOM, mas essa ideia é muito mais geral. Pode ser comparado a uma caixa na qual um programador pode colocar alguma coisa.
Mesmo externamente, uma construção da forma
this.something
parece uma imagem espelhada da construção de
this.something
. Eles são uma representação do mesmo conceito.
Por padrão, o React não cria entidades
ref
nos componentes funcionais para os valores mais recentes de propriedade ou estado. Em muitos casos, você não precisará deles, e sua criação automática seria uma perda de tempo. No entanto, o trabalho com eles, se necessário, pode ser organizado por conta própria:
function MessageThread() { const [message, setMessage] = useState(''); const latestMessage = useRef(''); const showMessage = () => { alert('You said: ' + latestMessage.current); }; const handleSendClick = () => { setTimeout(showMessage, 3000); }; const handleMessageChange = (e) => { setMessage(e.target.value); latestMessage.current = e.target.value; };
Se lermos a
message
em
showMessage
, veremos a mensagem que estava no campo no momento do clique no botão
Send
. Mas se você ler
latestMessage.current
, poderá obter o valor mais recente - mesmo se continuarmos inserindo texto no campo depois de clicar no botão
Send
.
Você pode comparar
este e
este exemplo para avaliar independentemente a diferença. O valor de
ref
é uma maneira de "evitar" a uniformidade da renderização; em alguns casos, pode ser muito útil.
Em geral, você deve evitar ler ou escrever valores
ref
durante o processo de renderização, porque esses valores são mutáveis. Nós nos esforçamos para tornar a renderização previsível. No entanto, se precisarmos obter o valor mais recente de algo armazenado nas propriedades ou no estado, atualizar manualmente o valor
ref
pode ser uma tarefa tediosa. Pode ser automatizado usando o efeito:
function MessageThread() { const [message, setMessage] = useState('');
→
Aqui está um exemplo que usa esse código
Estamos atribuindo um valor dentro do efeito, como resultado, o valor de
ref
será alterado somente após a atualização do DOM. Isso garante que nossa mutação não interrompa recursos como
Time Slicing e Suspense , que dependem da continuidade das operações de renderização.
Usar o valor
ref
dessa maneira geralmente não é necessário. A captura de propriedades ou estados geralmente parece ser um padrão muito melhor do comportamento padrão do sistema. No entanto, isso pode ser conveniente ao trabalhar com
APIs imperativas , como aquelas que usam intervalos ou assinaturas. Lembre-se de que você pode trabalhar dessa maneira com qualquer valor - com propriedades, com variáveis armazenadas em state, com todo o objeto
props
ou mesmo com uma função.
Além disso, esse padrão pode ser útil para fins de otimização. Por exemplo, quando algo como
useCallback
muda com muita frequência. É verdade que a
solução preferida é frequentemente
usar um redutor .
Sumário
Neste artigo, analisamos um dos padrões errados para o uso de componentes baseados em classe e falamos sobre como resolver esse problema com fechamentos. No entanto, você pode perceber que, ao tentar otimizar ganchos especificando uma matriz de dependências, poderá encontrar erros relacionados a fechamentos obsoletos. Isso significa que as falhas em si são um problema. Eu acho que não.
Como mostrado acima, os fechamentos, de fato, nos ajudam a resolver pequenos problemas difíceis de perceber. Eles também facilitam a gravação de código que funciona corretamente em
paralelo . Isso é possível devido ao fato de que dentro do componente as propriedades e o estado corretos com os quais esse componente foi renderizado estão "bloqueados".
Em todos os casos que vi até agora, o problema de "fechamentos obsoletos" ocorreu devido à suposição errônea de que "funções não mudam" ou que "propriedades sempre permanecem as mesmas". Espero que, depois de ler este material, você esteja convencido de que não é assim.
As funções “capturam” suas propriedades e estado - e, portanto, a compreensão de quais funções estão em questão também é importante. Isso não é um erro, é um recurso dos componentes funcionais. As funções não devem ser excluídas da "matriz de dependências" para
useEffect
ou
useCalback
, por exemplo. (Uma ferramenta adequada para resolver o problema é geralmente
useReducer
ou
useRef
. Falamos sobre isso acima e em breve prepararemos materiais que serão dedicados à escolha dessa ou daquela abordagem).
Se a maior parte do código em nossos aplicativos será baseada em componentes funcionais, isso significa que precisamos saber mais sobre
otimização de código e quais valores podem
mudar ao longo do tempo.
: « , , , , , ».
. , React , . , « », . , React .
, , .
React —