UNPKG

woby

Version:

A high-performance framework with fine-grained observable/signal-based reactivity for building rich applications.

513 lines (402 loc) 11.5 kB
# Quick Start Get up and running with Woby in just a few minutes! This guide will walk you through creating your first Woby application. ## What You'll Build We'll create a simple todo app that demonstrates Woby's core features: - Reactive state with observables - Component composition - Event handling - Dynamic lists - Advanced class management ## Step 1: Create Your App First, ensure you have [Woby installed](./Installation.md), then create your main application file: ```typescript // src/App.tsx import { $, For, render, useMemo } from 'woby' interface Todo { id: number text: string completed: boolean } const TodoApp = () => { // Observable state const todos = $<Todo[]>([]) const input = $('') const filter = $('all') // all, active, completed // Actions const addTodo = () => { const text = $$(input).trim() if (text) { todos(prev => [...prev, { id: Date.now(), text, completed: false }]) input('') } } const toggleTodo = (id: number) => { todos(prev => prev.map(todo => todo.id === id ? { ...todo, completed: !$$(todo.completed) } : todo )) } const removeTodo = (id: number) => { todos(prev => $$(prev).filter(todo => todo.id !== id)) } const setFilter = (newFilter: string) => { filter(newFilter) } // Computed values const filteredTodos = useMemo(() => { const currentFilter = $$(filter) const currentTodos = $$(todos) if (currentFilter === 'active') { return currentTodos.filter(todo => !todo.completed) } else if (currentFilter === 'completed') { return currentTodos.filter(todo => todo.completed) } return currentTodos }) const activeCount = useMemo(() => { return $$(todos).filter(todo => !todo.completed).length }) return ( <div class="todo-app max-w-md mx-auto p-4"> <h1 class="text-2xl font-bold mb-4 text-center">My Todo App</h1> {/* Add new todo */} <div class="flex gap-2 mb-4"> <input type="text" value={input} onInput={(e) => input(e.target.value)} placeholder="Add a new todo..." onKeyPress={(e) => e.key === 'Enter' && addTodo()} class="flex-1 p-2 border border-gray-300 rounded" /> <button onClick={addTodo} class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" > Add </button> </div> {/* Filter buttons */} <div class="flex justify-center gap-2 mb-4"> {['all', 'active', 'completed'].map((filterName) => ( <button key={filterName} onClick={() => setFilter(filterName)} class={[ 'px-3 py-1 rounded', () => $$(filter) === filterName ? 'bg-blue-500 text-white' : 'bg-gray-200 hover:bg-gray-300' ]} > {filterName.charAt(0).toUpperCase() + filterName.slice(1)} </button> ))} </div> {/* Todo list */} <ul class="list-none p-0"> <For values={filteredTodos}> {(todo) => ( <li class={[ 'flex items-center p-2 border-b border-gray-200 gap-2', { 'line-through text-gray-500': todo.completed, 'bg-yellow-50': () => todo.id % 2 === 0 // Alternate row styling } ]}> <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} class="w-4 h-4" /> <span class="flex-1">{todo.text}</span> <button class="w-6 h-6 flex items-center justify-center bg-red-500 text-white rounded-full hover:bg-red-600 text-xs" onClick={() => removeTodo(todo.id)} > ✕ </button> </li> )} </For> </ul> {/* Stats */} <div class="mt-4 p-2 bg-gray-100 rounded text-sm"> Total: {() => $$(todos).length} | Active: {activeCount} | Completed: {() => $$(todos).filter(t => t.completed).length} </div> </div> ) } // Render the application render(<TodoApp />, document.getElementById('app')!) ``` ## Step 2: Add Some Styling Create a CSS file to enhance the visual presentation: ``` /* src/styles.css */ .todo-app { max-width: 500px; margin: 2rem auto; padding: 1rem; font-family: system-ui, sans-serif; } .flex { display: flex; } .flex-1 { flex: 1; } .gap-2 { gap: 0.5rem; } .items-center { align-items: center; } .p-2 { padding: 0.5rem; } .p-4 { padding: 1rem; } .m-4 { margin: 1rem; } .mb-4 { margin-bottom: 1rem; } .border { border: 1px solid #ddd; } .border-b { border-bottom: 1px solid #eee; } .rounded { border-radius: 4px; } .rounded-full { border-radius: 50%; } .bg-blue-500 { background: #007bff; } .bg-gray-100 { background: #f8f9fa; } .bg-gray-200 { background: #e9ecef; } .bg-red-500 { background: #dc3545; } .text-white { color: white; } .text-gray-500 { color: #666; } .text-sm { font-size: 0.9rem; } .text-xs { font-size: 0.75rem; } .text-center { text-align: center; } .text-2xl { font-size: 1.5rem; } .font-bold { font-weight: bold; } .cursor-pointer { cursor: pointer; } .hover\:bg-blue-600:hover { background: #0069d9; } .hover\:bg-gray-300:hover { background: #dee2e6; } .hover\:bg-red-600:hover { background: #c82333; } .max-w-md { max-width: 500px; } .mx-auto { margin-left: auto; margin-right: auto; } .list-none { list-style: none; } .w-4 { width: 1rem; } .h-4 { height: 1rem; } .w-6 { width: 1.5rem; } .h-6 { height: 1.5rem; } .justify-center { justify-content: center; } .line-through { text-decoration: line-through; } ``` ## Step 3: Update Your HTML ``` <!DOCTYPE html> <html> <head> <title>Todo App - Woby</title> <link rel="stylesheet" href="./src/styles.css"> </head> <body> <div id="app"></div> <script type="module" src="./src/App.tsx"></script> </body> </html> ``` ## Advanced Class Management Woby provides powerful built-in class management that supports complex class expressions with full reactive observable support. ### Class Array Support Woby supports complex class expressions including arrays, objects, and functions: ``` // Array of classes <div class={['red', 'bold']}>Text</div> // Nested arrays <div class={['red', ['bold', ['italic']]]}>Text</div> // Mixed types with reactive expressions <div class={[ "base-class", () => $$(value) % 2 === 0 ? "even" : "odd", { hidden: () => $$(isVisible) === false, italic: false }, ['additional', ['nested', 'classes']] ]}>Complex classes</div> ``` ### Reactive Classes All class expressions support reactive observables that automatically update when values change: ``` const isActive = $(false) const theme = $('dark') const error = $(false) // Reactive boolean <div class={{ active: isActive, error: error }}>Toggle me</div> // Reactive string <div class={() => `btn btn-${theme()}`}>Themed button</div> // Complex reactive expression <div class={[ 'base-class', () => isActive() ? 'active' : 'inactive', { 'loading': () => $$(loadingState) } ]}>Dynamic element</div> ``` ### Class Object Syntax Woby supports object syntax for conditional classes where keys are class names and values are boolean conditions: ``` const error = $(false) const warning = $(false) <div class={{ 'base': true, // Always applied 'error': error, // Applied when error is truthy 'warning': warning, // Applied when warning is truthy 'success': () => !$$(error) && !$$(warning) // Applied when neither error nor warning }}>Status element</div> ``` ### Function-based Classes Classes can be computed using functions that return class strings or other class expressions: ``` const count = $(0) <div class={() => count() > 5 ? 'high-count' : 'low-count'}> Count: {count} </div> // Function returning complex expression <div class={() => [ 'base', count() > 10 ? 'large' : 'small', { 'even': () => count() % 2 === 0 } ]}> Dynamic element </div> ``` ### Important: Reactive Elements Must Be Wrapped All reactive elements in class expressions should be wrapped in `useMemo` or arrow functions `() =>` to ensure proper reactivity: ``` // Correct - wrapped in arrow function <div class={() => isActive() ? 'active' : 'inactive'}>Content</div> // Correct - observables automatically handled in objects <div class={{ 'active': isActive }}>Content</div> // Correct - complex expression with useMemo const dynamicClass = useMemo(() => ({ 'active': $$(isActive), 'disabled': $$(isDisabled), 'loading': $$(isLoading) })) <div class={dynamicClass}>Content</div> ``` ## Key Concepts Demonstrated ### 1. **Observables ($)** ```typescript const todos = $<Todo[]>([]) // Creates reactive state const input = $('') // String observable ``` ### 2. **Reactive Updates** ```typescript todos(prev => [...prev, newTodo]) // State updates trigger re-renders ``` ### 3. **Dynamic Rendering** ```typescript <For values={todos}> {(todo) => <li>...</li>} </For> ``` ### 4. **Event Handling** ```typescript <button onClick={addTodo}>Add</button> <input onInput={(e) => input(e.target.value)} /> ``` ### 5. **Advanced Class Management** ```typescript <div class={[ "base-class", () => $$(isActive) ? "active" : "inactive", { "error": () => $$(hasError) } ]}>Content</div> ``` ## What's Happening? 1. **Observables**: $() creates reactive state that automatically updates the UI when changed 2. **No Virtual DOM**: Woby directly updates DOM nodes for maximum performance 3. **Fine-grained Updates**: Only the specific parts that changed get re-rendered 4. **Automatic Dependency Tracking**: Woby knows what depends on what without manual declarations 5. **Built-in Class Management**: Complex class expressions with full reactive support ## Try It Out! 1. Add some todos 2. Mark them as completed 3. Delete todos 4. Filter todos by status 5. Watch the stats update automatically 6. Notice how class expressions update based on state changes Notice how everything stays in sync without any manual work - that's Woby's reactive system in action! ## Next Steps Now that you've built your first Woby application, explore these topics: - [Reactivity System](./Reactivity-System.md) - Deep dive into observables - [Components](./Components.md) - Learn about component patterns - [Hooks](./Hooks.md) - Discover built-in utilities - [Examples](./Examples.md) - See more complex applications ## Complete Example You can find this complete todo application and many others in our [examples gallery](./Examples.md) or check out the [live demos](https://github.com/wobyjs/demo).