Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions docs/Zustand-Migration.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Zustand 状态迁移与示例集成

## 概要

本文件记录将项目中 Redux 逻辑替换为 Zustand(模块化 slice + 组合式 API)的完整变更与使用说明,包含:代码位置、改动说明、使用示例、验证步骤和扩展指南,作为后续功能扩展的参考示例。

---

## 本次变更一览(文件)

- 新增 / 修改:
- `src/store/types.ts` —— 新增状态类型声明
- `src/store/modules/counterSlice.ts` —— 新增 Counter slice
Expand All @@ -21,8 +23,8 @@
- 删除:
- 项目中原始 Redux demo / action / reducer 文件(已删除冗余 `src/actions` / `src/reducers` 中的示例文件)


## 主要改动说明

- 状态组织
- 采用 Slice Pattern:每个功能模块(例如 counter、app)在 `src/store/modules` 下维护自己的 state + actions。所有 slice 在 `src/store/index.ts` 中组合为单个 `useStore` Hook。
- 持久化:通过 `zustand/middleware` 的 `persist` 对 `isSidebarOpen` 做了示例性持久化(可在 `partialize` 中调整持久字段)。
Expand All @@ -37,8 +39,8 @@
- 清理 Redux 代码
- 项目中原有用于示例的 Redux action/reducer 已删除,且已从依赖中卸载 `redux react-redux @reduxjs/toolkit redux-logger`。若项目还有其它 Redux 依赖点,会在构建/运行时暴露引用错误,请按需删除或替换。


## 如何在组件中使用(示例)

- 仅使用需要的字段以避免不必要渲染:

```tsx
Expand All @@ -49,67 +51,78 @@ const increment = useStore((s) => s.increment)
```

- 示例:控制侧边栏(任意组件)

```tsx
const toggle = useStore((s) => s.toggleSidebar)
<button onClick={toggle}>切换侧边栏</button>
```


## 新增 Slice(扩展步骤)

1. 在 `src/store/modules` 新建 `yourSlice.ts`:导出 `createYourSlice: StateCreator<StoreState, [], [], YourSlice>`。
2. 在 `src/store/types.ts` 中添加 `YourSlice` 的类型,并将 `StoreState` 组合上新的类型。
3. 在 `src/store/index.ts` 中引入并展开该 slice:

```ts
...createCounterSlice(...a),
...createAppSlice(...a),
...createYourSlice(...a),
```
4. 在页面中直接使用 `useStore((s) => s.yourField)`。

4. 在页面中直接使用 `useStore((s) => s.yourField)`。

## 验证与运行

- 本地开发预览:

```bash
npm run dev
# 或(项目有多个脚本)
npm run dev:vite
```

- 打包测试(库构建):

```bash
npm run build:lib
```

- 推荐检查点:
- 访问 `/zustand` 页面,验证计数器、异步动作、侧边栏切换是否生效。
- 在多个页面切换,确认 `keepAlive`(路由 meta)与组件行为一致。


## 回滚与注意事项

- 如果需回滚到 Redux,保留已删除文件的历史(Git 提交),通过 `git checkout` 恢复。当前改动假设项目不再使用 Redux。
- 注意第三方依赖:某些库或页面可能仍旧通过 `react-redux` 提供的 context 注入数据,迁移需逐步替换对应调用点(`useSelector`、`useDispatch`)。


## 结语

本次迁移以最小侵入(模块化 slice)方式将示例功能从 Redux 替换为 Zustand,并演示了如何将全局 UI 状态(如侧边栏)统一管理。该结构便于横向扩展(继续加入更多 slice),且组件可以按需订阅状态以获得高性能渲染。

如需我:

- 把 README / contrib 文档中添加迁移说明;
- 运行一次端到端检查(`npm run test:e2e`)并修复发现的问题;
- 或把更多业务模块按同样模式迁移 —— 我可以继续逐步替换并提交 PR。

---

文档位置:`docs/Zustand-Migration.md`。

## 权限、角色与 i18n 更新说明

为确保 `Zustand` 示例页面在真实项目中按照角色与本地化规则正确展示,以下是本次针对权限与国际化(i18n)的具体修改与操作说明:

### 代码层面已做的修改

- 路由:`src/routers/modules/business.routes.jsx` 中新增路由条目 `/zustand`,并在 `meta` 中声明 `permission`(示例为 `['admin','manager','dev','user']`)与 `keepAlive: true`。
- 懒加载:在 `src/routers/config/lazyLoad.config.jsx` 注册 `ZustandDemo`(`ZustandDemo: lazyLoad(() => import('@pages/zustand'), { preload: true })`)。
- 菜单:在 `src/config/menu.config.jsx` 中新增菜单项并设置 `i18nKey: 'menu.zustand'`。
- 路由权限映射:`src/mock/permission.ts` 中已包含 `/zustand` 对应的 `routePermissionMap`(`'/zustand': 'zustand:read'`),并在 `mockRoles` 中为部分角色分配了 `zustand:read` 权限(例如 `admin`)。

### 如何为账号分配角色(开发/测试)

项目提供了三种常见的测试/开发方式来给账号分配角色:

1. 使用内置测试账号(推荐快速验证)
Expand All @@ -129,12 +142,13 @@ localStorage.setItem('user_role', 'admin')
// 然后刷新,permissionService 会优先使用该覆盖
```

- 这种方式仅用于开发或 demo,不会影响真实后端数据。
- 这种方式仅用于开发或 demo,不会影响真实后端数据。

3. 真实后端授权(生产环境)
- 若项目对接真实权限 API,请在后端给对应用户分配 `roles` / `permissions`,后端 `permission` 接口会返回完整的 `UserPermission`(包含 `roles`、`permissions`、`routes`),`permissionService` 会从该接口读取并缓存。

### 修改 i18n key 的建议与示例

- 菜单项已设置 `i18nKey: 'menu.zustand'`(`src/config/menu.config.jsx`)。请在项目的 i18n 字典中添加对应条目,例如:

```json
Expand All @@ -148,13 +162,15 @@ localStorage.setItem('user_role', 'admin')
- 若需要页面标题国际化,可在路由配置添加 `i18nKey: 'routes.zustand.title'`,并在 i18n 词典中添加 `routes.zustand.title`。

### 路由 & 权限生效说明

- 应用启动或登录时,`permissionService.getPermissions()` 会根据:
1. `localStorage.user_role`(手动覆盖)
2. GitHub / token 标识(若启用)
3. 后端权限接口
依次获取并构建 `UserPermission` 对象,包含 `roles`、`permissions` 与 `routes`。路由访问控制通过 `permissionService.canAccessRoute(path)` 实现。
依次获取并构建 `UserPermission` 对象,包含 `roles`、`permissions` 与 `routes`。路由访问控制通过 `permissionService.canAccessRoute(path)` 实现。

### 在代码中判断权限的示例

在组件或路由守卫中检查角色/权限:

```ts
Expand All @@ -168,16 +184,17 @@ const canAccess = await permissionService.canAccessRoute('/zustand')
```

### 文档记录位置与变更说明

- 本节记录了如何为 `Zustand` 页面配置权限(路由 `meta.permission`)、如何给账号分配角色(mock、localStorage 覆盖、后端)以及 i18n key 的使用建议。
- 已在代码中完成的改动请参考上方“本次变更一览(文件)”。

## 操作小结(快速步骤)

1. 添加页面并注册路由:`src/pages/zustand/index.tsx` → `lazyLoad.config.jsx` → `business.routes.jsx`(加 `meta.permission`)
2. 在 `src/config/menu.config.jsx` 中添加 `i18nKey` 并在本地化文件中添加对应翻译。
3. 本地测试权限:使用 `localStorage.setItem('user_role', '<role_code>')` 或使用测试账号登录。
4. 验证:登录后访问 `/zustand`,或在控制台调用 `permissionService.canAccessRoute('/zustand')`。


## 已添加的翻译条目

为方便直接展示菜单项,已在项目的中英文 i18n 词典中添加或确认了 `menu.zustand` 条目:
Expand All @@ -186,5 +203,3 @@ const canAccess = await permissionService.canAccessRoute('/zustand')
- 英文(已添加):[src/locales/en/translation.js](src/locales/en/translation.js#L1) 中的 `menu.zustand: 'Zustand'`。

如果你希望使用不同的显示文案(例如中文显示为 “Zustand 示例”),请直接在上述文件中修改对应值并重启应用以加载新的语言资源。


Loading
Loading