一、概述
Remesh 是一款基于 CQRS(命令查询职责分离)模式 的 前端领域驱动设计(DDD)框架,核心用于解决大型前端应用的状态管理混乱、业务逻辑与视图耦合严重 的问题
该框架在 Vue 和 React 生态中均可使用,核心差异点如下:
- Vue 需使用 2.7 及以上版本 (支持 Composition API ),且需通过
remesh-vue进行专属适配改造 - React 生态可直接集成 remesh 核心包,无需额外适配
- 两者的核心思想、Domain 层设计逻辑完全一致,仅 View 层与框架的交互 Hooks 有细微差异
Remesh 的核心价值是将 "业务逻辑" 与 "视图展示" 彻底解耦,实现大型应用的可维护性、可扩展性和团队协作效率提升
二、核心概念
Remesh 的核心架构采用 "分层设计" ,核心分为 Domain 层(业务核心层) 和 View 层(视图展示层) ,两层通过框架提供的专属 API 进行交互,互不渗透
2.1 Domain 层:业务逻辑的容器
Domain 层是 Remesh 的核心,负责封装所有领域相关的状态、查询、命令和异步操作 ,是业务逻辑的集中管理中心,不与任何视图层代码耦合
1. 状态定义(State)
用于定义领域内的核心数据状态 ,是整个 Domain 层的数据基础,支持设置默认值,且状态修改仅能通过 "命令" 触发,不允许直接修改
示例场景:分页信息、用户列表、筛选条件等
2. 查询定义(Query)
用于从 "状态" 中获取数据,或基于已有状态进行派生计算 ,仅支持 "只读操作" ,不允许修改任何状态
示例场景:获取用户列表、判断列表是否为空、计算下一页分页参数等
3. 命令定义(Command)
用于定义修改状态的 "操作指令" ,是唯一能修改 State 的入口,支持同步修改状态,也可触发异步操作
示例场景:设置分页信息、重置用户列表、触发加载更多数据等。
4. 异步模块(AsyncModule)
框架封装的异步操作处理工具 ,自动管理异步请求的完整生命周期(默认 → 加载中 → 成功 → 失败 → 已取消),无需手动维护 loading、error 等状态
核心配置:
load:封装异步请求逻辑(如接口调用)onSuccess:请求成功后的回调,通常用于触发命令更新状态- 自动暴露异步状态查询和相关事件,供 View 层订阅
5. 副作用处理(Effect)
用于定义 Domain 层的副作用逻辑,在 Domain 初始化时自动执行,通常用于触发初始化异步请求(如页面加载时自动获取数据)
2.2 View 层:UI 展示的载体
View 层专注于 UI 渲染和用户交互 ,不包含任何业务逻辑,通过 Remesh 提供的专属 Hooks 与 Domain 层进行交互,实现 "数据驱动视图"
1. 核心 Hooks(交互入口)
useRemeshSend:获取 "命令发送器 ",用于触发 Domain 层定义的命令(如点击按钮触发加载更多)useRemeshDomain:获取 Domain 实例,用于关联对应的业务领域useRemeshQuery:订阅 Domain 层的查询结果,查询结果变化时自动更新 UIuseRemeshEvent:订阅 Domain 层的事件(如异步请求成功 / 失败事件),实现无耦合的逻辑回调
2. 异步状态监听
通过 useRemeshQuery 订阅 AsyncDataQuery,可直接获取异步请求的 5 种互斥状态,无需手动管理:
default:初始默认状态(未触发任何异步请求)loading:异步请求加载中(可渲染加载动画)success:异步请求成功(可渲染数据列表)failed:异步请求失败(可渲染错误提示)cancelled:异步请求已取消(可重置视图状态)
3. 事件订阅解耦
通过 useRemeshEvent 订阅 Domain 层暴露的事件,可在 View 层处理 "与 UI 相关的回调逻辑"(如成功提示、错误弹窗),避免业务逻辑与 UI 逻辑耦合
三、实践分析
3.1 优势
1. 职责边界清晰,代码结构规整
- 命令(修改状态)与查询(读取数据)分离,视图层可直观看到所有可用操作
- Domain 层封装所有业务逻辑,View 层专注 UI 渲染,提升代码可维护性
- 团队协作时可统一代码规范,降低沟通成本和交接成本
2. 自动化状态管理,减少冗余代码
AsyncModule自动封装异步请求的 loading、success、failed 等状态,无需手动定义和维护- 状态修改仅通过命令触发,避免非法修改,保证数据一致性
- 派生数据通过 Query 定义,缓存计算结果,提升性能
3. 高可扩展性,适配大型应用迭代
- 领域可拆分、可组合,支持复杂应用的模块化拆分(如用户领域、订单领域)
- 业务逻辑变更仅需修改 Domain 层,不影响 View 层,降低迭代风险
- 支持事件订阅机制,实现跨领域、跨视图的无耦合通信
4. 高可测试性,便于单元测试落地
- Domain 层无视图依赖,可单独进行单元测试
- 命令和查询都是纯函数(或接近纯函数),测试用例易编写、易执行
3.2 劣势
1. 异步事件处理繁琐,代码量增加
- 实际项目中,异步请求通常需要配套 UI 提示(如成功弹窗、错误提示)
- 每个异步请求都需要通过
useRemeshEvent订阅事件,再编写回调逻辑,增加大量模板代码 - 事件订阅机制存在 "逻辑跳跃性",不如同步代码直观,排查问题时效率较低
2. 复杂交互逻辑处理困难,可读性下降
- 实际项目中,单个用户操作(如提交表单)可能包含多个串行 / 并行请求、参数处理、条件判断、UI 交互
- 采用 Remesh 框架需要将这些逻辑拆分到 "命令、异步模块、事件" 中,代码分散,可读性下降
- 新手难以理解逻辑流转路径,后期维护成本较高
3. 学习成本高,上手门槛高
- 要求开发者掌握 DDD、CQRS 等核心概念,对前端开发者的架构能力要求较高
- 框架有专属的 API 规范和代码组织方式,需要一定时间的学习和适应
- 小型项目中容易出现 "过度设计",增加开发成本和项目复杂度
4. 生态相对薄弱,问题排查难度大
- 相比 Vuex、Redux 等主流状态管理框架,Remesh 的社区资源和解决方案较少
- 遇到框架相关问题时,难以快速找到有效解决方案
- 与部分第三方 UI 框架、工具库的集成兼容性有待提升
3.3 应用场景
1. 适用场景
- 大型前端应用(如企业后台、电商平台、SAAS 系统):需要清晰的状态管理和业务逻辑组织
- 复杂业务逻辑场景:需要将业务逻辑从视图层抽离,实现精细化的状态控制
- 多人协作项目:需要统一的代码规范和组织方式,降低团队协作成本
- 长期迭代项目:需要高可维护性、高可扩展性,支撑项目的长期演进
- 对测试要求较高的项目:需要实现单元测试落地,保证代码质量
2. 不适用场景
小型简单应用(如个人博客、静态页面、简单 CRUD 工具):过度设计,增加开发成本
快速迭代项目(如创业项目、短期原型开发):学习成本和开发成本较高,影响项目进度
同步交互频繁的场景(如小游戏、实时编辑工具):事件机制会增加逻辑复杂度,影响性能和体验
团队技术栈薄弱的项目:团队对 DDD、CQRS 概念理解不足,难以发挥框架优势,反而增加项目风险
四、代码示例说明
4.1 前置准备
补充代码示例中缺失的类型定义和模拟接口,保证代码的完整性和可参考性
typescript
// 定义分页类型
export interface Pagination {
currentPage: number;
pageSize: number;
}
// 定义用户类型
export interface User {
id: number;
name: string;
age: number;
email: string;
}
// 定义用户列表类型
export type UserList = User[];
// 模拟用户列表接口请求
export const getUserList = async (pagination: Pagination): Promise<UserList> => {
// 模拟接口延迟
await new Promise(resolve => setTimeout(resolve, 1000));
// 模拟返回数据
return Array.from({ length: pagination.pageSize }, (_, index) => ({
id: (pagination.currentPage - 1) * pagination.pageSize + index + 1,
name: `用户${(pagination.currentPage - 1) * pagination.pageSize + index + 1}`,
age: 20 + Math.floor(Math.random() * 10),
email: `user${(pagination.currentPage - 1) * pagination.pageSize + index + 1}@example.com`
}));
};
4.2 Domain 层代码示例
typescript
import { Remesh, AsyncModule } from 'remesh';
import { Pagination, UserList, getUserList } from './types';
// 1. 创建 Domain 实例
const UserDomain = Remesh.domain({
name: 'UserDomain'
});
// 2. 定义默认分页配置
const defaultPagination: Pagination = {
currentPage: 1,
pageSize: 10,
};
// 3. 定义领域状态
// 分页状态
const PaginationState = UserDomain.state({
name: 'PaginationState',
default: defaultPagination,
});
// 用户列表状态
const UserListState = UserDomain.state({
name: 'UserListState',
default: [] as UserList,
});
// 4. 定义查询逻辑(只读,派生数据)
// 获取用户列表
const UserListQuery = UserDomain.query({
name: 'UserListQuery',
impl: ({ get }) => {
return get(UserListState());
},
});
// 判断用户列表是否为空
const IsEmptyUserListQuery = UserDomain.query({
name: 'IsEmptyUserListQuery',
impl: ({ get }) => {
const userList = get(UserListQuery());
return userList.length === 0;
},
});
// 计算下一页分页信息
const NextPaginationQuery = UserDomain.query({
name: 'NextPaginationQuery',
impl: ({ get }) => {
const pagination = get(PaginationState());
const userList = get(UserListQuery());
// 列表为空时,保持当前分页
if (userList.length === 0) {
return pagination;
}
// 列表有数据时,页码+1
return {
...pagination,
currentPage: pagination.currentPage + 1,
};
},
});
// 5. 定义命令逻辑(唯一修改状态的入口)
// 设置分页信息
const SetPaginationCommand = UserDomain.command({
name: 'SetPaginationCommand',
impl: ({}, pagination: Pagination) => {
return PaginationState().new(pagination);
},
});
// 设置用户列表信息
const SetUserListCommand = UserDomain.command({
name: 'SetUserListCommand',
impl: ({}, userList: UserList) => {
return UserListState().new(userList);
},
});
// 6. 定义异步模块(处理接口请求,自动管理异步状态)
const UserAsyncModule = AsyncModule(UserDomain, {
name: 'UserAsyncModule',
// 封装异步请求逻辑
load: async ({}, pagination: Pagination) => {
return await getUserList(pagination);
},
// 请求成功后的回调:更新分页和用户列表
onSuccess: ({ get }, result: UserList) => {
const nextPagination = get(NextPaginationQuery());
const currentUserList = get(UserListState());
// 返回需要执行的命令列表(支持批量执行)
return [
SetPaginationCommand(nextPagination),
SetUserListCommand([...currentUserList, ...result])
];
},
});
// 7. 定义副作用(Domain 初始化时自动执行,加载初始数据)
UserDomain.effect({
name: 'UserInitEffect',
impl: ({}) => {
return [UserAsyncModule.command.LoadCommand(defaultPagination)];
},
});
// 8. 定义业务相关命令(组合已有命令/异步模块)
// 加载更多用户数据
const LoadMoreCommand = UserDomain.command({
name: 'LoadMoreCommand',
impl: ({ get }) => {
const nextPagination = get(NextPaginationQuery());
return UserAsyncModule.command.LoadCommand(nextPagination);
},
});
// 重置用户列表(恢复默认分页,清空列表,重新加载数据)
const ResetCommand = UserDomain.command({
name: 'ResetCommand',
impl: ({}) => {
return [
SetPaginationCommand(defaultPagination),
SetUserListCommand([]),
LoadMoreCommand()
];
},
});
// 9. 导出 Domain 对外暴露的能力(供 View 层使用)
export const UserDomainExports = {
query: {
UserListQuery,
IsEmptyUserListQuery,
AsyncDataQuery: UserAsyncModule.query.AsyncDataQuery,
},
command: {
LoadMoreCommand,
ResetCommand,
},
event: {
LoadingUsersEvent: UserAsyncModule.event.LoadingEvent,
SuccessToLoadUsersEvent: UserAsyncModule.event.SuccessEvent,
FailedToLoadUsersEvent: UserAsyncModule.event.FailedEvent,
},
domain: UserDomain,
};
4.3 View 层代码示例(React 版本)
typescript
import { useRemeshSend, useRemeshDomain, useRemeshQuery, useRemeshEvent } from 'remesh';
import { UserDomainExports } from './UserDomain';
const UserListPage = () => {
// 1. 获取 Domain 实例和命令发送器
const userDomain = useRemeshDomain(UserDomainExports.domain);
const send = useRemeshSend();
// 2. 订阅 Domain 层的查询结果
const userList = useRemeshQuery(userDomain.query.UserListQuery());
const isEmptyList = useRemeshQuery(userDomain.query.IsEmptyUserListQuery());
const asyncData = useRemeshQuery(userDomain.query.AsyncDataQuery());
// 3. 订阅异步事件(处理 UI 提示逻辑)
useRemeshEvent(userDomain.event.SuccessToLoadUsersEvent, () => {
// 模拟成功提示
console.log('用户数据加载成功!');
});
useRemeshEvent(userDomain.event.FailedToLoadUsersEvent, (error) => {
// 模拟错误提示
console.error('用户数据加载失败:', error);
});
// 4. 判断异步状态
const isLoading = UserAsyncModule.AsyncData.isLoading(asyncData);
// 5. 定义用户交互逻辑(触发 Domain 层命令)
const handleReset = () => {
send(userDomain.command.ResetCommand());
};
const handleLoadMore = () => {
if (isLoading) return;
send(userDomain.command.LoadMoreCommand());
};
// 6. 渲染 UI
return (
<div style={{ padding: '20px' }}>
<h2>用户列表</h2>
<button onClick={handleReset} style={{ marginBottom: '20px' }}>
重置列表
</button>
{isEmptyList && !isLoading && <p>暂无用户数据</p>}
<ul style={{ listStyle: 'none', padding: 0 }}>
{userList.map((user) => (
<li key={user.id} style={{ padding: '10px', borderBottom: '1px solid #eee' }}>
<p>姓名:{user.name}</p>
<p>年龄:{user.age}</p>
<p>邮箱:{user.email}</p>
</li>
))}
</ul>
{isLoading && <p>加载中...</p>}
{!isLoading && !isEmptyList && (
<button onClick={handleLoadMore} style={{ marginTop: '20px' }}>
加载更多
</button>
)}
</div>
);
};
export default UserListPage;
4.4 代码核心说明
1. Domain 层关键点
- 状态(State )是只读的,仅能通过命令(Command)修改,保证数据一致性
- 查询(Query)基于状态派生,无需重复存储冗余数据,提升性能
- 异步模块(AsyncModule)封装了异步请求的全生命周期,简化异步状态管理
- 副作用(Effect )用于初始化数据,实现 "页面加载即请求数据" 的需求
- 命令支持组合调用,可实现复杂的业务逻辑(如 ResetCommand 组合了三个操作)
2. View 层关键点
- 仅通过 Hooks 与 Domain 层交互,不包含任何业务逻辑
- 订阅查询结果后,数据变化时自动更新 UI ,实现 "数据驱动视图"
- 异步状态直接通过
AsyncData.isLoading等方法判断,无需手动维护 - 事件订阅用于处理 UI 相关的回调逻辑,实现与业务逻辑的解耦
3. 核心流转逻辑
用户操作 → View 层发送命令 → Domain 层执行命令 / 异步模块 → 状态更新 → Query 结果更新 → View 层 UI 自动刷新
五、总结
Remesh 框架为大型前端应用提供了一套完整的 "DDD + CQRS " 解决方案,通过 "Domain 层与 View 层的彻底分离 ",解决了传统状态管理框架的耦合问题,提升了代码的可维护性、可扩展性和可测试性。
然而,框架也存在明显的局限性,尤其是在处理复杂交互逻辑和快速迭代项目时,会增加学习成本和开发成本,甚至出现 "过度设计" 的问题。
5.1 选型建议
1. 评估项目复杂度
大型复杂应用(如企业后台、电商平台)可优先考虑,小型简单应用建议使用 Vuex、Pinia、Redux 等轻量框架
2. 评估团队能力
确保团队对 DDD、CQRS 等概念有足够的理解,避免后期维护困难
3. 评估项目周期
长期迭代项目适合使用 Remesh,短期快速迭代项目不建议使用
4. 渐进式接入
可先在核心业务模块中试点使用,逐步推广,降低项目风险
5.2 关键点回顾
- Remesh 核心是 "CQRS 模式 + DDD 分层",实现业务逻辑与视图的解耦
- Domain 层由五大核心组成:State、Query、Command、AsyncModule、Effect
- View 层通过专属 Hooks 与 Domain 层交互,专注于 UI 渲染和用户交互
- 适合大型复杂、长期迭代、多人协作的项目,不适合小型简单、快速迭代的项目
- 异步模块自动管理
loading等状态,是框架的核心优势之一,但也带来了事件处理的繁琐性