Echo 示例项目
本文档提供了使用 Echo 状态管理库的完整示例项目和用例,帮助您理解如何在实际应用中使用 Echo。
待办事项应用
这是一个完整的待办事项应用示例,展示了 Echo 的基本用法:
tsx
import React, { useState } from "react";
import { Echo } from "echo-state";
// 定义待办事项类型
interface Todo {
id: number;
text: string;
completed: boolean;
}
// 创建待办事项存储
const todoStore = new Echo<{ todos: Todo[] }>({
todos: [],
}).localStorage({ name: "todo-app", sync: true });
function TodoApp() {
const { todos } = todoStore.use();
const [newTodo, setNewTodo] = useState("");
const addTodo = () => {
if (!newTodo.trim()) return;
todoStore.set((state) => ({
todos: [
...state.todos,
{
id: Date.now(),
text: newTodo,
completed: false,
},
],
}));
setNewTodo("");
};
const toggleTodo = (id: number) => {
todoStore.set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
}));
};
const deleteTodo = (id: number) => {
todoStore.set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
}));
};
return (
<div>
<h1>待办事项</h1>
<div>
<input
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="添加新待办..."
/>
<button onClick={addTodo}>添加</button>
</div>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span
style={{
textDecoration: todo.completed ? "line-through" : "none",
}}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
export default TodoApp;
购物车应用
这个示例展示了如何使用 Echo 管理购物车状态:
tsx
import React from "react";
import { Echo } from "echo-state";
// 定义产品和购物车类型
interface Product {
id: string;
name: string;
price: number;
image: string;
}
interface CartItem {
product: Product;
quantity: number;
}
interface CartState {
items: CartItem[];
loading: boolean;
}
// 创建购物车存储
const cartStore = new Echo<CartState>({
items: [],
loading: false,
}).localStorage({ name: "shopping-cart", sync: true });
// 模拟产品数据
const products: Product[] = [
{
id: "p1",
name: "智能手机",
price: 2999,
image: "phone.jpg",
},
{
id: "p2",
name: "无线耳机",
price: 799,
image: "headphones.jpg",
},
{
id: "p3",
name: "平板电脑",
price: 3499,
image: "tablet.jpg",
},
];
// 产品列表组件
function ProductList() {
const addToCart = (product: Product) => {
cartStore.set((state) => {
const existingItem = state.items.find(
(item) => item.product.id === product.id
);
if (existingItem) {
// 如果产品已在购物车中,增加数量
return {
items: state.items.map((item) =>
item.product.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
),
};
} else {
// 否则添加新项目
return {
items: [...state.items, { product, quantity: 1 }],
};
}
});
};
return (
<div className="product-list">
<h2>产品列表</h2>
<div className="products">
{products.map((product) => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>¥{product.price}</p>
<button onClick={() => addToCart(product)}>加入购物车</button>
</div>
))}
</div>
</div>
);
}
// 购物车组件
function Cart() {
const { items } = cartStore.use();
const updateQuantity = (productId: string, quantity: number) => {
if (quantity <= 0) {
removeFromCart(productId);
return;
}
cartStore.set((state) => ({
items: state.items.map((item) =>
item.product.id === productId ? { ...item, quantity } : item
),
}));
};
const removeFromCart = (productId: string) => {
cartStore.set((state) => ({
items: state.items.filter((item) => item.product.id !== productId),
}));
};
const totalPrice = items.reduce(
(sum, item) => sum + item.product.price * item.quantity,
0
);
return (
<div className="cart">
<h2>购物车</h2>
{items.length === 0 ? (
<p>购物车为空</p>
) : (
<>
<ul>
{items.map((item) => (
<li key={item.product.id} className="cart-item">
<img
src={item.product.image}
alt={item.product.name}
className="cart-item-image"
/>
<div className="cart-item-details">
<h4>{item.product.name}</h4>
<p>¥{item.product.price}</p>
</div>
<div className="cart-item-actions">
<button
onClick={() =>
updateQuantity(item.product.id, item.quantity - 1)
}
>
-
</button>
<span>{item.quantity}</span>
<button
onClick={() =>
updateQuantity(item.product.id, item.quantity + 1)
}
>
+
</button>
</div>
<button
className="remove-button"
onClick={() => removeFromCart(item.product.id)}
>
删除
</button>
</li>
))}
</ul>
<div className="cart-summary">
<p>总计: ¥{totalPrice}</p>
<button className="checkout-button">结算</button>
</div>
</>
)}
</div>
);
}
// 主应用组件
function ShoppingApp() {
return (
<div className="shopping-app">
<header>
<h1>Echo 购物应用</h1>
</header>
<main>
<ProductList />
<Cart />
</main>
</div>
);
}
export default ShoppingApp;
用户认证应用
这个示例展示了如何使用 Echo 管理用户认证状态:
tsx
import React, { useState, useEffect } from "react";
import { Echo } from "echo-state";
// 定义认证状态类型
interface AuthState {
user: {
id: string;
name: string;
email: string;
} | null;
token: string | null;
isAuthenticated: boolean;
loading: boolean;
error: string | null;
}
// 创建认证状态存储
const authStore = new Echo<AuthState>({
user: null,
token: null,
isAuthenticated: false,
loading: false,
error: null,
}).localStorage({ name: "auth-store", sync: true });
// 模拟API调用
const api = {
login: async (email: string, password: string) => {
// 模拟网络延迟
await new Promise((resolve) => setTimeout(resolve, 1000));
// 模拟验证
if (email === "user@example.com" && password === "password") {
return {
user: {
id: "user-1",
name: "测试用户",
email: "user@example.com",
},
token: "fake-jwt-token",
};
} else {
throw new Error("邮箱或密码不正确");
}
},
logout: async () => {
// 模拟网络延迟
await new Promise((resolve) => setTimeout(resolve, 500));
return { success: true };
},
};
// 登录表单组件
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { loading, error } = authStore.use();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
authStore.set({ loading: true, error: null });
const { user, token } = await api.login(email, password);
authStore.set({
user,
token,
isAuthenticated: true,
loading: false,
});
} catch (err) {
authStore.set({
loading: false,
error: err.message,
});
}
};
return (
<form onSubmit={handleSubmit} className="login-form">
<h2>登录</h2>
{error && <div className="error-message">{error}</div>}
<div className="form-group">
<label htmlFor="email">邮箱</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">密码</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit" disabled={loading}>
{loading ? "登录中..." : "登录"}
</button>
<p className="hint">提示: 使用 user@example.com / password 登录</p>
</form>
);
}
// 用户信息组件
function UserProfile() {
const { user } = authStore.use();
const handleLogout = async () => {
try {
authStore.set({ loading: true });
await api.logout();
authStore.set({
user: null,
token: null,
isAuthenticated: false,
loading: false,
});
} catch (err) {
authStore.set({
loading: false,
error: err.message,
});
}
};
if (!user) return null;
return (
<div className="user-profile">
<h2>用户信息</h2>
<div className="profile-details">
<p>
<strong>姓名:</strong> {user.name}
</p>
<p>
<strong>邮箱:</strong> {user.email}
</p>
</div>
<button onClick={handleLogout}>退出登录</button>
</div>
);
}
// 受保护的内容组件
function ProtectedContent() {
return (
<div className="protected-content">
<h2>受保护的内容</h2>
<p>这是只有登录用户才能看到的内容。</p>
</div>
);
}
// 主应用组件
function AuthApp() {
const { isAuthenticated, loading } = authStore.use();
// 检查本地存储中的令牌
useEffect(() => {
const checkAuth = async () => {
await authStore.ready();
// 这里可以添加令牌验证逻辑
};
checkAuth();
}, []);
if (loading) {
return <div className="loading">加载中...</div>;
}
return (
<div className="auth-app">
<header>
<h1>Echo 认证示例</h1>
</header>
<main>
{isAuthenticated ? (
<>
<UserProfile />
<ProtectedContent />
</>
) : (
<LoginForm />
)}
</main>
</div>
);
}
export default AuthApp;
多项目管理应用
这个示例展示了如何使用 Echo 的 switch
方法在多个项目之间切换:
tsx
import React, { useState, useEffect } from "react";
import { Echo } from "echo-state";
// 定义项目类型
interface Project {
id: string;
name: string;
description: string;
tasks: Task[];
}
interface Task {
id: string;
title: string;
completed: boolean;
}
// 创建项目存储
const projectStore = new Echo<Project>({
id: "",
name: "",
description: "",
tasks: [],
}).indexed({
name: "current-project",
database: "projects-db",
object: "projects",
});
// 模拟项目数据
const projectsData: Project[] = [
{
id: "project-1",
name: "网站重设计",
description: "公司网站的重新设计和开发",
tasks: [
{ id: "task-1-1", title: "设计首页原型", completed: true },
{ id: "task-1-2", title: "开发首页", completed: false },
{ id: "task-1-3", title: "测试响应式布局", completed: false },
],
},
{
id: "project-2",
name: "移动应用开发",
description: "iOS 和 Android 客户端开发",
tasks: [
{ id: "task-2-1", title: "设计用户界面", completed: true },
{ id: "task-2-2", title: "实现用户认证", completed: true },
{ id: "task-2-3", title: "开发核心功能", completed: false },
{ id: "task-2-4", title: "应用测试", completed: false },
],
},
{
id: "project-3",
name: "营销活动",
description: "季度营销活动策划和执行",
tasks: [
{ id: "task-3-1", title: "市场调研", completed: true },
{ id: "task-3-2", title: "制定营销策略", completed: false },
{ id: "task-3-3", title: "准备宣传材料", completed: false },
{ id: "task-3-4", title: "社交媒体推广", completed: false },
],
},
];
// 项目选择器组件
function ProjectSelector() {
const [projects, setProjects] = useState<Project[]>([]);
const [loading, setLoading] = useState(true);
const currentProject = projectStore.use();
// 模拟从API获取项目列表
useEffect(() => {
const fetchProjects = async () => {
// 模拟网络延迟
await new Promise((resolve) => setTimeout(resolve, 800));
setProjects(projectsData);
setLoading(false);
// 如果没有当前项目,加载第一个项目
if (!currentProject.id && projectsData.length > 0) {
switchProject(projectsData[0].id);
}
};
fetchProjects();
}, []);
const switchProject = async (projectId: string) => {
setLoading(true);
try {
// 使用 switch 方法切换到不同的项目
await projectStore.switch(projectId).ready();
// 检查是否需要从"服务器"加载项目数据
if (!projectStore.current.id) {
const project = projectsData.find((p) => p.id === projectId);
if (project) {
projectStore.set(project, { replace: true });
}
}
} catch (error) {
console.error("切换项目失败:", error);
} finally {
setLoading(false);
}
};
if (loading && !currentProject.id) {
return <div className="loading">加载项目...</div>;
}
return (
<div className="project-selector">
<h2>项目</h2>
<ul>
{projects.map((project) => (
<li
key={project.id}
className={project.id === currentProject.id ? "active" : ""}
onClick={() => switchProject(project.id)}
>
{project.name}
</li>
))}
</ul>
</div>
);
}
// 任务列表组件
function TaskList() {
const { id, name, description, tasks } = projectStore.use();
const toggleTask = (taskId: string) => {
projectStore.set((state) => ({
tasks: state.tasks.map((task) =>
task.id === taskId ? { ...task, completed: !task.completed } : task
),
}));
};
const [newTask, setNewTask] = useState("");
const addTask = () => {
if (!newTask.trim()) return;
projectStore.set((state) => ({
tasks: [
...state.tasks,
{
id: `task-${Date.now()}`,
title: newTask,
completed: false,
},
],
}));
setNewTask("");
};
if (!id) {
return <div className="no-project">请选择一个项目</div>;
}
return (
<div className="task-list">
<div className="project-header">
<h2>{name}</h2>
<p>{description}</p>
</div>
<div className="add-task">
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="添加新任务..."
/>
<button onClick={addTask}>添加</button>
</div>
<ul>
{tasks.map((task) => (
<li key={task.id} className={task.completed ? "completed" : ""}>
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
/>
<span>{task.title}</span>
</li>
))}
</ul>
<div className="task-summary">
<p>
完成: {tasks.filter((t) => t.completed).length} / {tasks.length}
</p>
</div>
</div>
);
}
// 主应用组件
function ProjectApp() {
useEffect(() => {
// 确保在组件卸载时清理资源
return () => {
projectStore.destroy();
};
}, []);
return (
<div className="project-app">
<header>
<h1>Echo 项目管理</h1>
</header>
<main>
<ProjectSelector />
<TaskList />
</main>
</div>
);
}
export default ProjectApp;
主题切换应用
这个示例展示了如何使用 Echo 实现主题切换功能:
tsx
import React, { useEffect } from "react";
import { Echo } from "echo-state";
// 定义主题类型
interface ThemeState {
mode: "light" | "dark";
primaryColor: string;
fontSize: "small" | "medium" | "large";
}
// 创建主题存储
const themeStore = new Echo<ThemeState>({
mode: "light",
primaryColor: "#1890ff",
fontSize: "medium",
}).localStorage({ name: "theme-settings", sync: true });
// 颜色选项
const colorOptions = [
{ name: "蓝色", value: "#1890ff" },
{ name: "绿色", value: "#52c41a" },
{ name: "紫色", value: "#722ed1" },
{ name: "红色", value: "#f5222d" },
{ name: "橙色", value: "#fa8c16" },
];
// 字体大小选项
const fontSizeOptions = [
{ name: "小", value: "small" },
{ name: "中", value: "medium" },
{ name: "大", value: "large" },
];
// 主题设置组件
function ThemeSettings() {
const theme = themeStore.use();
const toggleThemeMode = () => {
themeStore.set((state) => ({
mode: state.mode === "light" ? "dark" : "light",
}));
};
const changeColor = (color: string) => {
themeStore.set({ primaryColor: color });
};
const changeFontSize = (size: "small" | "medium" | "large") => {
themeStore.set({ fontSize: size });
};
return (
<div className="theme-settings">
<h2>主题设置</h2>
<div className="setting-group">
<h3>主题模式</h3>
<button onClick={toggleThemeMode}>
切换到{theme.mode === "light" ? "暗色" : "亮色"}模式
</button>
</div>
<div className="setting-group">
<h3>主题颜色</h3>
<div className="color-options">
{colorOptions.map((option) => (
<div
key={option.value}
className={`color-option ${
theme.primaryColor === option.value ? "active" : ""
}`}
style={{ backgroundColor: option.value }}
onClick={() => changeColor(option.value)}
title={option.name}
></div>
))}
</div>
</div>
<div className="setting-group">
<h3>字体大小</h3>
<div className="font-size-options">
{fontSizeOptions.map((option) => (
<button
key={option.value}
className={theme.fontSize === option.value ? "active" : ""}
onClick={() => changeFontSize(option.value as any)}
>
{option.name}
</button>
))}
</div>
</div>
</div>
);
}
// 主题预览组件
function ThemePreview() {
const theme = themeStore.use();
return (
<div className="theme-preview">
<h2>主题预览</h2>
<div
className={`preview-content ${theme.mode}`}
style={
{
"--primary-color": theme.primaryColor,
fontSize:
theme.fontSize === "small"
? "14px"
: theme.fontSize === "medium"
? "16px"
: "18px",
} as React.CSSProperties
}
>
<h3>标题文本</h3>
<p>这是一段示例文本,用于展示当前主题设置的效果。</p>
<button className="primary-button">主要按钮</button>
<button className="secondary-button">次要按钮</button>
<div className="card">
<h4>卡片标题</h4>
<p>卡片内容示例,展示不同背景下的文本显示效果。</p>
</div>
</div>
</div>
);
}
// 应用主题到文档
function ThemeProvider({ children }: { children: React.ReactNode }) {
const theme = themeStore.use();
useEffect(() => {
// 应用主题到文档根元素
document.documentElement.setAttribute("data-theme", theme.mode);
document.documentElement.style.setProperty(
"--primary-color",
theme.primaryColor
);
document.documentElement.style.setProperty(
"--font-size",
theme.fontSize === "small"
? "14px"
: theme.fontSize === "medium"
? "16px"
: "18px"
);
}, [theme]);
return <>{children}</>;
}
// 主应用组件
function ThemeApp() {
return (
<ThemeProvider>
<div className="theme-app">
<header>
<h1>Echo 主题设置</h1>
</header>
<main>
<ThemeSettings />
<ThemePreview />
</main>
</div>
</ThemeProvider>
);
}
export default ThemeApp;
总结
这些示例展示了 Echo 状态管理库在不同场景下的应用:
- 待办事项应用 - 基本的状态管理和 LocalStorage 持久化
- 购物车应用 - 复杂状态操作和计算
- 用户认证应用 - 认证状态管理和跨窗口同步
- 多项目管理应用 - 使用
switch
方法在多个数据集之间切换 - 主题切换应用 - 主题状态管理和应用到 UI
这些示例可以作为您开发自己的应用的起点,根据需要进行修改和扩展。