不知道你们有没有这种感觉——每次新建一个 React 项目,光是搭状态管理架子就要花半天。
Redux 太重,MobX 太玄,Zustand 用着挺爽但总觉得缺了点什么……
直到我发现了 easy-model,用类的方式思考业务,状态管理突然就变得直观了。
先说痛点
我们先来回顾一下传统的状态管理写法:
Redux:一个计数器要写多少文件?
// actions/counter.ts
const INCREMENT = 'counter/increment';
export const increment = () => ({ type: INCREMENT });
// reducers/counter.ts
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: (state) => { state.count += 1; },
},
});
// store.ts
export const store = configureStore({
reducer: { counter: counterSlice.reducer },
});
// Counter.tsx
function Counter() {
const count = useSelector(state => state.counter.count);
const dispatch = useDispatch();
return <button onClick={() => dispatch(increment())}>{count}</button>;
}
4 个文件,60+ 行代码,就为了一个计数器?
用类的思维重新理解
easy-model 的核心理念很简单:字段就是状态,方法就是业务逻辑。
// counter.ts 一个文件搞定
export class CounterModel {
count = 0;
increment() {
this.count += 1;
}
}
// Counter.tsx
function Counter() {
const counter = useModel(CounterModel, []);
return <button onClick={counter.increment}>{counter.count}</button>;
}
你没看错,就这么简单。
为什么类的方式更好?
1. 代码组织更自然
业务逻辑和状态天然绑定在一起,不用在 action、reducer、selector 之间来回跳。
// 任务模型 - 所有任务相关的状态和行为都在这里
export class TaskModel {
tasks: Task[] = [];
currentFilter: "all" | "active" | "completed" = "all";
get filteredTasks() {
return this.tasks.filter((t) => {
if (this.currentFilter === "active") return !t.completed;
if (this.currentFilter === "completed") return t.completed;
return true;
});
}
addTask(title: string) {
this.tasks.push({ id: Date.now().toString(), title, completed: false });
}
toggleTask(id: string) {
const task = this.tasks.find((t) => t.id === id);
if (task) task.completed = !task.completed;
}
}
2. 类型推导更完整
IDE 能准确知道 this.tasks 是什么类型,refactor 也不容易出错。
3. 继承和复用
// 基础模型 - 自动处理 loading 和 error
class BaseModel {
loading = false;
error: string | null = null;
@loader.load()
async safeCall<T>(fn: () => Promise<T>) {
try {
return await fn();
} catch (e) {
this.error = e instanceof Error ? e.message : "Unknown error";
}
}
}
// 文章模型继承基础模型
class ArticleModel extends BaseModel {
articles: Article[] = [];
async fetchArticles() {
await this.safeCall(async () => {
const res = await fetch("/api/articles");
this.articles = await res.json();
});
}
}
核心 API 一览
useModel - 创建/获取共享实例
// 创建带初始值的实例
const article = useModel(ArticleModel, []);
// 组件卸载时自动清理生命周期
useInstance - 获取共享实例
class AppState {
user: User | null = null;
theme: 'light' | 'dark' = 'light';
setUser(user: User) {
this.user = user;
}
toggleTheme() {
this.theme = this.theme === 'light' ? 'dark' : 'light';
}
}
// 先定义一个共享实例
export const appState = provide(AppState)();
// 在组件中使用
function Header() {
const { theme, toggleTheme } = useInstance(appState);
return <button onClick={toggleTheme}>{theme} 模式</button>;
}
provide - 实例缓存
相同参数返回相同实例,不同参数返回不同实例。
// 购物车 - 按用户 ID 隔离
export const cartStore = provide(CartModel);
// 同一用户获取同一实例
const cart1 = cartStore("user-123");
const cart2 = cartStore("user-123");
// cart1 === cart2 ✓ 同一个购物车
// 不同用户是不同实例
const cartUserB = cartStore("user-456");
// cartUserB !== cart1 ✓
依赖注入:让服务管理更优雅
这是 easy-model 最让我惊喜的功能——在 React 里也能用依赖注入了。
场景:统一的 HTTP 客户端
// types/http.ts
import { z } from "zod";
export const HttpSchema = z.object({
get: z.function().args(z.string()),
post: z.function().args(z.string(), z.unknown()),
});
// models/article.ts
export class ArticleModel {
articles: Article[] = [];
@inject(HttpSchema)
private http?: HttpClient;
@loader.load(true)
@loader.once
async fetchArticles() {
this.articles = (await this.http?.get("/api/articles")) as Article[];
}
}
// main.tsx
import { CInjection, config, Container } from "@e7w/easy-model";
import { AxiosHttp } from "./http/axios";
import { DevHttp } from "./http/dev";
config(
<Container>
{/* 开发环境用 DevHttp */}
<CInjection schema={HttpSchema} ctor={DevHttp} />
{/* 生产环境换 AxiosHttp */}
{/* <CInjection schema={HttpSchema} ctor={AxiosHttp} /> */}
</Container>,
);
所有 @inject(HttpSchema) 的地方都会自动注入对应的实现。
开发环境用模拟数据,生产环境换真实接口,一行配置搞定。
加载状态:再也不用手动写 loading
class ArticleModel {
articles: Article[] = [];
@loader.load(true) // 参与全局 loading
@loader.once // 只加载一次
async fetchArticles() {
const res = await fetch("/api/articles");
this.articles = await res.json();
}
}
一行装饰器替代 setLoading(true) -> fetch -> setLoading(false) -> handleError 的样板代码。
性能怎么样?
官方的 benchmark(1000 个组件批量更新):
| 方案 | 耗时 |
|---|---|
| Zustand | ~0.6ms |
| easy-model | ~3.1ms |
| MobX | ~16.9ms |
| Redux | ~51.5ms |
easy-model 在功能完整度和性能之间取得了很好的平衡。
适合什么场景?
强烈推荐:
- 中大型 React 项目
- TypeScript 项目(类型推导很爽)
- 需要依赖注入的企业级应用
- 从 Redux/MobX 迁移
不太适合:
- 很简单的小项目(useState 够用)
- 不想用 TypeScript 的项目
怎么开始?
npm install @e7w/easy-model
然后把文档看一遍,基本上半天就能上手。
最后说点个人感受:用 easy-model 之后,我发现自己开始用"模型"的视角去思考业务,而不是纠结于"这个状态该放哪个 store"。代码的聚合度更高了,也更容易测试。
如果你也在寻找更优雅的状态管理方案,不妨试试看。
有问题可以在评论区聊~
GitHub: github.com/ZYF93/easy-…