Astro 中的状态管理是如何实现的?如何在 React、Vue、Svelte 组件中管理状态?
Astro 的状态管理对于构建交互式应用非常重要。虽然 Astro 默认是静态的,但可以通过多种方式实现状态管理。客户端状态管理:React 集成: // src/components/Counter.jsx import { useState, useEffect } from 'react'; export function Counter() { const [count, setCount] = useState(0); useEffect(() => { const saved = localStorage.getItem('count'); if (saved) setCount(parseInt(saved)); }, []); useEffect(() => { localStorage.setItem('count', count.toString()); }, [count]); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(c => c + 1)}>Increment</button> <button onClick={() => setCount(c => c - 1)}>Decrement</button> </div> ); }Vue 集成: <!-- src/components/TodoList.vue --> <script setup> import { ref, computed } from 'vue'; const todos = ref([ { id: 1, text: 'Learn Astro', completed: false }, { id: 2, text: 'Build app', completed: true }, ]); const completedCount = computed(() => todos.value.filter(t => t.completed).length ); function addTodo(text) { todos.value.push({ id: Date.now(), text, completed: false, }); } function toggleTodo(id) { const todo = todos.value.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } </script> <template> <div> <p>Completed: {{ completedCount }} / {{ todos.length }}</p> <ul> <li v-for="todo in todos" :key="todo.id"> <input type="checkbox" :checked="todo.completed" @change="toggleTodo(todo.id)" /> <span :style="{ textDecoration: todo.completed ? 'line-through' : 'none' }"> {{ todo.text }} </span> </li> </ul> </div> </template>Svelte 集成: <!-- src/components/ShoppingCart.svelte --> <script> import { writable } from 'svelte/store'; const cart = writable([]); const total = writable(0); function addToCart(product) { cart.update(items => [...items, product]); total.update(t => t + product.price); } function removeFromCart(index) { cart.update(items => { const removed = items[index]; total.update(t => t - removed.price); return items.filter((_, i) => i !== index); }); } </script> <div> <h2>Shopping Cart</h2> <p>Total: ${$total}</p> <ul> {#each $cart as item, index} <li> {item.name} - ${item.price} <button on:click={() => removeFromCart(index)}>Remove</button> </li> {/each} </ul> </div>全局状态管理(Zustand):// src/store/useStore.tsimport { create } from 'zustand';interface StoreState { user: User | null; theme: 'light' | 'dark'; setUser: (user: User) => void; setTheme: (theme: 'light' | 'dark') => void;}export const useStore = create<StoreState>((set) => ({ user: null, theme: 'light', setUser: (user) => set({ user }), setTheme: (theme) => set({ theme }),}));// src/components/Header.jsximport { useStore } from '../store/useStore';export function Header() { const { user, theme, setTheme } = useStore(); return ( <header> <h1>My App</h1> {user ? <p>Welcome, {user.name}</p> : <p>Please login</p>} <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> {theme === 'light' ? '🌙' : '☀️'} </button> </header> );}服务端状态管理(React Query):// src/lib/api.tsexport async function fetchPosts() { const response = await fetch('/api/posts'); return response.json();}export async function fetchPost(id: string) { const response = await fetch(`/api/posts/${id}`); return response.json();}// src/components/PostList.jsximport { useQuery } from '@tanstack/react-query';import { fetchPosts } from '../lib/api';export function PostList() { const { data, isLoading, error } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts, }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <ul> {data.map(post => ( <li key={post.id}> <a href={`/blog/${post.slug}`}>{post.title}</a> </li> ))} </ul> );}表单状态管理:// src/components/ContactForm.jsximport { useState } from 'react';export function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', message: '', }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); function handleChange(e) { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); } async function handleSubmit(e) { e.preventDefault(); setIsSubmitting(true); try { const response = await fetch('/api/contact', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }); if (!response.ok) throw new Error('Submission failed'); alert('Message sent!'); setFormData({ name: '', email: '', message: '' }); } catch (error) { setErrors({ submit: error.message }); } finally { setIsSubmitting(false); } } return ( <form onSubmit={handleSubmit}> <input name="name" value={formData.name} onChange={handleChange} placeholder="Name" required /> <input name="email" type="email" value={formData.email} onChange={handleChange} placeholder="Email" required /> <textarea name="message" value={formData.message} onChange={handleChange} placeholder="Message" required /> <button type="submit" disabled={isSubmitting}> {isSubmitting ? 'Sending...' : 'Send'} </button> {errors.submit && <p className="error">{errors.submit}</p>} </form> );}URL 状态管理:// src/components/ProductFilter.jsximport { useSearchParams } from 'react-router-dom';export function ProductFilter() { const [searchParams, setSearchParams] = useSearchParams(); const category = searchParams.get('category') || 'all'; const sort = searchParams.get('sort') || 'name'; const page = parseInt(searchParams.get('page') || '1'); function updateFilter(key, value) { setSearchParams(prev => { const newParams = new URLSearchParams(prev); newParams.set(key, value); return newParams; }); } return ( <div> <select value={category} onChange={(e) => updateFilter('category', e.target.value)} > <option value="all">All Categories</option> <option value="electronics">Electronics</option> <option value="clothing">Clothing</option> </select> <select value={sort} onChange={(e) => updateFilter('sort', e.target.value)} > <option value="name">Name</option> <option value="price">Price</option> <option value="date">Date</option> </select> <div> <button disabled={page === 1} onClick={() => updateFilter('page', page - 1)} > Previous </button> <span>Page {page}</span> <button onClick={() => updateFilter('page', page + 1)}> Next </button> </div> </div> );}服务端状态(SSR):---// src/pages/dashboard.astroimport { getEntry } from 'astro:content';// 服务端获取数据const user = await getUser(Astro.request);const posts = await getCollection('blog');const stats = await fetchStats(user.id);---<div class="dashboard"> <h1>Welcome, {user.name}</h1> <div class="stats"> <div class="stat"> <h3>Total Posts</h3> <p>{stats.totalPosts}</p> </div> <div class="stat"> <h3>Total Views</h3> <p>{stats.totalViews}</p> </div> </div> <div class="posts"> {posts.map(post => ( <article> <h2>{post.data.title}</h2> <p>{post.data.description}</p> </article> ))} </div></div>最佳实践:选择合适的状态管理方案:简单状态:使用框架内置状态(useState、ref)复杂状态:使用状态管理库(Zustand、Redux)服务端状态:使用数据获取库(React Query)保持状态简洁:只存储必要的数据避免冗余状态使用派生状态性能优化:使用 memo 避免不必要的重渲染实现虚拟滚动处理大量数据使用防抖和节流优化频繁更新类型安全:使用 TypeScript 定义状态类型确保状态更新类型正确使用类型推导减少错误测试状态管理:编写单元测试验证状态逻辑测试状态更新函数模拟异步状态更新Astro 的状态管理方案灵活多样,可以根据项目需求选择最适合的方案。