前端抽象化,打破框架枷锁:react现代化项目中的思想体现
引言
这一系列前文分别拆解了多端通讯、HTTP 请求、路由等专题,以及 OOP 在现代前端中的用法。但回到真实项目,时间与经验往往不允许我们一开始就搭一个“理想架构”甚至连设计一个高质量的模块都没时间,更多时候,我们需要在几小时内启动新项目,或在存量代码上渐进式重构、进行二开。
这篇文章给出一套“够用且可演进”的 React 现代化分层实践:把展示、状态/副作用、业务用例与服务边界划清,让组件更轻、逻辑更聚焦、测试更可控;同时尽量与框架解耦,方便未来迁移或替换实现。
文末附有可复制的 AI 提示词,可在 Cursor、VS Code 等 IDE 中约束生成严格遵循本文分层的代码。随着项目演进,建议同步更新提示词以贴合你的领域模型与团队规范。
基于 DDD 的前端分层实践(React + TypeScript)
这是一套面向react前端的轻量分层规范,目标是把“展示、状态、副作用、业务逻辑、数据访问”清晰拆开:组件更轻、用例更清晰、服务更可复用、测试更可控。它不追求“全能框架”,而是帮助你在日常迭代中,稳步提升可读性与可维护性。并且将业务逻辑与 React 解耦,方便未来迁移到其他框架(如 Vue、Svelte、原生 Web Components)。
架构图
🎯 各层职责与内容
1. Component(UI 层)
- 职责: 纯展示与交互承载。
- 包含: React 组件(例如 Modal、Form、Button)、样式与布局、交互事件绑定。
- 设计原则:
- css使用原子化css(如tailwindcss,unocsss),一次性解决大量关于css的老问题
- 可以包含纯 UI 的本地 state(例如弹窗显隐、输入值、hover 状态),但与业务数据分离
- 不包含业务规则与流程
- 不直接操作全局/业务状态(通过 Hook/Store 获取数据与动作)
- 高复用组件尽量保持“更小、更纯”,页面层多写一点没关系,避免把变化集中堆到一个“巨石组件”里
2. Hook/Store(状态管理层)
- 职责: 管理可视状态与副作用,承载微型业务 Store,并协调全局 Store;对外暴露“响应式数据 + 动作”。
- 包含:
- 非 Store 型 Hook:
useState
、useReducer
、useEffect
、useCallback
等 - 微型业务 Store:按功能模块拆分的模块级单例 Store(仅存状态与同步修改,不写业务流程)
- 协调全局 Store:整合用户信息、主题、语言等跨模块状态
- 将多个 UseCase 进行场景化组合,为组件提供统一的数据接口
- 非 Store 型 Hook:
- 设计原则:
- UI 层只关心可视状态;业务数据通过 Hook/Store 提供
- 协调本地状态、微型 Store 与全局 Store 的边界
- 处理异步与副作用,把复杂度挡在组件之外
- 对内调用 UseCase,对外仅暴露数据与交互函数
示例(登录:本地表单 state + 用例调用)
// hooks/useAuth.ts
import { useState } from 'react'// 假 UseCase(真实项目替换为注入的 UseCase 实例)
type Credentials = { username: string; password: string }
type User = { id: string; name: string; token: string }const loginUseCase = async (cred: Credentials): Promise<User> => {if (!cred.username || !cred.password) throw new Error('EMPTY')return { id: 'u1', name: cred.username, token: 'token-xxx' }
}
const logoutUseCase = async (): Promise<void> => Promise.resolve()export function useAuth() {// 业务相关状态(Hook 管理并暴露)const [username, setUsername] = useState('')const [password, setPassword] = useState('')const [user, setUser] = useState<User | null>(null)const [loading, setLoading] = useState(false)const [error, setError] = useState<string | undefined>()const login = async () => {setLoading(true); setError(undefined)try {const u = await loginUseCase({ username, password })setUser(u)} catch (e) {setError('登录失败,请检查账号或稍后再试')} finally {setLoading(false)}}const logout = async () => {await logoutUseCase()setUser(null)setPassword('')}return {username, setUsername,password, setPassword,user, loading, error,login, logout,}
}
3. UseCase(业务逻辑层)
- 职责: 承载“可读的业务流程”,进行领域规则校验与服务编排。用例是变化最频繁的地方,必须一眼读懂、注释清晰。
- 示例:
// 示例:展示流程可读性与依赖注入 class DemoUseCase {constructor(private readonly aService: AService,private readonly bService: BService,private readonly cStore: Store) {}async execute(href: string, storeData: Partial<StoreState>) {// 1) 获取 A 数据const aData = await this.aService.getA(href);// 2) 基于 A 数据获取 Bconst b = await this.bService.getB(aData);// 3) 更新 Store(也可选择返回结果,由 Hook/Store 处理)this.cStore.update({...storeData,b,});// 推荐返回领域实体/DTO,而非裸数据return b;} }
- 包含: 业务用例类(如 ImageStoryUseCase、ScriptGenerationUseCase)、完整的业务流程方法、业务规则验证、构建/协调领域实体。
- 设计原则:
- 用例包含完整的业务流程与校验
- 协调多个 Service 调用,屏蔽技术细节
- 返回领域实体或 DTO,而非原始数据
- 方法“无实例依赖状态”,不把业务长期状态挂在类字段上
- 与 React 解耦,保持纯 TypeScript
4. Service(外部服务层)
- 职责: 集成外部系统与技术细节,提供可复用的“纯函数式”能力。
- 包含: 技术实现(图片处理、网络请求、加解密、格式转换等)、错误包装与重试策略、可被多个 UseCase 复用的函数/模块。
- 设计原则:
- 尽量写成纯函数:输入 → 输出,不依赖外部闭包/全局状态
- 所需依赖通过显式参数传入(而非偷偷引用)
- 聚焦技术细节,屏蔽上层业务语义
- 与 React 解耦,保持纯 TypeScript
📊 状态管理策略
- 功能模块状态: 按“模块”拆分多个微型 Store(而非按页面),优先单例
- 纯 UI 状态: 组件内 useState,与业务解耦,只服务于视觉/交互
- 业务流程状态: 放在 UseCase 的执行流程里,以局部变量或参数传递,避免在类上维护长期状态
- 全局状态: 全局 Store(如用户信息、主题、语言等),仅在确有共享需求时引入
数据流向
用户操作 → Component(触发动作) → Hook/Store(协调 Store 与 UseCase) → UseCase(执行业务) → Service(外部交互/数据访问) → Store(更新) → Component(渲染)
设计模式应用
- 依赖注入(DI): UseCase 通过构造函数注入 Service,降低耦合
- 策略模式: 把“可替换的规则/算法”抽象为策略,方便扩展
- 观察者/事件总线: 跨页面、跨组件通信(如 ChatBox)用事件流解耦
🧪 测试策略
- Component 层: 不建议做单测,保障可访问性与视觉回归即可
- Hook/Store 层: 不建议做重单测,尽量把复杂逻辑下沉到 UseCase
- UseCase 层: 强烈建议做单测,这是性价比最高的测试点
- Service 层: 被 UseCase 覆盖;必要时做集成/契约测试(API 客户端/SDK 按团队规范)
- 注: 谦卑对象模式(Humble Object Pattern)把难测试的 UI/外部交互与“可测试的核心逻辑”拆开,核心逻辑独立为纯对象做单测。
错误处理机制
- 错误分类:
- 领域错误: 业务规则校验失败(如 StoryValidationError)
- 基础设施错误: 外部服务/网络失败(如 ImageUploadError)
- 应用错误: 用例编排/应用层错误(如 UseCaseError)
- 处理流程:
- Service 捕获原始错误 → 包装为可识别的应用/基础设施错误
- UseCase 根据业务语义抛出领域错误
- Hook 统一兜底,将错误转成用户可读的提示与可恢复动作
- Component 仅负责展示(不做错误分流与恢复逻辑)
性能优化策略
- 状态优化: useMemo 做派生数据,useCallback 控制引用稳定性;适度使用 React.memo,以真实瓶颈为依据
- 业务优化:
- UseCase 方法尽量“无实例状态”,只依赖入参与返回值
- 多微型 Store,少巨大组件的本地 state,便于子组件拆分与重用
- Store 只负责“状态 + 简单的同步修改”,业务流程放入 UseCase
- 统一 Tailwind 设计令牌(圆角、边框、主题、阴影),减少随意发挥
🤖 AI 生成提示词(可直接复制使用)
请严格按照“组件 Component → Hook/Store → UseCase → Service”的分层生成 React + TypeScript 代码,并遵守以下约束:
- 目标与范围
- 输出满足下述“功能描述”的最小可用实现,并提供必要的类型与依赖注入示例。
- 分层要求(必须遵守)
- Component: 仅展示与交互,不包含业务规则;可以使用纯 UI 的本地 state;不直接读写全局/业务状态;通过 Hook/Store 获取数据与动作。
- Hook/Store: 管理可视状态与副作用;承载微型业务 Store(模块级单例,存状态与同步修改);协调全局 Store;对内组合调用 UseCase,将用例中逻辑得到的数据变为状态,对外暴露数据与动作。
- UseCase: 编排完整业务流程,做领域校验;不依赖 React;方法不依赖实例长期状态;返回领域实体/DTO(必要时由 Hook/Store 更新状态)。
- Service: 技术细节与外部交互的“纯函数”,所需依赖显式入参传入;不依赖 React;可复用。
- 错误与边界
- 分类: 领域错误/基础设施错误/应用错误;在 Service 包装原始错误,在 UseCase 识别并抛出领域错误,在 Hook 转换为用户可读信息,哪怕和前面一样,也要有一个单独定义函数调用的过程。
- 代码风格与命名
- UseCase 命名: XxxUseCase;Service 函数: getXxx/createXxx;错误以 XxxError 结尾。
- 所有模块使用 TypeScript,避免 any(必要时用最小可行类型)。
- 代码简洁、注释清晰,变量命名语义化,避免缩写与模糊词。
请按上述规范完整输出,不要省略分层与类型。
求点赞,求关注,求收藏,求转发,欢迎各位也看看这个系列其他部分。