Skip to content

Commit c3df76c

Browse files
committed
feat: Zustand状态迁移
1 parent eb65365 commit c3df76c

File tree

23 files changed

+357
-229
lines changed

23 files changed

+357
-229
lines changed

docs/Zustand-Migration.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Zustand 状态迁移与示例集成
2+
3+
## 概要
4+
本文件记录将项目中 Redux 逻辑替换为 Zustand(模块化 slice + 组合式 API)的完整变更与使用说明,包含:代码位置、改动说明、使用示例、验证步骤和扩展指南,作为后续功能扩展的参考示例。
5+
6+
---
7+
8+
## 本次变更一览(文件)
9+
- 新增 / 修改:
10+
- `src/store/types.ts` —— 新增状态类型声明
11+
- `src/store/modules/counterSlice.ts` —— 新增 Counter slice
12+
- `src/store/modules/appSlice.ts` —— 新增 App slice(包含 `isSidebarOpen`
13+
- `src/store/index.ts` —— 组合 store(`useStore`)并添加 `persist`/`devtools` 中间件
14+
- `src/pages/zustand/index.tsx` —— 新增演示页面(ZustandDemo)
15+
- `src/pages/layout/proSider/index.jsx` —— 改造:侧边栏切换由全局 `isSidebarOpen` 控制
16+
- `src/pages/layout/index.jsx` —— 移除传入 `collapsed`/`onCollapse` 的调用点
17+
- `src/routers/config/lazyLoad.config.jsx` —— 注册 `ZustandDemo` 懒加载组件
18+
- `src/routers/modules/business.routes.jsx` —— 添加 `/zustand` 路由并配置 `meta.permission`
19+
- `src/config/menu.config.jsx` —— 在主菜单中新增 `Zustand演示` 条目
20+
- `docs/Zustand-Migration.md` —— (本文件)记录说明
21+
- 删除:
22+
- 项目中原始 Redux demo / action / reducer 文件(已删除冗余 `src/actions` / `src/reducers` 中的示例文件)
23+
24+
25+
## 主要改动说明
26+
- 状态组织
27+
- 采用 Slice Pattern:每个功能模块(例如 counter、app)在 `src/store/modules` 下维护自己的 state + actions。所有 slice 在 `src/store/index.ts` 中组合为单个 `useStore` Hook。
28+
- 持久化:通过 `zustand/middleware``persist``isSidebarOpen` 做了示例性持久化(可在 `partialize` 中调整持久字段)。
29+
30+
- 侧边栏(ProSider)改造
31+
- 原来由 `Layout` 组件通过 props `collapsed/onCollapse` 控制的逻辑,已统一改为 `useStore` 中的 `isSidebarOpen` / `toggleSidebar()`。优点:中央化、任何组件可以直接控制侧边栏、保持状态一致。
32+
- 修改位置:`src/pages/layout/proSider/index.jsx``src/pages/layout/index.jsx`
33+
34+
- 路由与页面
35+
- 新增演示页面 `src/pages/zustand/index.tsx`,作为 Zustand 使用示例。通过 `lazyLoad.config.jsx` 注册后在 `business.routes.jsx` 中加入路由并配置权限(`meta.permission`),在 `menu.config.jsx` 新增菜单项。
36+
37+
- 清理 Redux 代码
38+
- 项目中原有用于示例的 Redux action/reducer 已删除,且已从依赖中卸载 `redux react-redux @reduxjs/toolkit redux-logger`。若项目还有其它 Redux 依赖点,会在构建/运行时暴露引用错误,请按需删除或替换。
39+
40+
41+
## 如何在组件中使用(示例)
42+
- 仅使用需要的字段以避免不必要渲染:
43+
44+
```tsx
45+
// 仅订阅 count
46+
const count = useStore((s) => s.count)
47+
// 仅订阅方法(不会触发渲染)
48+
const increment = useStore((s) => s.increment)
49+
```
50+
51+
- 示例:控制侧边栏(任意组件)
52+
```tsx
53+
const toggle = useStore((s) => s.toggleSidebar)
54+
<button onClick={toggle}>切换侧边栏</button>
55+
```
56+
57+
58+
## 新增 Slice(扩展步骤)
59+
1.`src/store/modules` 新建 `yourSlice.ts`:导出 `createYourSlice: StateCreator<StoreState, [], [], YourSlice>`
60+
2.`src/store/types.ts` 中添加 `YourSlice` 的类型,并将 `StoreState` 组合上新的类型。
61+
3.`src/store/index.ts` 中引入并展开该 slice:
62+
```ts
63+
...createCounterSlice(...a),
64+
...createAppSlice(...a),
65+
...createYourSlice(...a),
66+
```
67+
4. 在页面中直接使用 `useStore((s) => s.yourField)`
68+
69+
70+
## 验证与运行
71+
- 本地开发预览:
72+
```bash
73+
npm run dev
74+
# 或(项目有多个脚本)
75+
npm run dev:vite
76+
```
77+
- 打包测试(库构建):
78+
```bash
79+
npm run build:lib
80+
```
81+
- 推荐检查点:
82+
- 访问 `/zustand` 页面,验证计数器、异步动作、侧边栏切换是否生效。
83+
- 在多个页面切换,确认 `keepAlive`(路由 meta)与组件行为一致。
84+
85+
86+
## 回滚与注意事项
87+
- 如果需回滚到 Redux,保留已删除文件的历史(Git 提交),通过 `git checkout` 恢复。当前改动假设项目不再使用 Redux。
88+
- 注意第三方依赖:某些库或页面可能仍旧通过 `react-redux` 提供的 context 注入数据,迁移需逐步替换对应调用点(`useSelector``useDispatch`)。
89+
90+
91+
## 结语
92+
本次迁移以最小侵入(模块化 slice)方式将示例功能从 Redux 替换为 Zustand,并演示了如何将全局 UI 状态(如侧边栏)统一管理。该结构便于横向扩展(继续加入更多 slice),且组件可以按需订阅状态以获得高性能渲染。
93+
94+
如需我:
95+
- 把 README / contrib 文档中添加迁移说明;
96+
- 运行一次端到端检查(`npm run test:e2e`)并修复发现的问题;
97+
- 或把更多业务模块按同样模式迁移 —— 我可以继续逐步替换并提交 PR。
98+
99+
---
100+
文档位置:`docs/Zustand-Migration.md`
101+
102+
## 权限、角色与 i18n 更新说明
103+
104+
为确保 `Zustand` 示例页面在真实项目中按照角色与本地化规则正确展示,以下是本次针对权限与国际化(i18n)的具体修改与操作说明:
105+
106+
### 代码层面已做的修改
107+
- 路由:`src/routers/modules/business.routes.jsx` 中新增路由条目 `/zustand`,并在 `meta` 中声明 `permission`(示例为 `['admin','manager','dev','user']`)与 `keepAlive: true`
108+
- 懒加载:在 `src/routers/config/lazyLoad.config.jsx` 注册 `ZustandDemo``ZustandDemo: lazyLoad(() => import('@pages/zustand'), { preload: true })`)。
109+
- 菜单:在 `src/config/menu.config.jsx` 中新增菜单项并设置 `i18nKey: 'menu.zustand'`
110+
- 路由权限映射:`src/mock/permission.ts` 中已包含 `/zustand` 对应的 `routePermissionMap``'/zustand': 'zustand:read'`),并在 `mockRoles` 中为部分角色分配了 `zustand:read` 权限(例如 `admin`)。
111+
112+
### 如何为账号分配角色(开发/测试)
113+
项目提供了三种常见的测试/开发方式来给账号分配角色:
114+
115+
1. 使用内置测试账号(推荐快速验证)
116+
- `src/mock/permission.ts` 中定义了 `testAccounts`
117+
- `[email protected]``super_admin`
118+
- `[email protected]``admin`
119+
- `[email protected]``business_user`
120+
- `[email protected]``user`
121+
- 在本地使用这些账号模拟登录(token 为邮箱字符串或 mock 流程),系统会根据 `testAccounts` 返回对应权限集。
122+
123+
2. 临时覆盖(便于调试)
124+
- 在浏览器 console 或代码中设置 `localStorage.user_role`
125+
126+
```js
127+
// 设置为角色 code,示例:'admin' 或 'business_user' 或 'super_admin'
128+
localStorage.setItem('user_role', 'admin')
129+
// 然后刷新,permissionService 会优先使用该覆盖
130+
```
131+
132+
- 这种方式仅用于开发或 demo,不会影响真实后端数据。
133+
134+
3. 真实后端授权(生产环境)
135+
- 若项目对接真实权限 API,请在后端给对应用户分配 `roles` / `permissions`,后端 `permission` 接口会返回完整的 `UserPermission`(包含 `roles``permissions``routes`),`permissionService` 会从该接口读取并缓存。
136+
137+
### 修改 i18n key 的建议与示例
138+
- 菜单项已设置 `i18nKey: 'menu.zustand'``src/config/menu.config.jsx`)。请在项目的 i18n 字典中添加对应条目,例如:
139+
140+
```json
141+
{
142+
"menu": {
143+
"zustand": "Zustand 示例"
144+
}
145+
}
146+
```
147+
148+
- 若需要页面标题国际化,可在路由配置添加 `i18nKey: 'routes.zustand.title'`,并在 i18n 词典中添加 `routes.zustand.title`
149+
150+
### 路由 & 权限生效说明
151+
- 应用启动或登录时,`permissionService.getPermissions()` 会根据:
152+
1. `localStorage.user_role`(手动覆盖)
153+
2. GitHub / token 标识(若启用)
154+
3. 后端权限接口
155+
依次获取并构建 `UserPermission` 对象,包含 `roles``permissions``routes`。路由访问控制通过 `permissionService.canAccessRoute(path)` 实现。
156+
157+
### 在代码中判断权限的示例
158+
在组件或路由守卫中检查角色/权限:
159+
160+
```ts
161+
import { permissionService } from '@src/service/permissionService'
162+
163+
// 检查角色
164+
const ok = await permissionService.hasRole('admin')
165+
166+
// 检查路由访问
167+
const canAccess = await permissionService.canAccessRoute('/zustand')
168+
```
169+
170+
### 文档记录位置与变更说明
171+
- 本节记录了如何为 `Zustand` 页面配置权限(路由 `meta.permission`)、如何给账号分配角色(mock、localStorage 覆盖、后端)以及 i18n key 的使用建议。
172+
- 已在代码中完成的改动请参考上方“本次变更一览(文件)”。
173+
174+
## 操作小结(快速步骤)
175+
1. 添加页面并注册路由:`src/pages/zustand/index.tsx``lazyLoad.config.jsx``business.routes.jsx`(加 `meta.permission`
176+
2.`src/config/menu.config.jsx` 中添加 `i18nKey` 并在本地化文件中添加对应翻译。
177+
3. 本地测试权限:使用 `localStorage.setItem('user_role', '<role_code>')` 或使用测试账号登录。
178+
4. 验证:登录后访问 `/zustand`,或在控制台调用 `permissionService.canAccessRoute('/zustand')`
179+
180+
181+
## 已添加的翻译条目
182+
183+
为方便直接展示菜单项,已在项目的中英文 i18n 词典中添加或确认了 `menu.zustand` 条目:
184+
185+
- 中文(已添加):[src/locales/zh/translation.js](src/locales/zh/translation.js#L1) 中的 `menu.zustand: 'Zustand'`(也可改为 `Zustand 示例` 或其它更友好的文案)。
186+
- 英文(已添加):[src/locales/en/translation.js](src/locales/en/translation.js#L1) 中的 `menu.zustand: 'Zustand'`
187+
188+
如果你希望使用不同的显示文案(例如中文显示为 “Zustand 示例”),请直接在上述文件中修改对应值并重启应用以加载新的语言资源。
189+
190+

package-lock.json

Lines changed: 0 additions & 120 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)