Entendendo a Arquitetura do React
React é uma biblioteca que revolucionou a forma como construímos interfaces. Sua beleza reside na simplicidade do modelo mental: componentes são funções que retornam descrições de UI, e React gerencia a atualização eficiente do DOM. Para compreender verdadeiramente como React funciona, vamos construir uma versão simplificada que captura os conceitos essenciais.
A arquitetura do React se baseia em três pilares fundamentais: a representação declarativa da UI através de elementos (criados com createElement), a transformação dessa representação em DOM real (através de render), e a capacidade de componentes manterem estado e reagirem a mudanças (através de hooks). Entender esses pilares é crucial porque quando você domina os mecanismos internos, escreve código React mais eficaz e consegue debugar problemas muito mais rapidamente.
O que é um Elemento React?
Um elemento React não é um componente — é a representação mais básica de algo que você quer renderizar. Pense em um elemento como um objeto JavaScript comum que descreve: "Eu quero um botão, com essas props, com esses filhos". Isso é tudo. Não há magia aqui. Um elemento é apenas dados, uma estrutura que diz ao React o que renderizar.
// Um elemento é simplesmente um objeto descritivo
const elemento = {
type: 'button',
props: {
className: 'btn-primary',
onClick: () => alert('Clicado!'),
children: ['Clique aqui']
}
};
Implementando createElement
createElement é a função que você chama (ou que o JSX transpila para chamar) para criar esses objetos que descrevem a UI. Quando você escreve <Button />, o transpilador converte isso em createElement(Button, null). É um passo crucial compreender que JSX é apenas açúcar sintático.
A Função createElement
A função createElement recebe três argumentos principais: o tipo (pode ser uma string como 'div' ou uma função de componente), as props (propriedades e atributos), e os filhos (children). Ela retorna um objeto simples que descreve o elemento.
function createElement(type, props, ...children) {
// Se não há props, inicializa um objeto vazio
const elementProps = props || {};
// Os children precisam ser normalizados: arrays aninhados viram um array único
const flatChildren = children.flat();
// Se há filhos, eles são adicionados às props
if (flatChildren.length > 0) {
elementProps.children = flatChildren.length === 1
? flatChildren[0]
: flatChildren;
}
// Retorna o objeto que descreve o elemento
return {
type,
props: elementProps
};
}
// Exemplos de uso:
const elemento1 = createElement('div', { className: 'container' },
createElement('h1', null, 'Olá Mundo')
);
const elemento2 = createElement('button', { onClick: () => {} }, 'Enviar');
console.log(elemento1);
// Output: {
// type: 'div',
// props: {
// className: 'container',
// children: { type: 'h1', props: { children: 'Olá Mundo' } }
// }
// }
Lidando com Componentes Funcionais
Componentes funcionais são diferentes de elementos primitivos. Um componente é uma função que retorna um elemento. Quando createElement recebe uma função (ao invés de uma string), ele não deve criar um objeto imediatamente — esse trabalho será feito durante a renderização.
function Botao({ label, onClick }) {
return createElement('button', { onClick, className: 'btn' }, label);
}
// Quando chamamos createElement com um componente:
const meuBotao = createElement(Botao, { label: 'Clique', onClick: () => {} });
console.log(meuBotao);
// Output: {
// type: Botao, // A função em si, não o resultado
// props: { label: 'Clique', onClick: ... }
// }
Implementando Render
render é onde a mágica acontece: ele pega esses objetos descritivos (elementos) e os transforma em DOM real, inserindo-os na página. Este é também o ponto onde precisamos lidar com atualizações eficientes — React não reconstrói tudo do zero, ele difere o novo estado do antigo e aplica apenas as mudanças necessárias.
A Função Render Básica
Começamos com uma implementação simples que apenas cria o DOM. Depois evoluiremos para lidar com atualizações e reconciliação.
function render(elemento, container) {
// Se o elemento é texto ou número, cria um nó de texto
if (typeof elemento === 'string' || typeof elemento === 'number') {
container.appendChild(document.createTextNode(elemento));
return;
}
// Se é um array, renderiza cada item
if (Array.isArray(elemento)) {
elemento.forEach(el => render(el, container));
return;
}
// Se é null ou undefined, não faz nada
if (!elemento) {
return;
}
const { type, props } = elemento;
// Se type é uma string, é um elemento HTML primitivo
if (typeof type === 'string') {
// Cria o elemento DOM
const domElement = document.createElement(type);
// Aplica as props (atributos, listeners, etc)
Object.entries(props).forEach(([key, value]) => {
if (key === 'children') {
// Children precisam ser renderizados recursivamente
if (value) {
render(value, domElement);
}
} else if (key.startsWith('on')) {
// Listeners de eventos (onClick, onChange, etc)
const eventName = key.substring(2).toLowerCase();
domElement.addEventListener(eventName, value);
} else if (key !== 'key') {
// Atributos normais
domElement.setAttribute(key, value);
}
});
container.appendChild(domElement);
} else if (typeof type === 'function') {
// Se type é uma função, é um componente
// Chama a função com as props para obter o elemento que ela retorna
const componentElement = type(props);
// Renderiza o elemento retornado
render(componentElement, container);
}
}
// Exemplo de uso:
const app = createElement('div', { className: 'app' },
createElement('h1', null, 'Meu Mini React'),
createElement('p', null, 'Isso é incrível!')
);
render(app, document.getElementById('root'));
Renderização com Componentes e Estado
A coisa fica mais interessante quando queremos que componentes mantenham estado. Para isso, precisamos rastrear qual componente está sendo renderizado no momento. Esta é a base dos hooks do React.
// Rastreadores globais para hooks
let currentComponent = null;
let componentHooks = new Map();
let hookIndex = 0;
function render(elemento, container) {
if (typeof elemento === 'string' || typeof elemento === 'number') {
container.appendChild(document.createTextNode(elemento));
return;
}
if (Array.isArray(elemento)) {
elemento.forEach(el => render(el, container));
return;
}
if (!elemento) {
return;
}
const { type, props } = elemento;
if (typeof type === 'string') {
const domElement = document.createElement(type);
Object.entries(props).forEach(([key, value]) => {
if (key === 'children') {
if (value) {
render(value, domElement);
}
} else if (key.startsWith('on')) {
const eventName = key.substring(2).toLowerCase();
domElement.addEventListener(eventName, value);
} else if (key !== 'key') {
domElement.setAttribute(key, value);
}
});
container.appendChild(domElement);
} else if (typeof type === 'function') {
// Antes de chamar o componente, definimos qual é o componente atual
currentComponent = type;
hookIndex = 0;
// Se não há hooks registrados para este componente, cria um array vazio
if (!componentHooks.has(type)) {
componentHooks.set(type, []);
}
const componentElement = type(props);
render(componentElement, container);
}
}
Implementando Hooks (useState e useEffect)
Hooks são funções que permitem componentes funcionais acessarem recursos que antes eram exclusivos de componentes de classe, como estado e efeitos colaterais. useState é o hook mais fundamental — ele permite que um componente tenha estado. useEffect permite executar código em resposta a mudanças.
useState: Adicionando Estado a Componentes Funcionais
useState retorna um array com dois elementos: o valor atual do estado e uma função para atualizá-lo. Cada componente possui seu próprio conjunto de estados, e precisamos rastreá-los por índice (é por isso que a ordem dos hooks importa).
function useState(initialValue) {
// Obtém os hooks do componente atual
const hooks = componentHooks.get(currentComponent);
// O índice do hook atual (useState, outro useState, useEffect, etc)
const index = hookIndex;
hookIndex++;
// Se este hook não foi inicializado ainda, inicializa
if (!hooks[index]) {
hooks[index] = {
value: typeof initialValue === 'function'
? initialValue()
: initialValue,
setState: null // Será definido abaixo
};
}
const hook = hooks[index];
// Define a função setState que atualiza o estado
hook.setState = (newValue) => {
const actualNewValue = typeof newValue === 'function'
? newValue(hook.value)
: newValue;
// Se o valor não mudou, não faz nada
if (actualNewValue === hook.value) {
return;
}
// Atualiza o valor
hook.value = actualNewValue;
// Reinicializa o índice de hooks
hookIndex = 0;
// Renderiza novamente o componente
const currentHooks = componentHooks.get(currentComponent);
const componentElement = currentComponent({});
// Encontra o container anterior e limpa
const container = document.getElementById('root');
container.innerHTML = '';
// Renderiza novamente
render(componentElement, container);
};
return [hook.value, hook.setState];
}
// Exemplo prático:
function Contador() {
const [count, setCount] = useState(0);
return createElement('div', { className: 'contador' },
createElement('p', null, `Contador: ${count}`),
createElement('button', {
onClick: () => setCount(count + 1)
}, 'Incrementar')
);
}
// Uso:
render(createElement(Contador, {}), document.getElementById('root'));
useEffect: Efeitos Colaterais e Cleanup
useEffect permite executar código após o componente ser renderizado. É útil para requisições, subscrições, e limpeza. Ele aceita uma função e um array de dependências — o efeito é executado quando qualquer dependência muda (ou em toda renderização, se não houver dependências).
function useEffect(callback, dependencies) {
const hooks = componentHooks.get(currentComponent);
const index = hookIndex;
hookIndex++;
// Inicializa o hook se necessário
if (!hooks[index]) {
hooks[index] = {
cleanup: null,
dependencies: null
};
}
const hook = hooks[index];
const hasNoDependencies = !dependencies;
const dependenciesChanged = !hook.dependencies ||
dependencies.some((dep, i) => dep !== hook.dependencies[i]);
// Executa o efeito se não há dependências ou se elas mudaram
if (hasNoDependencies || dependenciesChanged) {
// Limpa o efeito anterior, se existir
if (hook.cleanup) {
hook.cleanup();
}
// Executa o novo efeito
const cleanup = callback();
hook.cleanup = typeof cleanup === 'function' ? cleanup : null;
hook.dependencies = dependencies;
}
}
// Exemplo: fetch de dados
function Usuario({ userId }) {
const [usuario, setUsuario] = useState(null);
const [carregando, setCarregando] = useState(true);
useEffect(() => {
// Simula uma requisição
setCarregando(true);
setTimeout(() => {
setUsuario({ id: userId, nome: 'João Silva' });
setCarregando(false);
}, 500);
// Cleanup (opcional)
return () => {
console.log('Componente foi desmontado ou dependências mudaram');
};
}, [userId]); // Reexecuta quando userId muda
if (carregando) {
return createElement('p', null, 'Carregando...');
}
return createElement('div', null,
createElement('h2', null, usuario.nome),
createElement('p', null, `ID: ${usuario.id}`)
);
}
Colocando Tudo Junto: Um Exemplo Completo
Agora vamos integrar tudo em um exemplo funcional que demonstra createElement, render e hooks trabalhando juntos.
// Sistema de hooks
let currentComponent = null;
let componentHooks = new Map();
let hookIndex = 0;
function useState(initialValue) {
const hooks = componentHooks.get(currentComponent);
const index = hookIndex;
hookIndex++;
if (!hooks[index]) {
hooks[index] = {
value: typeof initialValue === 'function' ? initialValue() : initialValue,
setState: null
};
}
const hook = hooks[index];
hook.setState = (newValue) => {
const actualNewValue = typeof newValue === 'function'
? newValue(hook.value)
: newValue;
if (actualNewValue === hook.value) return;
hook.value = actualNewValue;
hookIndex = 0;
const container = document.getElementById('root');
container.innerHTML = '';
const componentElement = currentComponent({});
render(componentElement, container);
};
return [hook.value, hook.setState];
}
function useEffect(callback, dependencies) {
const hooks = componentHooks.get(currentComponent);
const index = hookIndex;
hookIndex++;
if (!hooks[index]) {
hooks[index] = { cleanup: null, dependencies: null };
}
const hook = hooks[index];
const hasNoDependencies = !dependencies;
const dependenciesChanged = !hook.dependencies ||
dependencies.some((dep, i) => dep !== hook.dependencies[i]);
if (hasNoDependencies || dependenciesChanged) {
if (hook.cleanup) hook.cleanup();
const cleanup = callback();
hook.cleanup = typeof cleanup === 'function' ? cleanup : null;
hook.dependencies = dependencies;
}
}
// Core functions
function createElement(type, props, ...children) {
const elementProps = props || {};
const flatChildren = children.flat();
if (flatChildren.length > 0) {
elementProps.children = flatChildren.length === 1
? flatChildren[0]
: flatChildren;
}
return { type, props: elementProps };
}
function render(elemento, container) {
if (typeof elemento === 'string' || typeof elemento === 'number') {
container.appendChild(document.createTextNode(elemento));
return;
}
if (Array.isArray(elemento)) {
elemento.forEach(el => render(el, container));
return;
}
if (!elemento) return;
const { type, props } = elemento;
if (typeof type === 'string') {
const domElement = document.createElement(type);
Object.entries(props).forEach(([key, value]) => {
if (key === 'children') {
if (value) render(value, domElement);
} else if (key.startsWith('on')) {
const eventName = key.substring(2).toLowerCase();
domElement.addEventListener(eventName, value);
} else if (key !== 'key') {
domElement.setAttribute(key, value);
}
});
container.appendChild(domElement);
} else if (typeof type === 'function') {
currentComponent = type;
hookIndex = 0;
if (!componentHooks.has(type)) {
componentHooks.set(type, []);
}
const componentElement = type(props);
render(componentElement, container);
}
}
// Componentes
function App() {
const [mensagem, setMensagem] = useState('Bem-vindo ao Mini React!');
const [cliques, setCliques] = useState(0);
useEffect(() => {
console.log(`Cliques: ${cliques}`);
}, [cliques]);
return createElement('div', { style: 'text-align: center; padding: 20px;' },
createElement('h1', null, mensagem),
createElement('p', null, `Você clicou ${cliques} vezes`),
createElement('button', {
onClick: () => setCliques(cliques + 1),
style: 'padding: 10px 20px; font-size: 16px; cursor: pointer;'
}, 'Clique aqui')
);
}
// Inicializa
render(createElement(App, {}), document.getElementById('root'));
HTML para testar:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Mini React</title>
</head>
<body>
<div id="root"></div>
<script src="mini-react.js"></script>
</body>
</html>
Conclusão
Implementar um mini React do zero ensina três lições fundamentais que transformam sua compreensão sobre a biblioteca. Primeiro, elementos são apenas objetos JavaScript — não há magia, apenas descrições de UI que o React interpreta. Compreender que JSX é açúcar sintático para createElement remove muito da "mágica negra" que envolta React. Segundo, hooks funcionam através de rastreamento de índice — a ordem importa porque React usa a posição do hook na renderização para identificá-lo, não nomes. Isso explica por que não podemos usar hooks condicionalmente. Terceiro, renderização é recursiva e componentes são funções — quando você entende que um componente é simplesmente uma função que retorna um elemento, e que esse elemento é renderizado recursivamente, a maioria dos comportamentos estranhos do React fazem sentido perfeito.
Este exercício também revela limitações importantes da nossa implementação (sem virtual DOM, sem diffing eficiente, sem limpeza de memória), que nos leva a apreciar a engenharia elegante que a equipe do React fez. Use esse conhecimento não apenas para entender React melhor, mas para fazer decisões melhores ao construir componentes.