Componentes
O que são componentes?
Section titled “O que são componentes?”No Slash, componentes são funções JavaScript puras que retornam elementos DOM. Não há classes, hooks especiais ou APIs complexas - apenas funções que recebem props e retornam elementos criados com h() ou html.
import { html } from '@_bashell/slash'
// Componente simplesfunction Button({ label, onClick }) { return html` <button onClick=${onClick}> ${label} </button> `}
// Usando o componenteconst app = html` <div> <${Button} label="Clique aqui" onClick=${() => console.log('Clicado!')} /> </div>`Anatomia de um componente
Section titled “Anatomia de um componente”Um componente Slash típico segue esta estrutura:
import { html, createState } from '@_bashell/slash'import type { Component, State } from '@_bashell/slash'
type CounterProps = { count: State<number> onValueChange?: (value: number) => void}
// O state vem de fora (props ou módulo): se fosse criado aqui dentro,// seria recriado a cada re-render e perderia o valorconst Counter: Component<CounterProps> = ({ count, onValueChange }) => {
const increment = () => { const newValue = count.get() + 1 count.set(newValue) onValueChange?.(newValue) }
return html` <div class="counter"> <p>Contador: ${count.get()}</p> <button onClick=${increment}>+1</button> </div> `}Características principais
Section titled “Características principais”- Função pura: Componente é apenas uma função
- Props tipadas: Use TypeScript para definir o contrato
- Retorno: Qualquer
Childválido (Node, string, número, array, etc.) - Estado fora do componente: crie o state fora da função (módulo ou props). Um componente re-executa quando um state que ele leu muda, então um
createState()dentro dele seria recriado a cada render. Monte-o como<${Counter} />: chamado diretamente (Counter()), ele executa uma vez e não é reativo - Event handlers: Passe funções diretamente às props
Props são o mecanismo de passagem de dados para componentes. No Slash, props são simplesmente o primeiro argumento da função do componente.
Props básicas
Section titled “Props básicas”type GreetingProps = { name: string age?: number}
function Greeting({ name, age }: GreetingProps) { return html` <div> <h1>Olá, ${name}!</h1> ${age && html`<p>Você tem ${age} anos</p>`} </div> `}
// Usoconst app = html`<${Greeting} name="João" age=${25} />`Props com valores padrão
Section titled “Props com valores padrão”const isOpen = createState(true)
function Panel({ title = 'Sem título' }: { title?: string }) { return html` <div class="panel"> <h3 onClick=${() => isOpen.set(!isOpen.get())}> ${title} </h3> ${isOpen.get() && html`<div class="panel-content">Conteúdo aqui</div>`} </div> `}Props reativas
Section titled “Props reativas”Você pode passar um State como prop. Ele não é reativo por si só (um State tem get/watch, não subscribe): o componente que chama get() é quem re-renderiza quando o state muda.
import { createState, html } from '@_bashell/slash'
function Display({ count }) { // count é um State<number>: ler com get() inscreve Display no state return html`<p>Valor: ${count.get()}</p>`}
const value = createState(0)
const app = html` <div> <${Display} count=${value} /> <button onClick=${() => value.set(value.get() + 1)}>+1</button> </div>`Children
Section titled “Children”Children são elementos filhos passados para um componente. No Slash, children são apenas mais uma prop.
Recebendo children
Section titled “Recebendo children”type CardProps = { title: string children: Child // tipo do Slash}
function Card({ title, children }: CardProps) { return html` <div class="card"> <h2>${title}</h2> <div class="card-body"> ${children} </div> </div> `}
// Usoconst app = html` <${Card} title="Meu Card"> <p>Este é o conteúdo do card</p> <button>Ação</button> </${Card}>`Children múltiplos
Section titled “Children múltiplos”Children podem ser qualquer valor válido do tipo Child:
import type { Child } from '@_bashell/slash'
function Layout({ children }: { children: Child }) { return html` <div class="layout"> ${children} </div> `}
// Children como arrayconst app = html` <${Layout}> ${[ html`<header>Cabeçalho</header>`, html`<main>Conteúdo</main>`, html`<footer>Rodapé</footer>`, ]} </${Layout}>`Children condicionais
Section titled “Children condicionais”function Alert({ type, message, showIcon = true }: { type: 'info' | 'error' | 'success' message: string showIcon?: boolean}) { const icons = { info: 'ℹ️', error: '❌', success: '✅' }
return html` <div class="alert alert-${type}"> ${showIcon && html`<span class="icon">${icons[type]}</span>`} <span class="message">${message}</span> </div> `}Composição de componentes
Section titled “Composição de componentes”A composição é o padrão principal para reutilizar lógica no Slash.
Exemplo: Lista de tarefas
Section titled “Exemplo: Lista de tarefas”import { html, createState } from '@_bashell/slash'import type { Component } from '@_bashell/slash'
type Task = { id: number title: string completed: boolean}
// Componente de item individualconst TaskItem: Component<{ task: Task; onToggle: (id: number) => void }> = ({ task, onToggle}) => { return html` <li class=${task.completed ? 'completed' : ''}> <input type="checkbox" checked=${task.completed} onChange=${() => onToggle(task.id)} /> <span>${task.title}</span> </li> `}
// Componente de lista// O state fica fora do componente: se fosse criado dentro, seria recriado a cada renderconst tasks = createState<Task[]>([ { id: 1, title: 'Aprender Slash', completed: false }, { id: 2, title: 'Criar app', completed: false },])
const TaskList: Component = () => { const toggleTask = (id: number) => { const current = tasks.get() tasks.set( current.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)) ) }
return html` <div class="task-list"> <h2>Minhas Tarefas</h2> <ul> ${tasks.get().map((task) => html`<${TaskItem} task=${task} onToggle=${toggleTask} />` )} </ul> </div> `}Componentes com estado complexo
Section titled “Componentes com estado complexo”Para componentes com múltiplos estados relacionados, agrupe-os em um único objeto:
import { html, createState, batch } from '@_bashell/slash'
// O state fica fora do componente: se fosse criado dentro, seria recriado a cada renderconst formState = createState({ name: '', email: '', errors: { name: '', email: '' }})
// Mensagens de erro: único componente que lê `errors`const FieldError = ({ field }: { field: 'name' | 'email' }) => { const message = formState.get().errors[field] return html`${message && html`<span class="error">${message}</span>`}`}
function UserForm() { const validate = () => { const state = formState.get() const errors = { name: '', email: '' }
if (!state.name.trim()) { errors.name = 'Nome é obrigatório' }
if (!state.email.includes('@')) { errors.email = 'Email inválido' }
// Usa batch para agrupar múltiplas atualizações batch(() => { formState.set({ ...state, errors }) })
return !errors.name && !errors.email }
const handleSubmit = (e: Event) => { e.preventDefault() if (validate()) { console.log('Formulário válido!', formState.get()) } }
// O form não lê states no render: os <input> não são recriados ao digitar return html` <form onSubmit=${handleSubmit}> <div> <input type="text" placeholder="Nome" onInput=${(e: Event) => { const state = formState.get() formState.set({ ...state, name: (e.target as HTMLInputElement).value }) }} /> <${FieldError} field="name" /> </div>
<div> <input type="email" placeholder="Email" onInput=${(e: Event) => { const state = formState.get() formState.set({ ...state, email: (e.target as HTMLInputElement).value }) }} /> <${FieldError} field="email" /> </div>
<button type="submit">Enviar</button> </form> `}Próximos passos
Section titled “Próximos passos”- Lifecycle e Cleanup - Gerenciando recursos e limpeza
- Roteamento - Criando SPAs com router
- Formulários - Trabalhando com forms e validação