函数式组件 + Store + Bus + Service 架构总结
一、四层职责(先记住这个)
Component → 显示什么 + 用户做了什么
Store → 现在是什么状态(事实)
Bus → 发生了什么事(通知)
Service → 业务怎么做(逻辑)
一句话版本:
组件不写业务,Service 不写 JSX,Store 不存事件,Bus 不存状态。
二、数据流方向
用户操作
↓
Component 调用 Service
↓
Service 处理业务逻辑
├── store.setState() → 更新状态(事实)
└── bus.emit() → 发出通知(事件)
↓
├── Component 读 Store 重新渲染
└── 其他 Service 监听 Bus 响应
三、各层实现
Store(Zustand)
// store.ts
interface EditorState {
activeShardId: number;
frameRange: [number, number];
loadingStatus: 'idle' | 'loading' | 'ready' | 'error';
}
export const useEditorStore = create<EditorState>(() => ({
activeShardId: 0,
frameRange: [0, 0],
loadingStatus: 'idle',
}));
原则:只存有"当前值"的东西。
Bus(事件总线)
// bus.ts
export interface BusEvents {
'shard:willChange': { fromId: number; toId: number };
'shard:didChange': { shardId: number };
'shard:loadError': { shardId: number; error: Error };
'frame:rangeChanged': { start: number; end: number };
'toast:show': { message: string; type: 'info' | 'error' | 'success' };
}
export const bus = new TypedEventBus<BusEvents>();
原则:只存"发生了什么",没有当前值。
Service(业务逻辑)
// services/shardService.ts
class ShardService extends BaseService {
private currentShardId = 0;
init() {
// 订阅其他模块的事件
this.on('shard:didChange', ({ shardId }) => {
this.currentShardId = shardId;
});
}
async switchShard(newShardId: number) {
// 1. 通知即将发生
bus.emit('shard:willChange', {
fromId: this.currentShardId,
toId: newShardId,
});
// 2. 更新 loading 状态
store.setState({ loadingStatus: 'loading' });
try {
// 3. 执行业务逻辑
await this.loadPcd(newShardId);
// 4. 更新状态(事实)
store.setState({
activeShardId: newShardId,
loadingStatus: 'ready',
});
// 5. 通知完成
bus.emit('shard:didChange', { shardId: newShardId });
} catch (error) {
store.setState({ loadingStatus: 'error' });
bus.emit('shard:loadError', { shardId: newShardId, error });
}
}
}
原则:只处理业务逻辑,不写 JSX,不直接依赖其他 Service。
Editor(统一实例化)
// editor.ts
class Editor {
shardService = new ShardService();
cacheService = new CacheService();
frameService = new FrameService();
toastService = new ToastService();
init() {
this.shardService.init();
this.cacheService.init();
this.frameService.init();
this.toastService.init();
}
destroy() {
this.shardService.destroy();
this.cacheService.destroy();
this.frameService.destroy();
this.toastService.destroy();
}
}
// 全局单例,应用共享
export const editor = new Editor();
Hook(组件订阅 Bus 的标准方式)
// hooks/useBusEvent.ts
export function useBusEvent<K extends keyof BusEvents>(
event: K,
handler: (payload: BusEvents[K]) => void,
) {
const handlerRef = useRef(handler);
handlerRef.current = handler;
useEffect(() => {
const unsub = bus.on(event, (payload) => {
handlerRef.current(payload);
});
return unsub; // 组件卸载自动取消订阅
}, [event]);
}
Component(函数式组件)
// components/ShardPanel.tsx
export function ShardPanel() {
// ✅ 读 Store → 渲染
const activeShardId = useEditorStore(s => s.activeShardId);
const loadingStatus = useEditorStore(s => s.loadingStatus);
// ✅ 监听 Bus → 一次性 UI 副作用
useBusEvent('shard:loadError', ({ shardId }) => {
toast.error(`分片 ${shardId} 加载失败`);
});
// ✅ 用户操作 → 调用 Service
const handleShardClick = (id: number) => {
editor.shardService.switchShard(id);
};
return (
<div>
{shardList.map(shard => (
<div
key={shard.id}
onClick={() => handleShardClick(shard.id)}
className={shard.id === activeShardId ? 'active' : ''}
>
区域 {shard.id}
{loadingStatus === 'loading' && shard.id === activeShardId && (
<span>加载中...</span>
)}
</div>
))}
</div>
);
}
四、应用入口
// App.tsx
import { editor } from './editor';
export function App() {
useEffect(() => {
editor.init();
return () => editor.destroy();
}, []);
return (
<div>
<ShardPanel />
<Timeline />
<RoiPanel />
<ToastContainer />
</div>
);
}
五、判断每件事该放哪层
这个东西有"当前值"吗?
是 → Store
这件事只在"发生瞬间"有意义吗?
是 → Bus
这是业务逻辑 / 数据处理吗?
是 → Service
这是 UI 渲染 / 用户交互吗?
是 → Component
六、常见错误对照
| 错误写法 | 问题 | 正确做法 |
|---|---|---|
| 组件里写业务逻辑 | 逻辑散落,难复用 | 移到 Service |
| Store 里存事件 | 重复消费,状态污染 | 走 Bus |
| Bus 传递"当前状态" | 新订阅者拿不到 | 存 Store |
| 组件里 new Service | 每次渲染重建实例 | Editor 单例统一管理 |
| Service 直接调用其他 Service | 网状依赖 | 通过 Bus 解耦 |
七、完整协作图
┌─────────────────────────────────────┐
│ Component │
│ useEditorStore() → 读状态渲染 │
│ useBusEvent() → 响应一次性副作用 │
│ onClick() → 调用 Service │
└──────────┬──────────────────────────┘
│ 调用
▼
┌─────────────────────────────────────┐
│ Service │
│ 处理业务逻辑 │
│ store.setState() → 更新状态 │
│ bus.emit() → 发出通知 │
└──────────┬──────────────────────────┘
│
┌─────┴──────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ Store │ │ Bus │
│ 事实 │ │ 通知 │
└────┬────┘ └────┬────┘
│ │
▼ ▼
Component Service /
重新渲染 Component
响应副作用
八、一句话总结
Store 是系统的记忆,Bus 是系统的神经,Service 是系统的大脑,Component 是系统的脸。
四层各司其职,系统行为可预测、可追踪、可测试。