Componentes e JSX
Um componente é uma função que roda uma vez e devolve nós do DOM. As partes que mudam são as funções que você coloca no JSX.
O JSX cria nós de verdade
const button = <button class="btn">Salvar</button>;
button instanceof HTMLButtonElement; // trueNão há DOM virtual nem comparação de árvores. Quando o render() roda seu componente, os elementos são criados uma vez; depois, os signals atualizam os atributos e nós de texto específicos que os leem.
As três regras
1. Leia signals dentro de funções
function Greeting() {
const [name, setName] = createSignal('Ada');
return (
<>
<p>Olá, {name}</p> {/* atualiza */}
<p>Olá, {() => name().toUpperCase()}</p> {/* atualiza */}
<p>Olá, {name()}</p> {/* lido uma vez: nunca atualiza */}
<input value={name()} onInput={(e) => setName(e.currentTarget.value)} />
</>
);
}Atributos funcionam do mesmo jeito: class={() => (active() ? 'on' : '')} é reativo, class={active() ? 'on' : ''} é calculado uma vez.
2. As props chegam como foram passadas
As props passam sem alteração. Uma função continua função, então callbacks e render props funcionam:
function Price(props: { amount: number; format: (n: number) => string }) {
return <span>{props.format(props.amount)}</span>;
}
<Price amount={42} format={(n) => `R$ ${n.toFixed(2)}`} />;Para aceitar um valor ou um signal, tipe a prop como MaybeAccessor<T> e leia com access():
import { access, type MaybeAccessor } from '@velozjs/dom';
function Badge(props: { count: MaybeAccessor<number> }) {
return <span class="badge">{() => access(props.count)}</span>;
}
<Badge count={3} />
<Badge count={unread} /> {/* unread é o getter de um signal */}3. Passe uma função para renderizar sob demanda
O JSX avalia os filhos antes de o componente pai rodar. Na maior parte do tempo tudo bem, mas quando o conteúdo deve ser criado depois ou dentro do escopo de um componente, passe uma função:
<Show when={open}>{() => <PainelPesado />}</Show> {/* criado só quando aparece */}
<ErrorBoundary fallback={<p>Algo quebrou</p>}>
{() => <Widget />} {/* erros são capturados */}
</ErrorBoundary>
<Theme.Provider value="dark">{() => <App />}</Theme.Provider> {/* enxerga o contexto */}Eventos
Handlers são funções em props on*. Eventos que borbulham (click, input, keydown...) são delegados; os outros (focus, blur, mouseenter, scroll...) ganham um listener no próprio elemento. Nos dois casos, event.currentTarget é o elemento com o handler.
<button onClick={(e) => console.log(e.currentTarget)}>Clique</button>
<input onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} />Refs
let input!: HTMLInputElement;
<input ref={(el) => (input = el)} />;
const box = { current: null as HTMLDivElement | null };
<div ref={box} />;Estilos e classes
<div
class={() => ['card', selected() && 'card-selected']}
classList={() => ({ disabled: locked() })}
style={() => ({ opacity: visible() ? 1 : 0.4, '--progress': `${pct()}%` })}
/>Objetos de estilo aceitam camelCase e propriedades customizadas, números ganham px (menos propriedades sem unidade, como opacity), e chaves que somem na atualização seguinte são removidas.
Formulários
value, checked e selected são aplicados depois dos outros atributos e dos filhos, então <select value="b"> seleciona uma opção renderizada dentro dele, e o valor de um <input type="range"> não é cortado pela ordem em que você escreve min/max.
<select value={() => size()} onChange={(e) => setSize(e.currentTarget.value)}>
<For each={sizes}>{(s) => <option value={s}>{s}</option>}</For>
</select>Tipos
Cada elemento conhece seu tipo no DOM: o ref de um <dialog> recebe um HTMLDialogElement, e os handlers recebem um currentTarget tipado, então e.currentTarget.value dispensa conversão. Filhos que são funções de renderização (<Show>{(user) => …}</Show>) também são tipados. Exige TypeScript 5.1+.
SVG
Tags SVG (svg, path, circle, g, use...) são criadas no namespace SVG automaticamente, então ícones inline funcionam.
<svg viewBox="0 0 24 24" class="icon"><path d="M5 12h14" /></svg>Renderizando
import { render } from '@velozjs/dom';
const dispose = render(() => <App />, document.getElementById('app')!);
dispose(); // remove o conteúdo e para todos os effectsSem JSX
import { createElement as h, render } from '@velozjs/dom';
render(() => h('button', { onClick: () => alert('oi') }, 'Olá'), document.body);