🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Exemplos de Qwik
Exemplos do framework Qwik incluindo componentes, sinais, gerenciamento de estado e padrões zero-hydration
💻 Olá Mundo com Qwik typescript
🟢 simple
Exemplos básicos de componentes Qwik e aplicativos Olá Mundo com JSX
// Qwik Hello World Examples
import { component$, useSignal, useStore, $, useTask$ } from '@builder.io/qwik';
// 1. Basic functional component
export const HelloWorld = component$(() => {
return <h1>Hello, World!</h1>;
});
// 2. Component with props
export interface GreetingProps {
name: string;
}
export const Greeting = component$<GreetingProps>((props) => {
return <h1>Hello, {props.name}!</h1>;
});
// 3. Component with signal (reactive state)
export const Counter = component$(() => {
const count = useSignal(0);
const increment = $(() => {
count.value++;
});
const decrement = $(() => {
count.value--;
});
return (
<div>
<h1>Count: {count.value}</h1>
<button onClick$={increment}>Increment</button>
<button onClick$={decrement}>Decrement</button>
</div>
);
});
// 4. Component with store (for complex objects)
export const UserProfile = component$(() => {
const user = useStore({
name: 'Guest',
age: 25,
isLoggedIn: false
});
const login = $(() => {
user.name = 'John Doe';
user.age = 30;
user.isLoggedIn = true;
});
const logout = $(() => {
user.name = 'Guest';
user.age = 25;
user.isLoggedIn = false;
});
const updateName = $((event: Event) => {
const target = event.target as HTMLInputElement;
user.name = target.value;
});
const updateAge = $((event: Event) => {
const target = event.target as HTMLInputElement;
user.age = parseInt(target.value);
});
return (
<div>
<h2>User Profile</h2>
{user.isLoggedIn ? (
<div>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<input
type="text"
value={user.name}
onInput$={updateName}
placeholder="Enter name"
/>
<input
type="number"
value={user.age}
onInput$={updateAge}
placeholder="Enter age"
/>
<button onClick$={logout}>Logout</button>
</div>
) : (
<button onClick$={login}>Login</button>
)}
</div>
);
});
// 5. Component with computed values
export const ShoppingCart = component$(() => {
const items = useStore([
{ name: 'Apple', price: 1.5, quantity: 2 },
{ name: 'Banana', price: 0.8, quantity: 5 }
]);
const total = useSignal(0);
const itemCount = useSignal(0);
// UseTask$ for reactive computations
useTask$(({ track }) => {
track(() => items);
total.value = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
itemCount.value = items.reduce((sum, item) => sum + item.quantity, 0);
});
const removeItem = $((index: number) => {
items.splice(index, 1);
});
return (
<div>
<h2>Shopping Cart</h2>
<p>Items: {itemCount.value}</p>
<p>Total: ${total.value.toFixed(2)}</p>
<ul>
{items.map((item, index) => (
<li key={index}>
{item.name} - ${item.price} x {item.quantity}
<button onClick$={() => removeItem(index)}>Remove</button>
</li>
))}
</ul>
</div>
);
});
// 6. Async component with resource
import { Resource, useResource$ } from '@builder.io/qwik';
export interface Product {
id: number;
name: string;
price: number;
}
export const ProductList = component$(() => {
const productsResource = useResource$<Product[]>(async ({ track, cleanup }) => {
// Track any reactive dependencies
track(() => {/* track signals if needed */});
// Cleanup function
cleanup(() => {
console.log('Cleaning up product fetch');
});
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
return [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Phone', price: 699 },
{ id: 3, name: 'Tablet', price: 399 }
];
});
return (
<div>
<h2>Products</h2>
<Resource
value={productsResource}
onPending={() => <div>Loading products...</div>}
onRejected={(error) => <div>Error: {error.message}</div>}
onResolved={(products) => (
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
)}
/>
</div>
);
});
// 7. Parent component demonstrating usage
export const App = component$(() => {
return (
<div>
<HelloWorld />
<Greeting name="Qwik" />
<Counter />
<UserProfile />
<ShoppingCart />
<ProductList />
</div>
);
});
// 8. Component with lifecycle hooks
import { useClientEffect$, useVisibleTask$ } from '@builder.io/qwik';
export const LifecycleDemo = component$(() => {
const message = useSignal('Component mounted');
// Runs on the client only (equivalent to componentDidMount)
useClientEffect$(() => {
console.log('Component mounted on client');
message.value = 'Client effect executed';
// Cleanup function
return () => {
console.log('Component will unmount');
};
});
// Runs when component becomes visible
useVisibleTask$(({ cleanup }) => {
console.log('Component is visible');
message.value = 'Visible task executed';
cleanup(() => {
console.log('Component hidden/cleaned up');
});
});
return (
<div>
<h3>Lifecycle Demo</h3>
<p>{message.value}</p>
</div>
);
});
// 9. Form handling
export const ContactForm = component$(() => {
const form = useStore({
name: '',
email: '',
message: '',
submitted: false
});
const handleSubmit = $(async (event: Event) => {
event.preventDefault();
console.log('Form submitted:', form);
// Simulate form submission
await new Promise(resolve => setTimeout(resolve, 1000));
form.submitted = true;
// Reset form after 2 seconds
setTimeout(() => {
form.name = '';
form.email = '';
form.message = '';
form.submitted = false;
}, 2000);
});
return (
<div>
<h3>Contact Form</h3>
{form.submitted ? (
<p>Thank you for your submission!</p>
) : (
<form onSubmit$={handleSubmit}>
<div>
<label>
Name:
<input
type="text"
value={form.name}
onInput$={(e) => form.name = (e.target as HTMLInputElement).value}
required
/>
</label>
</div>
<div>
<label>
Email:
<input
type="email"
value={form.email}
onInput$={(e) => form.email = (e.target as HTMLInputElement).value}
required
/>
</label>
</div>
<div>
<label>
Message:
<textarea
value={form.message}
onInput$={(e) => form.message = (e.target as HTMLTextAreaElement).value}
required
/>
</label>
</div>
<button type="submit">Submit</button>
</form>
)}
</div>
);
});
export { App as HelloWorldExample };
💻 Sinais e Stores do Qwik typescript
🟡 intermediate
Trabalhando com sinais reativos, stores e gerenciamento de estado no Qwik
// Qwik Signals and Stores
import {
component$,
useSignal,
useStore,
useTask$,
$,
useResource$,
Resource
} from '@builder.io/qwik';
// 1. Basic signals
export const SignalDemo = component$(() => {
const count = useSignal(0);
const text = useSignal('Hello');
const isVisible = useSignal(true);
const increment = $(() => {
count.value++;
});
const decrement = $(() => {
count.value--;
});
const reset = $(() => {
count.value = 0;
text.value = 'Hello';
});
const toggle = $(() => {
isVisible.value = !isVisible.value;
});
// Reactive computation with useTask$
const doubled = useSignal(0);
useTask$(({ track }) => {
track(() => count.value);
doubled.value = count.value * 2;
});
return (
<div>
<h3>Signal Demo</h3>
<p>Count: {count.value}</p>
<p>Doubled: {doubled.value}</p>
<p>Text: {text.value}</p>
<div>
<button onClick$={increment}>+</button>
<button onClick$={decrement}>-</button>
<button onClick$={reset}>Reset</button>
</div>
<input
type="text"
value={text.value}
onInput$={(e) => text.value = (e.target as HTMLInputElement).value}
/>
<button onClick$={toggle}>
{isVisible.value ? 'Hide' : 'Show'} Content
</button>
{isVisible.value && (
<div>Toggle content is visible!</div>
)}
</div>
);
});
// 2. Complex store
interface Todo {
id: number;
text: string;
completed: boolean;
createdAt: Date;
}
interface TodoStore {
todos: Todo[];
filter: 'all' | 'active' | 'completed';
newTodoText: string;
}
export const TodoApp = component$(() => {
const store = useStore<TodoStore>({
todos: [],
filter: 'all',
newTodoText: ''
});
const filteredTodos = useSignal<Todo[]>([]);
const stats = useSignal({ total: 0, completed: 0, active: 0 });
// Reactive computations
useTask$(({ track }) => {
track(() => store.todos);
track(() => store.filter);
// Filter todos
filteredTodos.value = store.todos.filter(todo => {
switch (store.filter) {
case 'active':
return !todo.completed;
case 'completed':
return todo.completed;
default:
return true;
}
});
// Update stats
stats.value = {
total: store.todos.length,
completed: store.todos.filter(todo => todo.completed).length,
active: store.todos.filter(todo => !todo.completed).length
};
});
const addTodo = $((event: Event) => {
event.preventDefault();
if (store.newTodoText.trim()) {
store.todos.push({
id: Date.now(),
text: store.newTodoText.trim(),
completed: false,
createdAt: new Date()
});
store.newTodoText = '';
}
});
const toggleTodo = $((id: number) => {
const todo = store.todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
}
});
const deleteTodo = $((id: number) => {
const index = store.todos.findIndex(t => t.id === id);
if (index !== -1) {
store.todos.splice(index, 1);
}
});
const clearCompleted = $(() => {
store.todos = store.todos.filter(todo => !todo.completed);
});
const setFilter = $((filter: 'all' | 'active' | 'completed') => {
store.filter = filter;
});
return (
<div>
<h3>Todo App</h3>
<form onSubmit$={addTodo}>
<input
type="text"
value={store.newTodoText}
onInput$={(e) => store.newTodoText = (e.target as HTMLInputElement).value}
placeholder="What needs to be done?"
/>
<button type="submit">Add</button>
</form>
<div class="filters">
<button
class={store.filter === 'all' ? 'active' : ''}
onClick$={() => setFilter('all')}
>
All ({stats.value.total})
</button>
<button
class={store.filter === 'active' ? 'active' : ''}
onClick$={() => setFilter('active')}
>
Active ({stats.value.active})
</button>
<button
class={store.filter === 'completed' ? 'active' : ''}
onClick$={() => setFilter('completed')}
>
Completed ({stats.value.completed})
</button>
</div>
<ul>
{filteredTodos.value.map((todo) => (
<li key={todo.id} class={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onClick$={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick$={() => deleteTodo(todo.id)}>Delete</button>
</li>
))}
</ul>
{stats.value.completed > 0 && (
<button onClick$={clearCompleted}>
Clear Completed ({stats.value.completed})
</button>
)}
</div>
);
});
// 3. Shopping cart with nested stores
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
isOpen: boolean;
couponCode: string;
discount: number;
}
export const ShoppingCart = component$(() => {
const cart = useStore<CartStore>({
items: [],
isOpen: false,
couponCode: '',
discount: 0
});
const total = useSignal(0);
const subtotal = useSignal(0);
const itemCount = useSignal(0);
// Reactive calculations
useTask$(({ track }) => {
track(() => cart.items);
track(() => cart.discount);
subtotal.value = cart.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
total.value = subtotal.value - (subtotal.value * cart.discount);
itemCount.value = cart.items.reduce((sum, item) => sum + item.quantity, 0);
});
const addItem = $((item: Omit<CartItem, 'quantity'>) => {
const existingItem = cart.items.find(i => i.id === item.id);
if (existingItem) {
existingItem.quantity++;
} else {
cart.items.push({ ...item, quantity: 1 });
}
});
const removeItem = $((id: number) => {
const index = cart.items.findIndex(item => item.id === id);
if (index !== -1) {
cart.items.splice(index, 1);
}
});
const updateQuantity = $((id: number, quantity: number) => {
const item = cart.items.find(item => item.id === id);
if (item) {
if (quantity <= 0) {
removeItem(id);
} else {
item.quantity = quantity;
}
}
});
const toggleCart = $(() => {
cart.isOpen = !cart.isOpen;
});
const applyCoupon = $(() => {
// Simple coupon logic
if (cart.couponCode === 'SAVE10') {
cart.discount = 0.1;
} else if (cart.couponCode === 'SAVE20') {
cart.discount = 0.2;
}
});
return (
<div>
<button onClick$={toggleCart}>
Cart ({itemCount.value}) - ${total.value.toFixed(2)}
</button>
{cart.isOpen && (
<div class="cart-modal">
<h3>Shopping Cart</h3>
{cart.items.length === 0 ? (
<p>Your cart is empty</p>
) : (
<>
<ul>
{cart.items.map((item) => (
<li key={item.id}>
<div>
<h4>{item.name}</h4>
<p>${item.price} x {item.quantity} = ${(item.price * item.quantity).toFixed(2)}</p>
</div>
<div>
<input
type="number"
value={item.quantity}
min="1"
onInput$={(e) => updateQuantity(item.id, parseInt((e.target as HTMLInputElement).value))}
/>
<button onClick$={() => removeItem(item.id)}>Remove</button>
</div>
</li>
))}
</ul>
<div class="cart-summary">
<p>Subtotal: ${subtotal.value.toFixed(2)}</p>
<p>Discount: {(cart.discount * 100).toFixed(0)}%</p>
<p><strong>Total: ${total.value.toFixed(2)}</strong></p>
</div>
<div class="coupon">
<input
type="text"
value={cart.couponCode}
onInput$={(e) => cart.couponCode = (e.target as HTMLInputElement).value}
placeholder="Coupon code"
/>
<button onClick$={applyCoupon}>Apply</button>
</div>
<button onClick$={() => cart.items = []}>Clear Cart</button>
</>
)}
<button onClick$={toggleCart}>Close</button>
</div>
)}
</div>
);
});
// 4. Data fetching with stores
interface User {
id: number;
name: string;
email: string;
posts: Post[];
}
interface Post {
id: number;
title: string;
body: string;
}
export const UserDashboard = component$(() => {
const user = useStore<User | null>(null);
const loading = useSignal(true);
const error = useSignal<string | null>(null);
// Resource for fetching user data
const userResource = useResource$<User>(async ({ track }) => {
track(() => {/* Track any signals if needed */});
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
return {
id: 1,
name: 'John Doe',
email: '[email protected]',
posts: [
{ id: 1, title: 'First Post', body: 'This is my first post' },
{ id: 2, title: 'Second Post', body: 'This is my second post' }
]
};
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
throw err;
}
});
const refreshData = $(async () => {
loading.value = true;
error.value = null;
// Resource will automatically refetch
});
return (
<div>
<h2>User Dashboard</h2>
<button onClick$={refreshData}>Refresh Data</button>
<Resource
value={userResource}
onPending={() => <div>Loading user data...</div>}
onRejected={(error) => <div>Error: {error.message}</div>}
onResolved={(userData) => {
// Store the resolved data
user.value = userData;
loading.value = false;
return null; // We'll render from the store
}}
/>
{user.value && !loading.value && (
<div>
<div class="user-info">
<h3>{user.value.name}</h3>
<p>{user.value.email}</p>
</div>
<div class="user-posts">
<h4>Posts ({user.value.posts.length})</h4>
<ul>
{user.value.posts.map((post) => (
<li key={post.id}>
<h5>{post.title}</h5>
<p>{post.body}</p>
</li>
))}
</ul>
</div>
</div>
)}
</div>
);
});
// 5. Global state management
interface AppState {
theme: 'light' | 'dark';
user: User | null;
notifications: string[];
}
// This would typically be in a separate file and used with context
export const createAppStore = (): AppState => ({
theme: 'light',
user: null,
notifications: []
});
export const ThemeToggle = component$(() => {
const appState = useStore(createAppStore());
const toggleTheme = $(() => {
appState.theme = appState.theme === 'light' ? 'dark' : 'light';
});
return (
<div class={`theme-${appState.theme}`}>
<button onClick$={toggleTheme}>
Current theme: {appState.theme}
</button>
</div>
);
});
export { SignalDemo, TodoApp, ShoppingCart, UserDashboard, ThemeToggle };
💻 Assíncrono e Lazy Loading no Qwik typescript
🔴 complex
Operações assíncronas, lazy loading e rendering resumable no Qwik
// Qwik Async and Lazy Loading
import {
component$,
useSignal,
useStore,
useTask$,
$,
useResource$,
Resource,
useVisibleTask$,
lazy$,
useStylesScoped$
} from '@builder.io/qwik';
// 1. Lazy loaded component
export const LazyComponent = component$(() => {
const isVisible = useSignal(false);
const data = useSignal<string>('');
useVisibleTask$(() => {
isVisible.value = true;
// Load data only when component is visible
setTimeout(() => {
data.value = 'Lazy loaded data!';
}, 1000);
});
return (
<div>
<h3>Lazy Component</h3>
{isVisible.value ? (
<div>
<p>Component is now visible!</p>
<p>{data.value || 'Loading...'}</p>
</div>
) : (
<p>Component will load when visible...</p>
)}
</div>
);
});
// 2. Using lazy$ for code splitting
const HeavyComponent = lazy$(() => import('./heavy-component'));
export const LazyLoadDemo = component$(() => {
const showHeavyComponent = useSignal(false);
return (
<div>
<h3>Lazy Load Demo</h3>
<button
onClick$={() => showHeavyComponent.value = true}
>
Load Heavy Component
</button>
{showHeavyComponent.value && (
<HeavyComponent />
)}
</div>
);
});
// 3. Resource management with cleanup
export const ResourceManagement = component$(() => {
const data = useSignal<string[]>([]);
const loading = useSignal(false);
const error = useSignal<string | null>(null);
const intervalId = useSignal<number | null>(null);
const startPolling = $(() => {
loading.value = true;
error.value = null;
// Simulate polling API
intervalId.value = window.setInterval(async () => {
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
const newData = `Data ${Date.now()}`;
data.value = [...data.value, newData];
loading.value = false;
} catch (err) {
error.value = err instanceof Error ? err.message : 'Unknown error';
loading.value = false;
}
}, 2000);
});
const stopPolling = $(() => {
if (intervalId.value) {
clearInterval(intervalId.value);
intervalId.value = null;
}
});
const clearData = $(() => {
data.value = [];
});
// Cleanup on unmount
useVisibleTask$(({ cleanup }) => {
cleanup(() => {
stopPolling();
});
});
return (
<div>
<h3>Resource Management</h3>
<div>
<button onClick$={startPolling}>Start Polling</button>
<button onClick$={stopPolling}>Stop Polling</button>
<button onClick$={clearData}>Clear Data</button>
</div>
{loading.value && <p>Loading...</p>}
{error.value && <p style="color: red">Error: {error.value}</p>}
<ul>
{data.value.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
<p>Status: {intervalId.value ? 'Polling' : 'Stopped'}</p>
</div>
);
});
// 4. Advanced async patterns
interface AsyncData {
id: number;
title: string;
description: string;
metadata: {
created: Date;
updated: Date;
tags: string[];
};
}
export const AsyncPatterns = component$(() => {
const items = useSignal<AsyncData[]>([]);
const loading = useSignal(false);
const currentFilter = useSignal('all');
// Complex resource with filtering and caching
const itemsResource = useResource$<AsyncData[]>(async ({ track }) => {
track(() => currentFilter.value);
loading.value = true;
try {
// Simulate API call with different endpoints based on filter
let endpoint = '/api/items';
if (currentFilter.value !== 'all') {
endpoint += `?filter=${currentFilter.value}`;
}
console.log('Fetching from:', endpoint);
// Simulate network delay and different responses
await new Promise(resolve => setTimeout(resolve, 1500));
const allItems: AsyncData[] = [
{
id: 1,
title: 'Item 1',
description: 'Description for item 1',
metadata: {
created: new Date('2023-01-01'),
updated: new Date('2023-12-01'),
tags: ['tag1', 'tag2']
}
},
{
id: 2,
title: 'Item 2',
description: 'Description for item 2',
metadata: {
created: new Date('2023-02-01'),
updated: new Date('2023-11-01'),
tags: ['tag2', 'tag3']
}
},
{
id: 3,
title: 'Item 3',
description: 'Description for item 3',
metadata: {
created: new Date('2023-03-01'),
updated: new Date('2023-10-01'),
tags: ['tag1', 'tag3']
}
}
];
// Apply filtering logic
let filteredItems = allItems;
if (currentFilter.value !== 'all') {
filteredItems = allItems.filter(item =>
item.metadata.tags.includes(currentFilter.value)
);
}
return filteredItems;
} catch (err) {
console.error('Failed to fetch items:', err);
throw err;
} finally {
loading.value = false;
}
});
const setFilter = $((filter: string) => {
currentFilter.value = filter;
});
const refreshItems = $(() => {
// Force resource refetch by changing a dependency
const temp = currentFilter.value;
currentFilter.value = '';
setTimeout(() => {
currentFilter.value = temp;
}, 10);
});
return (
<div>
<h3>Advanced Async Patterns</h3>
<div class="filters">
<button
class={currentFilter.value === 'all' ? 'active' : ''}
onClick$={() => setFilter('all')}
>
All
</button>
<button
class={currentFilter.value === 'tag1' ? 'active' : ''}
onClick$={() => setFilter('tag1')}
>
Tag 1
</button>
<button
class={currentFilter.value === 'tag2' ? 'active' : ''}
onClick$={() => setFilter('tag2')}
>
Tag 2
</button>
<button
class={currentFilter.value === 'tag3' ? 'active' : ''}
onClick$={() => setFilter('tag3')}
>
Tag 3
</button>
<button onClick$={refreshItems}>Refresh</button>
</div>
<Resource
value={itemsResource}
onPending={() => <div>Loading items...</div>}
onRejected={(error) => (
<div style="color: red">
Error loading items: {error.message}
</div>
)}
onResolved={(items) => {
return (
<div>
<p>Found {items.length} items</p>
<ul>
{items.map((item) => (
<li key={item.id} class="item-card">
<h4>{item.title}</h4>
<p>{item.description}</p>
<div class="metadata">
<small>
Created: {item.metadata.created.toLocaleDateString()} |
Updated: {item.metadata.updated.toLocaleDateString()}
</small>
<div class="tags">
{item.metadata.tags.map((tag) => (
<span key={tag} class="tag">
{tag}
</span>
))}
</div>
</div>
</li>
))}
</ul>
</div>
);
}}
/>
{loading.value && <div class="loading-indicator">Processing...</div>}
</div>
);
});
// 5. Progressive enhancement with async
export const ProgressiveEnhancement = component$(() => {
const isClient = useSignal(false);
const features = useSignal<string[]>([]);
// Check if we're on the client
useVisibleTask$(() => {
isClient.value = true;
// Detect available features
const detectedFeatures: string[] = [];
if ('serviceWorker' in navigator) {
detectedFeatures.push('Service Worker');
}
if ('IntersectionObserver' in window) {
detectedFeatures.push('Intersection Observer');
}
if ('WebAssembly' in window) {
detectedFeatures.push('WebAssembly');
}
if ('localStorage' in window) {
detectedFeatures.push('Local Storage');
}
features.value = detectedFeatures;
});
const loadAdvancedFeature = $(async () => {
if (!isClient.value) return;
try {
// Simulate loading an advanced feature
const module = await import('./advanced-feature');
console.log('Advanced feature loaded:', module);
features.value = [...features.value, 'Advanced Feature'];
} catch (err) {
console.error('Failed to load advanced feature:', err);
}
});
const testAsyncOperation = $(async () => {
const result = await new Promise<string>((resolve) => {
setTimeout(() => {
resolve('Async operation completed!');
}, 2000);
});
alert(result);
});
return (
<div>
<h3>Progressive Enhancement</h3>
<div class="client-info">
<p>Environment: {isClient.value ? 'Client' : 'Server'}</p>
{isClient.value && (
<div>
<p>Available Features:</p>
<ul>
{features.value.map((feature, index) => (
<li key={index}>{feature}</li>
))}
</ul>
</div>
)}
</div>
{isClient.value && (
<div class="client-only-features">
<button onClick$={loadAdvancedFeature}>
Load Advanced Feature
</button>
<button onClick$={testAsyncOperation}>
Test Async Operation
</button>
</div>
)}
{!isClient.value && (
<div class="server-fallback">
<p>This content works even without JavaScript!</p>
</div>
)}
</div>
);
});
// 6. Optimistic updates
export const OptimisticUpdates = component$(() => {
const posts = useSignal<{ id: number; title: string; likes: number }[]>([
{ id: 1, title: 'Post 1', likes: 5 },
{ id: 2, title: 'Post 2', likes: 3 }
]);
const pendingLikes = useSignal<number[]>([]);
const likePost = $(async (postId: number) => {
// Optimistic update
const post = posts.value.find(p => p.id === postId);
if (post) {
post.likes++;
pendingLikes.value = [...pendingLikes.value, postId];
}
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
// Server succeeded - remove from pending
pendingLikes.value = pendingLikes.value.filter(id => id !== postId);
} catch (err) {
// Revert optimistic update
if (post) {
post.likes--;
}
pendingLikes.value = pendingLikes.value.filter(id => id !== postId);
alert('Failed to like post. Please try again.');
}
});
const addComment = $(async (postId: number, comment: string) => {
if (!comment.trim()) return;
// Optimistic update would go here
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));
alert('Comment added successfully!');
} catch (err) {
alert('Failed to add comment. Please try again.');
}
});
return (
<div>
<h3>Optimistic Updates</h3>
{posts.value.map((post) => (
<div key={post.id} class="post">
<h4>{post.title}</h4>
<div class="post-actions">
<button
onClick$={() => likePost(post.id)}
disabled={pendingLikes.value.includes(post.id)}
>
{pendingLikes.value.includes(post.id) ? 'Liking...' : `Like (${post.likes})`}
</button>
</div>
</div>
))}
</div>
);
});
// Add some scoped styles
useStylesScoped$(`
.filters {
margin-bottom: 1rem;
}
.filters button {
margin-right: 0.5rem;
padding: 0.5rem 1rem;
border: 1px solid #ccc;
background: white;
cursor: pointer;
}
.filters button.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.item-card {
border: 1px solid #ddd;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 4px;
}
.metadata {
margin-top: 0.5rem;
}
.tags {
margin-top: 0.25rem;
}
.tag {
display: inline-block;
background: #f0f0f0;
padding: 0.25rem 0.5rem;
margin-right: 0.25rem;
border-radius: 3px;
font-size: 0.8rem;
}
.loading-indicator {
margin-top: 1rem;
padding: 0.5rem;
background: #f8f9fa;
border-left: 4px solid #007bff;
}
.post {
border: 1px solid #ddd;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 4px;
}
.post-actions button {
margin-right: 0.5rem;
}
.post-actions button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`);
export {
LazyComponent,
LazyLoadDemo,
ResourceManagement,
AsyncPatterns,
ProgressiveEnhancement,
OptimisticUpdates
};
💻 Melhores Práticas e Padrões do Qwik typescript
🔴 complex
Melhores práticas, padrões de performance e técnicas de otimização para aplicações Qwik
// Qwik Best Practices and Patterns
import {
component$,
useSignal,
useStore,
useTask$,
$,
useResource$,
Resource,
useVisibleTask$,
useClientEffect$,
useStylesScoped$,
useBrowserVisibleTask$
} from '@builder.io/qwik';
// 1. Component decomposition and reusability
interface ButtonProps {
onClick$: () => void;
variant?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
loading?: boolean;
}
export const Button = component$<ButtonProps>((props) => {
return (
<button
class={`btn btn-${props.variant || 'primary'} btn-${props.size || 'medium'}`}
onClick$={props.onClick$}
disabled={props.disabled || props.loading}
>
{props.loading ? 'Loading...' : props.children}
</button>
);
});
// 2. Custom hooks pattern
export const useDebounce = <T>(value: T, delay: number) => {
const debouncedValue = useSignal(value);
useTask$(({ track }) => {
track(() => value);
const timer = setTimeout(() => {
debouncedValue.value = value;
}, delay);
return () => clearTimeout(timer);
});
return debouncedValue;
};
export const useLocalStorage = <T>(key: string, initialValue: T) => {
const value = useSignal<T>(initialValue);
useClientEffect$(() => {
// Load from localStorage on mount
const stored = localStorage.getItem(key);
if (stored !== null) {
value.value = JSON.parse(stored);
}
// Save to localStorage when value changes
return () => {
localStorage.setItem(key, JSON.stringify(value.value));
};
});
return value;
};
// 3. Performance optimization patterns
export const VirtualizedList = component$<{
items: any[];
itemHeight: number;
containerHeight: number;
renderItem: (item: any, index: number) => any;
}>((props) => {
const scrollTop = useSignal(0);
const startIndex = useSignal(0);
const endIndex = useSignal(0);
useTask$(({ track }) => {
track(() => scrollTop.value);
track(() => props.items);
start = Math.floor(scrollTop.value / props.itemHeight);
end = Math.min(
start + Math.ceil(props.containerHeight / props.itemHeight) + 1,
props.items.length
);
startIndex.value = start;
endIndex.value = end;
});
const handleScroll = $((event: Event) => {
const target = event.target as HTMLElement;
scrollTop.value = target.scrollTop;
});
let start = 0;
let end = 10;
return (
<div
style={{
height: `${props.containerHeight}px`,
overflow: 'auto'
}}
onScroll$={handleScroll}
>
<div
style={{
height: `${props.items.length * props.itemHeight}px`,
position: 'relative'
}}
>
{props.items.slice(startIndex.value, endIndex.value).map((item, index) => (
<div
key={index}
style={{
position: 'absolute',
top: `${(startIndex.value + index) * props.itemHeight}px`,
height: `${props.itemHeight}px`,
width: '100%'
}}
>
{props.renderItem(item, startIndex.value + index)}
</div>
))}
</div>
</div>
);
});
// 4. Error boundaries and error handling
interface ErrorBoundaryProps {
fallback: (error: Error, reset: () => void) => any;
}
export const ErrorBoundary = component$<ErrorBoundaryProps>((props) => {
const error = useSignal<Error | null>(null);
const hasError = useSignal(false);
useTask$(({ track }) => {
track(() => props.children);
// In a real implementation, you would catch errors from children
// This is a simplified example
});
const reset = $(() => {
error.value = null;
hasError.value = false;
});
return (
<>
{hasError.value && error.value ? (
props.fallback(error.value, reset)
) : (
props.children
)}
</>
);
});
// 5. Optimized form handling
export const OptimizedForm = component$(() => {
const form = useStore({
email: '',
password: '',
errors: {} as Record<string, string>,
touched: {} as Record<string, boolean>
});
const isSubmitting = useSignal(false);
const debouncedEmail = useDebounce(form.email, 500);
const emailValid = useSignal(true);
// Validate email on debounce
useTask$(({ track }) => {
track(() => debouncedEmail.value);
if (form.touched.email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
emailValid.value = emailRegex.test(debouncedEmail.value);
if (!emailValid.value) {
form.errors.email = 'Please enter a valid email address';
} else {
delete form.errors.email;
}
}
});
const handleFieldBlur = $((fieldName: string) => {
form.touched[fieldName] = true;
});
const handleSubmit = $(async (event: Event) => {
event.preventDefault();
isSubmitting.value = true;
// Mark all fields as touched
form.touched.email = true;
form.touched.password = true;
// Validate all fields
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(form.email)) {
form.errors.email = 'Please enter a valid email address';
}
if (form.password.length < 8) {
form.errors.password = 'Password must be at least 8 characters';
}
// Submit if no errors
if (Object.keys(form.errors).length === 0) {
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
alert('Form submitted successfully!');
} catch (err) {
alert('Submission failed. Please try again.');
}
}
isSubmitting.value = false;
});
return (
<form onSubmit$={handleSubmit}>
<div class="form-group">
<label>Email:</label>
<input
type="email"
value={form.email}
onInput$={(e) => form.email = (e.target as HTMLInputElement).value}
onBlur$={() => handleFieldBlur('email')}
class={form.touched.email && form.errors.email ? 'error' : ''}
/>
{form.touched.email && form.errors.email && (
<span class="error-message">{form.errors.email}</span>
)}
</div>
<div class="form-group">
<label>Password:</label>
<input
type="password"
value={form.password}
onInput$={(e) => form.password = (e.target as HTMLInputElement).value}
onBlur$={() => handleFieldBlur('password')}
class={form.touched.password && form.errors.password ? 'error' : ''}
/>
{form.touched.password && form.errors.password && (
<span class="error-message">{form.errors.password}</span>
)}
</div>
<Button
type="submit"
variant="primary"
loading={isSubmitting.value}
disabled={isSubmitting.value}
>
Submit
</Button>
</form>
);
});
// 6. Memory optimization patterns
export const MemoryOptimizedComponent = component$(() => {
const data = useSignal<any[]>([]);
const activeItem = useSignal<any | null>(null);
const subscriptions = useSignal<(() => void)[]>([]);
// Proper cleanup pattern
useVisibleTask$(({ cleanup }) => {
// Setup subscriptions or timers
const timer = setInterval(() => {
console.log('Timer running');
}, 1000);
// Add cleanup function
cleanup(() => {
clearInterval(timer);
// Cleanup all subscriptions
subscriptions.value.forEach(unsubscribe => unsubscribe());
subscriptions.value = [];
});
});
const loadMoreData = $(async () => {
// Load data efficiently
try {
const response = await fetch('/api/data');
const newData = await response.json();
// Process data in chunks to avoid blocking
const chunkSize = 100;
for (let i = 0; i < newData.length; i += chunkSize) {
const chunk = newData.slice(i, i + chunkSize);
data.value = [...data.value, ...chunk];
// Allow UI to update
await new Promise(resolve => setTimeout(resolve, 10));
}
} catch (err) {
console.error('Failed to load data:', err);
}
});
return (
<div>
<h3>Memory Optimized Component</h3>
<button onClick$={loadMoreData}>Load More Data</button>
<p>Data items: {data.value.length}</p>
</div>
);
});
// 7. Responsive design patterns
export const ResponsiveLayout = component$(() => {
const screenSize = useSignal<'mobile' | 'tablet' | 'desktop'>('desktop');
const menuOpen = useSignal(false);
useBrowserVisibleTask$(() => {
const updateScreenSize = () => {
const width = window.innerWidth;
if (width < 768) {
screenSize.value = 'mobile';
} else if (width < 1024) {
screenSize.value = 'tablet';
} else {
screenSize.value = 'desktop';
}
};
updateScreenSize();
window.addEventListener('resize', updateScreenSize);
return () => {
window.removeEventListener('resize', updateScreenSize);
};
});
const toggleMenu = $(() => {
menuOpen.value = !menuOpen.value;
});
return (
<div class={`layout layout-${screenSize.value}`}>
<header class="header">
<h1>My App</h1>
{screenSize.value === 'mobile' && (
<button onClick$={toggleMenu}>Menu</button>
)}
</header>
<div class="container">
<aside class={`sidebar ${menuOpen.value ? 'open' : ''}`}>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
</aside>
<main class="main">
<p>Screen size: {screenSize.value}</p>
{screenSize.value === 'mobile' && (
<p>Mobile menu is {menuOpen.value ? 'open' : 'closed'}</p>
)}
</main>
</div>
</div>
);
});
// 8. Performance monitoring
export const PerformanceMonitor = component$(() => {
const metrics = useStore({
renderTime: 0,
componentCount: 0,
memoryUsage: 0
});
const startTime = useSignal(Date.now());
useVisibleTask$(() => {
const endTime = Date.now();
metrics.renderTime = endTime - startTime.value;
// Monitor memory usage if available
if ('memory' in performance) {
const memory = (performance as any).memory;
metrics.memoryUsage = Math.round(memory.usedJSHeapSize / 1024 / 1024);
}
// Monitor performance metrics
if ('PerformanceObserver' in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'measure') {
console.log(`Performance measure: ${entry.name} - ${entry.duration}ms`);
}
}
});
observer.observe({ entryTypes: ['measure'] });
}
});
return (
<div class="performance-monitor">
<h3>Performance Metrics</h3>
<ul>
<li>Render time: {metrics.renderTime}ms</li>
<li>Memory usage: {metrics.memoryUsage}MB</li>
<li>Components: {metrics.componentCount}</li>
</ul>
</div>
);
});
// Add scoped styles for all components
useStylesScoped$(`
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
transition: all 0.2s;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-small {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.btn-medium {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.btn-large {
padding: 1rem 2rem;
font-size: 1rem;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.form-group input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-group input.error {
border-color: #dc3545;
}
.error-message {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
display: block;
}
.layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.header {
padding: 1rem;
background: #f8f9fa;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.container {
display: flex;
flex: 1;
}
.sidebar {
width: 250px;
background: #f8f9fa;
border-right: 1px solid #ddd;
transition: transform 0.3s;
}
.layout-mobile .sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 1000;
transform: translateX(-100%);
}
.layout-mobile .sidebar.open {
transform: translateX(0);
}
.main {
flex: 1;
padding: 1rem;
}
.performance-monitor {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 1rem;
border-radius: 4px;
font-size: 0.875rem;
}
.performance-monitor h3 {
margin: 0 0 0.5rem 0;
font-size: 1rem;
}
.performance-monitor ul {
margin: 0;
padding-left: 1rem;
}
.performance-monitor li {
margin-bottom: 0.25rem;
}
`);
export {
Button,
useDebounce,
useLocalStorage,
VirtualizedList,
ErrorBoundary,
OptimizedForm,
MemoryOptimizedComponent,
ResponsiveLayout,
PerformanceMonitor
};