- Writing JavaScript Views the Hard Way: um texto que explica como construir views com JavaScript puro, sem frameworks
- Por meio de uma abordagem imperativa direta, garante desempenho, manutenibilidade e portabilidade
- Separa com clareza as atualizações de estado das atualizações de DOM e segue regras rígidas de nomenclatura e padrões estruturais para cada função
- Essa abordagem é fácil de depurar, garante compatibilidade com todos os navegadores e tem a grande vantagem de 0 dependencies
- Pode ser difícil para iniciantes, mas ao aprender oferece uma compreensão profunda de como os sistemas realmente funcionam
Escrevendo views em JavaScript do "Hard Way"
O que é isso?
- Essa abordagem é um padrão para compor views apenas com JavaScript, sem frameworks como React, Vue, lit-html
- Não é uma biblioteca nem uma ferramenta específica, mas sim o próprio padrão de escrita do código, evitando o problema do código espaguete
- Ao usar uma abordagem imperativa direta, reduz abstrações e aumenta a intuitividade
Vantagens em relação aos frameworks
- Desempenho: como o código é imperativo, funciona sem cálculos desnecessários e é adequado tanto para hot paths quanto para cold paths
- 0 dependencies: livre de upgrades de bibliotecas e problemas de compatibilidade
- Portabilidade: o código escrito pode ser portado para qualquer framework
- Manutenibilidade: a estrutura clara de seções e as regras de nomenclatura facilitam localizar o código
- Suporte a navegadores: compatível com a maioria dos navegadores a partir do IE9, e com alguns ajustes pode até suportar IE6
- Facilidade de depuração: fornece stack traces rasos, sem camadas intermediárias
- Estrutura funcional: embora não seja imutável, todos os componentes são estruturados com base em funções
Explicação da estrutura
Estrutura geral
- É composta por
template → clone() → init()
- A função
init() cria uma instância de view que inclui variáveis de estado, referências ao DOM, funções de atualização, event listeners etc.
Exemplo de estrutura de código (Hello World)
const template = document.createElement('template');
template.innerHTML = `<div>Hello <span id="name">world</span>!</div>`;
function clone() {
return document.importNode(template.content, true);
}
function init() {
let frag = clone();
let nameNode = frag.querySelector('#name');
let name;
function setNameNode(value) {
nameNode.textContent = value;
}
function setName(value) {
if(name !== value) {
name = value;
setNameNode(value);
}
}
function update(data = {}) {
if(data.name) setName(data.name);
return frag;
}
return update;
}
Componentes internos da função init()
1. Variáveis de DOM
frag é o fragmento de template gerado a partir de clone()
- Elementos internos são referenciados com
querySelector(), e os nomes das variáveis seguem o formato fooNode
2. Views de DOM
- Partes que incluem outras views (subviews reutilizáveis)
- Exemplo:
let updateChildView = childView();
- As funções de atualização de view são nomeadas no formato
updateFoo
3. Variáveis de estado
- Valores de dados que podem mudar dentro da view
- Para tornar as atualizações de DOM eficientes, compara o valor atual e só altera o DOM quando necessário
4. Funções de atualização do DOM
- Usadas ao mudar o estado de elementos do DOM
- Exemplo:
function setNameNode(value) {
nameNode.textContent = value;
}
- A manipulação do DOM deve acontecer obrigatoriamente apenas dentro dessas funções
5. Funções de atualização de estado
- Incluem a lógica de mudança de estado e o reflexo disso no DOM
- Valores que não mudaram são ignorados para evitar alterações desnecessárias no DOM
- Exemplo:
function setName(value) {
if(name !== value) {
name = value;
setNameNode(value);
}
}
Funções template e clone()
template
- Cria a estrutura HTML estática com o elemento
<template>
- Não é inserido diretamente no DOM; uma cópia é criada via clone
clone()
- Faz a clonagem com
document.importNode(template.content, true)
- Se necessário, pode retornar o elemento raiz usando
.firstElementChild
Forma de interação
Fluxo de dados pai → filho
- O pai chama o
init() do filho para obter a função de atualização e a invoca no formato update({ name: 'foo' })
Propagação de dados baseada em eventos
- Segue basicamente o modelo props down, events up
- Views filhas se comunicam despachando eventos para cima
Comparação com React
constructor() (React) → init() (Hard Way)
- Responsável pela configuração inicial do componente
render() (React) → update(data) (Hard Way)
- Responsável por atualizar a tela e a UI
this.setState() (React) → setX(value) (Hard Way)
- Substituído por uma forma de definir diretamente o valor do estado
props (React) → valores passados por update(data) (Hard Way)
- Forma de tratar os dados recebidos do componente pai
- JSX / Virtual DOM (React) → template HTML + DOM API (Hard Way)
- Em vez de uma UI declarativa, usa manipulação manual do DOM e templates
Conclusão
- Em comparação com frameworks conhecidos, essa abordagem tem uma barreira de entrada inicial mais alta, mas oferece as seguintes vantagens:
- Otimização de desempenho
- Controle total
- Compreensão profunda por meio do aprendizado
- Com a separação de funções por papel e regras de nomenclatura, é possível construir uma UI manutenível mesmo sem frameworks
Compatibilidade
- Os exemplos mais recentes usam APIs voltadas a navegadores modernos, mas com substituições baseadas em funções ainda é possível dar suporte até ao IE9 ou inferior
- Também pode ser expandido até o IE6 usando uma abordagem de passar funções por props em vez de eventos
3 comentários
No fim das contas, com Web Components..
Parabéns. Mais um framework de js nasceu.
Comentários do Hacker News
Para muitos desenvolvedores JS isso pode ser heresia, mas acho que variáveis de
statesão um antipadrãovalue/textContent/checkedetc. dos elementos DOM como única fonte da verdadeA documentação diz que essa abordagem é muito fácil de manter, mas não concordo
Recentemente venho escrevendo aplicações em TypeScript puro "vanilla" com vite e questionando cada vez mais as "melhores" práticas de frontend
Essa abordagem me lembra a antiga biblioteca backbone js
Recentemente pensei em algo parecido, mas sem usar elementos de template
innerHTMLde elementos existentes ou criando um novo elementodivEsse código parece exatamente o mesmo código de atualização manual que bibliotecas de views reativas tentam substituir
Programo há cerca de 20 anos, mas nunca me acostumei com frameworks de frontend
Uso um helper semelhante ao
React.createElementEstou trabalhando em deja-vu.junglecoder.com como uma tentativa de construir um toolkit JS para ferramentas baseadas em HTML
grab/patchestá ficando bem bomNo meu primeiro emprego formal depois de me formar na universidade, trabalhei em criar uma versão web de um software em Delphi