10 pontos por GN⁺ 2025-04-23 | 3 comentários | Compartilhar no WhatsApp
  • 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 templateclone()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

 
wfedev 2025-04-24

No fim das contas, com Web Components..

 
ahwjdekf 2025-04-23

Parabéns. Mais um framework de js nasceu.

 
GN⁺ 2025-04-23
Comentários do Hacker News
  • Para muitos desenvolvedores JS isso pode ser heresia, mas acho que variáveis de state são um antipadrão

    • Ao usar Web Components, em vez de adicionar variáveis de estado para tipos de variáveis "planas", uso value/textContent/checked etc. dos elementos DOM como única fonte da verdade
    • Adiciono setters e getters quando necessário
    • Mesmo com pouco código, muita coisa acaba funcionando corretamente de forma natural
    • Usar WebComponents separa o objeto e o template HTML adjacente, criando uma granularidade tipo fusilli ou macarrão, e não código espaguete
  • A documentação diz que essa abordagem é muito fácil de manter, mas não concordo

    • O padrão de design se baseia apenas em convenções
    • Quando vários desenvolvedores trabalham ao mesmo tempo em um app complexo, é bem provável que pelo menos um deles fuja das convenções
    • Frameworks de UI baseados em classes, como o UIKit do iOS, forçam todos os desenvolvedores a usar um conjunto padrão de APIs, o que torna o código previsível e fácil de manter
  • Recentemente venho escrevendo aplicações em TypeScript puro "vanilla" com vite e questionando cada vez mais as "melhores" práticas de frontend

    • Ainda não dá para concluir sobre escalabilidade, mas há uma grande vantagem em desempenho
    • É divertido, aprendo muito, depurar é simples e a arquitetura é fácil de entender
    • O que mais sinto falta é templating
  • Essa abordagem me lembra a antiga biblioteca backbone js

    • Também existe um repositório no GitHub com exemplos do padrão MVC adaptado à plataforma web
  • Recentemente pensei em algo parecido, mas sem usar elementos de template

    • Uso funções e template literals para retornar strings, e as insiro no innerHTML de elementos existentes ou criando um novo elemento div
    • As funções ficam aninhadas e é difícil organizar isso de forma razoável
  • Esse 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

    • Sou mais forte em backend, então acho que interações relacionadas à segurança devem passar pelo servidor
    • Vejo JS como uma forma de adicionar funcionalidades no cliente sobre uma base sólida de HTML e CSS
  • Uso um helper semelhante ao React.createElement

    • Há um exemplo funcional de um dashboard de servidor simulado
  • Estou trabalhando em deja-vu.junglecoder.com como uma tentativa de construir um toolkit JS para ferramentas baseadas em HTML

    • Ainda não consegui resolver direito data binding reativo/bidirecional, mas grab/patch está ficando bem bom
    • A forma como usa templates torna muito fácil mover partes do template
  • No meu primeiro emprego formal depois de me formar na universidade, trabalhei em criar uma versão web de um software em Delphi

    • A equipe já estava reescrevendo o frontend pela terceira vez e precisava trocar de framework
    • Eu defendia que deveríamos escrever nosso próprio framework, mas a equipe não gostou da minha proposta
    • Depois saí ao receber uma proposta melhor em outra empresa
    • Mais tarde tentei outro framework chamado tiny.js e o uso em projetos pessoais até hoje