React & Frontend

Implementando um Mini React do Zero: createElement, render e Hooks: Do Básico ao Avançado

15 min de leitura

Implementando um Mini React do Zero: createElement, render e Hooks: Do Básico ao Avançado

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 ), a transformação dessa representação em DOM real (através de ), 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

<h2>Entendendo a Arquitetura do React</h2>

<p>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.</p>

<p>A arquitetura do React se baseia em três pilares fundamentais: a representação declarativa da UI através de elementos (criados com <code>createElement</code>), a transformação dessa representação em DOM real (através de <code>render</code>), 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.</p>

<h3>O que é um Elemento React?</h3>

<p>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: &quot;Eu quero um botão, com essas props, com esses filhos&quot;. Isso é tudo. Não há magia aqui. Um elemento é apenas dados, uma estrutura que diz ao React o que renderizar.</p>

<pre><code class="language-javascript">// Um elemento é simplesmente um objeto descritivo

const elemento = {

type: &#039;button&#039;,

props: {

className: &#039;btn-primary&#039;,

onClick: () =&gt; alert(&#039;Clicado!&#039;),

children: [&#039;Clique aqui&#039;]

}

};</code></pre>

<h2>Implementando createElement</h2>

<p><code>createElement</code> é a função que você chama (ou que o JSX transpila para chamar) para criar esses objetos que descrevem a UI. Quando você escreve <code>&lt;Button /&gt;</code>, o transpilador converte isso em <code>createElement(Button, null)</code>. É um passo crucial compreender que JSX é apenas açúcar sintático.</p>

<h3>A Função createElement</h3>

<p>A função <code>createElement</code> recebe três argumentos principais: o tipo (pode ser uma string como <code>&#039;div&#039;</code> ou uma função de componente), as props (propriedades e atributos), e os filhos (children). Ela retorna um objeto simples que descreve o elemento.</p>

<pre><code class="language-javascript">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 &gt; 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(&#039;div&#039;, { className: &#039;container&#039; },

createElement(&#039;h1&#039;, null, &#039;Olá Mundo&#039;)

);

const elemento2 = createElement(&#039;button&#039;, { onClick: () =&gt; {} }, &#039;Enviar&#039;);

console.log(elemento1);

// Output: {

// type: &#039;div&#039;,

// props: {

// className: &#039;container&#039;,

// children: { type: &#039;h1&#039;, props: { children: &#039;Olá Mundo&#039; } }

// }

// }</code></pre>

<h3>Lidando com Componentes Funcionais</h3>

<p>Componentes funcionais são diferentes de elementos primitivos. Um componente é uma função que retorna um elemento. Quando <code>createElement</code> 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.</p>

<pre><code class="language-javascript">function Botao({ label, onClick }) {

return createElement(&#039;button&#039;, { onClick, className: &#039;btn&#039; }, label);

}

// Quando chamamos createElement com um componente:

const meuBotao = createElement(Botao, { label: &#039;Clique&#039;, onClick: () =&gt; {} });

console.log(meuBotao);

// Output: {

// type: Botao, // A função em si, não o resultado

// props: { label: &#039;Clique&#039;, onClick: ... }

// }</code></pre>

<h2>Implementando Render</h2>

<p><code>render</code> é 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.</p>

<h3>A Função Render Básica</h3>

<p>Começamos com uma implementação simples que apenas cria o DOM. Depois evoluiremos para lidar com atualizações e reconciliação.</p>

<pre><code class="language-javascript">function render(elemento, container) {

// Se o elemento é texto ou número, cria um nó de texto

if (typeof elemento === &#039;string&#039; || typeof elemento === &#039;number&#039;) {

container.appendChild(document.createTextNode(elemento));

return;

}

// Se é um array, renderiza cada item

if (Array.isArray(elemento)) {

elemento.forEach(el =&gt; 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 === &#039;string&#039;) {

// Cria o elemento DOM

const domElement = document.createElement(type);

// Aplica as props (atributos, listeners, etc)

Object.entries(props).forEach(([key, value]) =&gt; {

if (key === &#039;children&#039;) {

// Children precisam ser renderizados recursivamente

if (value) {

render(value, domElement);

}

} else if (key.startsWith(&#039;on&#039;)) {

// Listeners de eventos (onClick, onChange, etc)

const eventName = key.substring(2).toLowerCase();

domElement.addEventListener(eventName, value);

} else if (key !== &#039;key&#039;) {

// Atributos normais

domElement.setAttribute(key, value);

}

});

container.appendChild(domElement);

} else if (typeof type === &#039;function&#039;) {

// 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(&#039;div&#039;, { className: &#039;app&#039; },

createElement(&#039;h1&#039;, null, &#039;Meu Mini React&#039;),

createElement(&#039;p&#039;, null, &#039;Isso é incrível!&#039;)

);

render(app, document.getElementById(&#039;root&#039;));</code></pre>

<h3>Renderização com Componentes e Estado</h3>

<p>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.</p>

<pre><code class="language-javascript">// Rastreadores globais para hooks

let currentComponent = null;

let componentHooks = new Map();

let hookIndex = 0;

function render(elemento, container) {

if (typeof elemento === &#039;string&#039; || typeof elemento === &#039;number&#039;) {

container.appendChild(document.createTextNode(elemento));

return;

}

if (Array.isArray(elemento)) {

elemento.forEach(el =&gt; render(el, container));

return;

}

if (!elemento) {

return;

}

const { type, props } = elemento;

if (typeof type === &#039;string&#039;) {

const domElement = document.createElement(type);

Object.entries(props).forEach(([key, value]) =&gt; {

if (key === &#039;children&#039;) {

if (value) {

render(value, domElement);

}

} else if (key.startsWith(&#039;on&#039;)) {

const eventName = key.substring(2).toLowerCase();

domElement.addEventListener(eventName, value);

} else if (key !== &#039;key&#039;) {

domElement.setAttribute(key, value);

}

});

container.appendChild(domElement);

} else if (typeof type === &#039;function&#039;) {

// 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);

}

}</code></pre>

<h2>Implementando Hooks (useState e useEffect)</h2>

<p>Hooks são funções que permitem componentes funcionais acessarem recursos que antes eram exclusivos de componentes de classe, como estado e efeitos colaterais. <code>useState</code> é o hook mais fundamental — ele permite que um componente tenha estado. <code>useEffect</code> permite executar código em resposta a mudanças.</p>

<h3>useState: Adicionando Estado a Componentes Funcionais</h3>

<p><code>useState</code> 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).</p>

<pre><code class="language-javascript">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 === &#039;function&#039;

? initialValue()

: initialValue,

setState: null // Será definido abaixo

};

}

const hook = hooks[index];

// Define a função setState que atualiza o estado

hook.setState = (newValue) =&gt; {

const actualNewValue = typeof newValue === &#039;function&#039;

? 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(&#039;root&#039;);

container.innerHTML = &#039;&#039;;

// Renderiza novamente

render(componentElement, container);

};

return [hook.value, hook.setState];

}

// Exemplo prático:

function Contador() {

const [count, setCount] = useState(0);

return createElement(&#039;div&#039;, { className: &#039;contador&#039; },

createElement(&#039;p&#039;, null, Contador: ${count}),

createElement(&#039;button&#039;, {

onClick: () =&gt; setCount(count + 1)

}, &#039;Incrementar&#039;)

);

}

// Uso:

render(createElement(Contador, {}), document.getElementById(&#039;root&#039;));</code></pre>

<h3>useEffect: Efeitos Colaterais e Cleanup</h3>

<p><code>useEffect</code> 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).</p>

<pre><code class="language-javascript">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) =&gt; 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 === &#039;function&#039; ? cleanup : null;

hook.dependencies = dependencies;

}

}

// Exemplo: fetch de dados

function Usuario({ userId }) {

const [usuario, setUsuario] = useState(null);

const [carregando, setCarregando] = useState(true);

useEffect(() =&gt; {

// Simula uma requisição

setCarregando(true);

setTimeout(() =&gt; {

setUsuario({ id: userId, nome: &#039;João Silva&#039; });

setCarregando(false);

}, 500);

// Cleanup (opcional)

return () =&gt; {

console.log(&#039;Componente foi desmontado ou dependências mudaram&#039;);

};

}, [userId]); // Reexecuta quando userId muda

if (carregando) {

return createElement(&#039;p&#039;, null, &#039;Carregando...&#039;);

}

return createElement(&#039;div&#039;, null,

createElement(&#039;h2&#039;, null, usuario.nome),

createElement(&#039;p&#039;, null, ID: ${usuario.id})

);

}</code></pre>

<h2>Colocando Tudo Junto: Um Exemplo Completo</h2>

<p>Agora vamos integrar tudo em um exemplo funcional que demonstra createElement, render e hooks trabalhando juntos.</p>

<pre><code class="language-javascript">// 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 === &#039;function&#039; ? initialValue() : initialValue,

setState: null

};

}

const hook = hooks[index];

hook.setState = (newValue) =&gt; {

const actualNewValue = typeof newValue === &#039;function&#039;

? newValue(hook.value)

: newValue;

if (actualNewValue === hook.value) return;

hook.value = actualNewValue;

hookIndex = 0;

const container = document.getElementById(&#039;root&#039;);

container.innerHTML = &#039;&#039;;

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) =&gt; dep !== hook.dependencies[i]);

if (hasNoDependencies || dependenciesChanged) {

if (hook.cleanup) hook.cleanup();

const cleanup = callback();

hook.cleanup = typeof cleanup === &#039;function&#039; ? cleanup : null;

hook.dependencies = dependencies;

}

}

// Core functions

function createElement(type, props, ...children) {

const elementProps = props || {};

const flatChildren = children.flat();

if (flatChildren.length &gt; 0) {

elementProps.children = flatChildren.length === 1

? flatChildren[0]

: flatChildren;

}

return { type, props: elementProps };

}

function render(elemento, container) {

if (typeof elemento === &#039;string&#039; || typeof elemento === &#039;number&#039;) {

container.appendChild(document.createTextNode(elemento));

return;

}

if (Array.isArray(elemento)) {

elemento.forEach(el =&gt; render(el, container));

return;

}

if (!elemento) return;

const { type, props } = elemento;

if (typeof type === &#039;string&#039;) {

const domElement = document.createElement(type);

Object.entries(props).forEach(([key, value]) =&gt; {

if (key === &#039;children&#039;) {

if (value) render(value, domElement);

} else if (key.startsWith(&#039;on&#039;)) {

const eventName = key.substring(2).toLowerCase();

domElement.addEventListener(eventName, value);

} else if (key !== &#039;key&#039;) {

domElement.setAttribute(key, value);

}

});

container.appendChild(domElement);

} else if (typeof type === &#039;function&#039;) {

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(&#039;Bem-vindo ao Mini React!&#039;);

const [cliques, setCliques] = useState(0);

useEffect(() =&gt; {

console.log(Cliques: ${cliques});

}, [cliques]);

return createElement(&#039;div&#039;, { style: &#039;text-align: center; padding: 20px;&#039; },

createElement(&#039;h1&#039;, null, mensagem),

createElement(&#039;p&#039;, null, Você clicou ${cliques} vezes),

createElement(&#039;button&#039;, {

onClick: () =&gt; setCliques(cliques + 1),

style: &#039;padding: 10px 20px; font-size: 16px; cursor: pointer;&#039;

}, &#039;Clique aqui&#039;)

);

}

// Inicializa

render(createElement(App, {}), document.getElementById(&#039;root&#039;));</code></pre>

<p>HTML para testar:</p>

<pre><code class="language-html">&lt;!DOCTYPE html&gt;

&lt;html lang=&quot;pt-BR&quot;&gt;

&lt;head&gt;

&lt;meta charset=&quot;UTF-8&quot;&gt;

&lt;title&gt;Mini React&lt;/title&gt;

&lt;/head&gt;

&lt;body&gt;

&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;

&lt;script src=&quot;mini-react.js&quot;&gt;&lt;/script&gt;

&lt;/body&gt;

&lt;/html&gt;</code></pre>

<h2>Conclusão</h2>

<p>Implementar um mini React do zero ensina três lições fundamentais que transformam sua compreensão sobre a biblioteca. <strong>Primeiro</strong>, 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 <code>createElement</code> remove muito da &quot;mágica negra&quot; que envolta React. <strong>Segundo</strong>, 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. <strong>Terceiro</strong>, 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.</p>

<p>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.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://react.dev/reference/react/createElement" target="_blank" rel="noopener noreferrer">React Official Documentation - Creating Elements</a></li>

<li><a href="https://react.dev/reference/react/hooks" target="_blank" rel="noopener noreferrer">React Hooks Documentation</a></li>

<li><a href="https://egghead.io/courses/the-beginner-s-guide-to-react" target="_blank" rel="noopener noreferrer">Building React from Scratch by Kent C. Dodds</a></li>

<li><a href="https://javascript.info/generators" target="_blank" rel="noopener noreferrer">JavaScript.info - Generators and Iterables</a></li>

<li><a href="https://overreacted.io/" target="_blank" rel="noopener noreferrer">A Deep Dive into React Hooks by Dan Abramov</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Guia Completo de Formulários Multi-step em React: Estado, Validação e Navegação
Guia Completo de Formulários Multi-step em React: Estado, Validação e Navegação

Entendendo Formulários Multi-step em React Um formulário multi-step, também c...

Como Usar Bundle Analysis em React: Webpack Bundle Analyzer e Tree Shaking em Produção
Como Usar Bundle Analysis em React: Webpack Bundle Analyzer e Tree Shaking em Produção

Entendendo Bundle Analysis e sua Importância Bundle analysis é o processo de...

Como Usar SWR em React: Estratégia Stale-While-Revalidate na Prática em Produção
Como Usar SWR em React: Estratégia Stale-While-Revalidate na Prática em Produção

SWR em React: Estratégia Stale-While-Revalidate na Prática O que é SWR e por...