Componentes, Elementos e Instâncias em React

Você sabe qual a diferença e o porque eles existem?

Image for post
Image for post
Wuji, Taiji e Taijitu.

Muitas pessoas ficam confusas com as diferenças entre componentes, suas instâncias e elementos em React. Porque existem 3 termos para se referir a algo na mostrado na UI?

Se você é novo no React, você provavelmente só trabalhou com a classe do componente e a sua instância. Por exemplo, você deve ter declarado um componente Button, através da criação de uma classe. Quando o programa roda, você provavelmente terá várias instâncias desse componente, cada um deles com suas props e state. Isso é a tradicional orientação a objeto em programção UI. Porque introduzir elementos?

Nesse modelo tradicional de UI, é sua responsabilidade criar e destruir as instâncias de componentes filhos. Se um componente Form quer renderizar um Button, ele precisa criar uma instância, e manualmente manter ela atualizada com novas informações.

Esse é um pseudo código, mas é mais o menos isso que acabamos fazendo quando tentamos escrever componentes reutilizáveis na UI, se comportando consistentemente com orientação a objeto, do mesmo modo que o Backbone, por exemplo.

Cada componente tem uma referência com seu nó do DOM, e com as instâncias dos componentes filhos, criando, atualizando e destruindo eles quando é o momento certo. As linhas de códigos crescem assim como o quadrado da quantidade de estados que você tem que manter no componente, e como os componentes pais tem acesso direto as instâncias dos componentes filhos, fica difícil de desunir no futuro.

Agora, vamos falar de React…

No React, é nesse ponto onde Elementos fazem a diferença. Um elemento é um objeto descrevendo uma instância de um componente ou um nó DOM e suas propriedades. Ele contém informações apenas sobre o tipo do componente (por exemplo, Button, que falamos acima), suas propriedades (como cor, por exemplo) e seus elementos filhos.

Um Elemento não é uma instância. Ao invés disso, é uma maneira de dizer ao React o que você quer ver na sua UI. Você não pode chamar qualquer método no Elemento. Ele é apenas um objeto de descrição imutável com dois campos: type (string | Componente) e props | Objeto.

Quando o type do elemento é string, ele é um elemento representando um nó DOM com uma tag de mesmo nome, e props são seus atributos. Essa é o que React irá renderizar. Por exemplo:

{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
children: 'OK!'
}
}
}

Será representado em HTML como:

<button class='button button-blue'>
<b>
OK!
</b>
</button>

Perceba como o Elemento pode ser aninhado. Por convenção, quando nós queremos criar uma árvore de elementos, nós especificamos um ou mais elementos filhos como a propriedade children daquele elemento.

O que é importante aqui: Ambos children e elemento pai são apenas descrições e não instâncias. Eles não se referênciam a nada na tela. Você pode cria-los e joga-los fora, não irá importar.

Elementos React são fáceis de examinar, não precisam ser parseados, e claro, são muito mais leves que um nó DOM — eles são apenas objetos!

Porém, o type do elemento também pode ser uma função ou classe correspondendo a um Componente React:

{
type: Button,
props: {
color: 'blue',
children: 'OK!'
}
}

Essa é a idéia principal do React.

Um elemento descrevendo um componente também é um componente. Do mesmo modo que um elemento descrevendo um nó DOM. Eles podem ser aninhados uns com os outros.

Isso possibilita a criação de um DangerButton usando o Button com uma cor específica, sem se preocupar muito se o Button renderiza um button ou uma div, ou qualquer outra coisa:

const DangerButton = ({ children }) => ({
type: Button,
props: {
color: 'red',
children: children
}
});

Misturando eles:

const DeleteAccount = () => ({
type: 'div',
props: {
children: [{
type: 'p',
props: {
children: ‘Tem certeza?’
}
}, {
type: DangerButton,
props: {
children: ’Sim’
}
}, {
type: Button,
props: {
color: 'blue',
children: ’Não’
}
}]
});

Se você preferir JSX:

const DeleteAccount = () => (
<div>
<p>Tem certeza?</p>
<DangerButton>Sim</DangerButton>
<Button color='blue'>Não</Button>
</div>
);

Isso mantém os componentes desacoplados um dos outros. Podendo manter suas relações com outros componentes através de composição.

Quando React encontra um elemento com um type função ou classe, ele irá saber que deve perguntar para aquele componente qual elemento ele descreve baseado nessas props.

Algo como:

{
type: Button,
props: {
color: 'blue',
children: 'OK!'
}
}

React irá perguntar para Button, o que ele descreve, e receberá em volta:

{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
children: 'OK!'
}
}
}

React irá repetir esse processo até ele resolver todos os types para cada componente na sua página. Descrevendo completamente sua árvore DOM.

React é como uma criança perguntando o que é esse X? para cada X que ele encontra, ele repete esse processo até que tudo tenha sido esclarecido!

Lembra o Form do exemplo acima? Podemos escrever ele assim:

const Form = ({ isSubmitted, buttonText }) => {
if (isSubmitted) {
// Form enviado, retorne um Elemento Message
return {
type: Message,
props: {
text: ‘Sucesso!’
}
};
}
// Form ainda visível, retorne um Elemento Button
return {
type: Button,
props: {
children: buttonText,
color: 'blue'
}
};
};

É isso! Para um Componente React, props são os dados de entrada, e uma árvore de elementos é o objeto de saída.

A árvore de elementos retornada pode conter elementos descrevendo nós DOM e elementos descrevendo outros componentes. Isso permite a composição de partes independentes da UI sem precisar depender da estrutura interna do DOM.

Nós deixamos para o React, criar, atualizar e destruir as instâncias. Nós apenas descrevemos quais elementos queremos retornados dos componentes e React irá cuidar do resto.

No exemplo acima, Form, Message e Button são componentes React. Eles podem ser escrito em funções, como acima, ou classes descendentes de React.Component:

class Button extends React.Component {
render() {
const { children, color } = this.props;
// Retorna um elemento descrevendo um
// <button><b>{children}</b></button>
return {
type: 'button',
props: {
className: 'button button-' + color,
children: {
type: 'b',
props: {
children: children
}
}
}
};
}
}

Quando um componente é definido como classe, ele é um pouco mais poderoso que um componente funcional. Ele pode guardar estado local e realizar algumas lógicas quando sua estrutura DOM é criada ou destruída. Um componente funcional é menos poderoso, mas é mais simples e funciona como um componente de classe com apenas o método render().

Porém, sendo funções ou classes, fundamentalmente, eles todos são componentes para o React. Eles recebem props como dados de entrada e retornam elementos como saída.

Quando você chama:

ReactDOM.render({
type: Form,
props: {
isSubmitted: false,
buttonText: 'OK!'
}
}, document.getElementById('root'));

React irá perguntar para o Form quais elementos ele retorna, baseado nas props. E gradualmente irá refinar o conhecimento da árvore de componentes, tudo em termos de objetos primitivos:

// React: Você me falou…
{
type: Form,
props: {
isSubmitted: false,
buttonText: 'OK!'
}
}
// React: …o Form me falou…
{
type: Button,
props: {
children: 'OK!',
color: 'blue'
}
}
// React: …e o Button me falou isso! Eu acho que terminei.
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}

No final do processo, React saberá o resultado da árvore do DOM, e uma função de renderização como ReactDOM ou React Native podem aplicar as mudanças mínimas necessárias para atualizar os nós.

Esse processo de refinamento gradativo é a razão pela qual apps em React são fáceis de otimizar. Se algumas partes da árvore de componentes ficar muito grande para o React visitar eficientemente, você pode então, pular a etapada de refinamento e verificar se certas partes das props não mudaram. É bem rápido de calcular quais props mudaram se elas forem imutáveis. Isso quer dizer que React e imutabilidade trabalham perfeitamente lado a lado! E podem disponibilizar uma otimização com um mínimo de esforço.

Você deve ter notado que eu falei muito sobre componentes e elementos e não falei quase nada sobre instâncias. A verdade é, instâncias tem uma importância muito menor em React do que na maioria dos frameworks UI.

Apenas componentes declarados como classes tem instâncias, e você nunca cria eles diretamente, React faz isso para você. Claro que há mecanismos para um componente pai ter acesso a instância do componente filho, mas elas são usadas apenas para interações imperativas (como focus em campo), e devem ser evitadas.

React irá tomar conta de criar instâncias para cada classe de componente, então você pode escrever seus componentes pensando em orientação a objetos, com métodos e estado local, mas, fora isso, instâncias não são muito importantes para o modo de programação usando React, no fim, elas são controladas pelo próprio React.

Recapitulando

Um elemento é um simples objeto descrevendo o que você quer que apareça na UI em termos de nó DOM e outros componentes. Elementos podem conter outros elementos nas suas props.

Um componente pode ser duas coisas. Pode ser uma classe com um método render() e suas heranças do React.Component. Ou pode ser uma função. Em ambos os casos, elas recebem props como entrada de dados e retornam uma árvore de elementos como resultado.

Quando um componente recebe props como entrada de dados, é porque um componente pai retornou um elemento descrevendo seu type com aquelas props. É por causa disso que pessoas dizem que props em React caminham de uma única maneira: de componente pai para componentes filhos.

Uma instância é o que você referência com this na classe do componente. É útil para guardar estado local e reagir para eventos de ciclo de vida.

Componentes funcionais não criam instâncias. Componentes criados como classe criam instâncias. Mas você nunca precisará criar uma instância de componente diretamente — React irá cuidar disso.

Para finalizar, para criar elementos, use React.createElement(), usando JSX ou qualquer utilitário de elementos. Eu recomendo você à não escrever simples objetos como código real — apenas saiba que eles são simples objetos por baixo dos panos.

Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store