14 KiB
id, title, created, updated, status, scope, category, confidence, last_verified, promotion_target, projects, tags
| id | title | created | updated | status | scope | category | confidence | last_verified | promotion_target | projects | tags | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 20260803-usercenter-react-directory-convention | React 管理后台按应用组装、路由、业务切片与共享能力分目录 | 2026-08-03 | 2026-08-03 | candidate | repository | frontend-architecture | high | 2026-08-03 | none |
|
|
React 管理后台按应用组装、路由、业务切片与共享能力分目录
Trigger
在 usercenter-react 中新增、生成或移动 React 页面、业务模块、路由、请求、表单、表格或跨业务组件,需要判断文件应放在 src/app、src/routes、src/features、src/pages、src/shared 还是 src/styles;或者发现业务代码开始散落在顶层 pages、通用 components 和请求工具中。
Context
这个项目的目录结构不是按 React 文件类型全局分组,而是先区分应用组装、路由、业务切片和横向共享能力,再在每个业务切片内按 API、组件、页面、Schema 和资源规格分层。这样既让单个业务模块的改动保持局部化,也让路由、权限、表格、表单、认证和国际化等公共契约有明确归属,并允许资源校验器检查生成式 CRUD 模块的完整性。
当前约定的核心结构是:
src/
├── app/ # 应用组装:Providers、QueryClient、Router 实例、Route Catalog
├── routes/ # 手写路由树、路由元数据类型与路由副作用
├── features/<resource>/ # 业务垂直切片
│ ├── api/
│ ├── components/
│ ├── pages/
│ ├── schemas/
│ └── specs/
├── pages/ # 非资源型系统页、设置页和可运行示例页
├── shared/ # 跨业务复用的技术能力与业务无关 UI 组合
├── styles/ # 全局 reset、tokens 和全局样式入口
└── main.tsx # 浏览器入口,只负责全局样式/运行时初始化和挂载 App
状态与请求管理遵循“服务端状态和客户端 UI 状态分离”的技术栈:
@tanstack/react-query ^5.90.12管理服务端状态,包括查询缓存、loading/error 状态、mutation、缓存更新与失效。全局QueryClient位于src/app/query-client.ts,具体 query 和 mutation hooks 位于各 feature 的api/目录。zustand ^5.0.9管理不来自服务端的全局 UI 状态。当前src/shared/config/ui-store.ts负责主题、语言、侧边栏折叠和命令面板开关,并按需要同步到localStorage或 i18next。- 请求链保持
page/component -> TanStack Query hook -> typed feature API adapter -> shared request helper -> native fetch。JSON、二进制下载和 multipart 上传分别通过requestJson、requestBlob、requestMultipart进入统一传输边界,页面和组件不直接发送 HTTP 请求。
Evidence
- 2026-08-03:检查
usercenter-react当前项目文档、src目录、导入关系、资源校验器和 CodeGraph 索引;中央知识库搜索“React 目录 / feature module / src/features / module boundary”未发现同根因条目。 usercenter-react/AGENTS.md:18-26:明确应用代码只放在src,路由使用手写 TanStack Router 路由树和 Route Catalog,服务端状态使用 TanStack Query 与 feature API adapter,共享 CRUD 模式放在共享层,运行时 AI 与根目录specs/、skills/分离。usercenter-react/specs/feature-module.spec.md:3-15:明确业务模块位于src/features/<resource>,标准子目录为api/、components/、pages/、schemas/、specs/,表格、表单、权限、认证、i18n 和布局等共享能力位于 feature 外部。usercenter-react/specs/api.spec.md:3-14:定义page/component -> query or mutation hook -> typed feature API -> mock or real adapter数据访问链,并禁止组件直接访问fetch、axios或 mock store。usercenter-react/specs/route.spec.md:3-11、src/app/router.tsx、src/routes/route-tree.tsx:路由实例和路由树分离;路由树负责懒加载src/pages的系统页与src/features/*/pages的业务页,元数据不放进页面组件。usercenter-react/src/main.tsx:1-20:入口仅加载 Semi React 19 适配、全局样式、i18n,并挂载src/app/app.tsx,没有承载业务逻辑。usercenter-react/src/features/products:完整 CRUD 样例按 API、组件、页面、Schema 和资源规格垂直聚合;文件名使用.api.ts、.query.ts、.mutation.ts、.types.ts、.schema.ts、-page.tsx、-table.tsx等职责后缀。usercenter-react/src/features/market-management:在标准目录之外按业务需要增加policies/和routes/,说明标准结构是稳定基线,不是禁止扩展的封闭清单。usercenter-react/src/shared:按api、auth、config、form、i18n、layout、list-templates、menu、permission、table、utils等横向能力组织,不按具体资源命名。usercenter-react/package.json:22,35:服务端状态依赖为@tanstack/react-query ^5.90.12,客户端状态依赖为zustand ^5.0.9。usercenter-react/src/app/query-client.ts与src/app/providers.tsx:应用集中创建并注入 QueryClient;默认 query 配置包含staleTime: 30_000、retry: 1和refetchOnWindowFocus: false。usercenter-react/src/features/products/api/products.query.ts、products.mutation.ts:feature 使用useQuery、useMutation和useQueryClient管理读取、提交、详情缓存更新与列表缓存失效。usercenter-react/src/shared/config/ui-store.ts:使用 Zustand 集中管理主题、语言、侧边栏折叠和命令面板状态;这些状态不进入 TanStack Query 缓存。usercenter-react/src/shared/api/json-request.ts、binary-request.ts:共享请求层基于原生fetch封装 JSON、Blob 和 multipart 请求;feature API adapter 使用这些请求 helper,而不是让页面或组件直接访问传输层。usercenter-react/tsconfig.json:18-21与vite.config.ts:@/*统一映射到src/*,源码使用稳定的根路径导入,避免跨目录相对路径漂移。usercenter-react/packages/registry/src/resource-module-verifier.mjs:37-65:生成式资源模块校验器会定位src/features/<module>,并检查 API adapter、类型、mock、query/mutation hooks、Schema、表格、表单、详情组件、CRUD 页面和资源规格的文件职责与命名。- 2026-08-03 运行
pnpm validate:resources:products.resource.jsonSchema 校验和src/features/products模块结构验证均通过。 - 2026-08-03 运行
codegraph status:索引覆盖 131 个文件、1491 个节点和 3631 条边,但存在 1 个 pending modified file;因此目录结论以当前源码和规范直接检查为准,没有把未同步图谱当成唯一证据。
Root cause
已验证:项目同时服务人工开发和 AI 生成,需要让业务模块的输入规格、类型、数据访问、UI、路由入口和验证规则可发现、可组合、可检查。若按全局 components/、hooks/、services/ 平铺,单一资源会跨多个顶层目录分散,资源校验器也难以围绕 src/features/<resource> 做完整性验证。
已验证:应用组装和路由元数据具有全局生命周期,资源业务代码具有按功能演进的生命周期,共享表格、表单、权限、认证、i18n 和布局具有跨功能演进的生命周期。按变化原因划分目录,比仅按文件技术类型划分更符合当前代码和规范。
已验证:远程数据与本地 UI 状态拥有不同生命周期。服务端状态需要请求去重、缓存、失效和 mutation 协调,项目由 TanStack Query 负责;主题、语言和界面开关只在浏览器内变化,项目由 Zustand 负责。请求传输细节则下沉到共享 fetch helper 和 feature API adapter,不进入组件。
推断:把“业务垂直切片 + 横向共享能力 + 集中应用/路由组装”作为新增代码的默认落点,可以减少跨目录修改、重复抽象和 AI 生成时的放置歧义;但是否需要新增 feature 子目录仍应由真实职责决定。
Preferred action
- 运行时代码统一放在
src;根目录specs/、skills/、packages/registry等属于 AI Harness、规范或工具链,不要导入浏览器运行时。 - 保持
src/main.tsx薄:只做全局样式和运行时初始化、DOM 根节点检查及<App />挂载。Providers、认证启动边界、QueryClient、Router 实例和 Route Catalog 放在src/app。 - 手写路由树、路由元数据类型和路由副作用放在
src/routes;路由组件可以懒加载 feature page 或顶层 system/example page,但不要把路由元数据散落到页面组件。 - 资源或业务能力默认建立
src/features/<resource>垂直切片。完整单资源 CRUD 使用api/、components/、pages/、schemas/、specs/;只有出现明确职责时再增加policies/、routes/等子目录,不提前创建空的通用层。 - feature 内按职责命名文件:数据边界用
*.api.ts、*.types.ts、*.query.ts、*.mutation.ts、必要时*.mock.ts和*.serializers.ts;校验与 URL search shape 用*.schema.ts;页面用*-page.tsx;复杂表格拆成*-table.tsx、*-table-columns.tsx、*-table-toolbar.tsx和*-row-actions.tsx。 - 服务端状态使用 TanStack Query:query/mutation hooks 放在 feature 的
api/目录,query keys 使用共享工厂集中管理,mutation 成功后显式更新或失效相关缓存。不要把远程数据复制进 Zustand。 - 全局客户端 UI 状态使用 Zustand:只存放主题、语言、界面开关等浏览器状态。局部组件状态继续使用 React 本地 state,不因使用 Zustand 而集中所有交互状态。
- feature API adapter 负责端点、参数、响应和业务类型;共享
requestJson、requestBlob、requestMultipart负责 URL、请求选项和统一传输行为。页面和组件只调用 query/mutation hooks,不直接调用fetch。 - 非资源型的 dashboard、403/404、外观设置和模板演示页放在
src/pages。一旦页面属于具体业务资源,应放回对应 feature 的pages/,不要把业务页长期堆在顶层pages。 - 跨业务复用且不拥有具体资源语义的能力放在
src/shared,按能力域继续分目录。共享表格、表单和列表模板只拥有布局与交互壳;筛选状态、导航、API 调用和 mutation 保留在 feature。 - 全局 reset、token 和应用级样式入口放在
src/styles;只服务单一组件的 CSS 可与组件同目录。UI 原语直接使用 Semi Design,只有出现重复的项目工作流时才建立项目自有共享抽象。 - 源码跨目录导入使用
@/别名。判断归属时先问“谁拥有这项业务语义、它与谁一起变化”,再决定目录,不以“它是组件/Hook/工具”作为唯一依据。 - 新增或生成完整资源模块后运行
pnpm validate:resources;涉及共享 API、类型或跨目录影响时,再按项目规则使用 CodeGraph、源码搜索、类型检查和针对性测试核对影响范围。
Boundaries
- 这是
usercenter-react的仓库级规范,不是所有 React 项目的通用目录标准;其他仓库需要先读取其路由、状态管理、生成器和模块边界约定。 api/components/pages/schemas/specs是完整单资源 CRUD 的标准结构,不要求轻量 feature 为了形式完整而创建所有目录。current-user只拥有 API 与 Schema,market-management还拥有policies和 feature-localroutes,都是当前结构允许的变体。- 顶层
src/pages适合无独立资源归属的系统页和示例页,不代表所有路由页面都应放在那里。 src/shared的目标是跨业务复用和资源语义中立,但当前shared/auth/auth-context.tsx会组合features/current-user。这说明仓库尚未建立可由 lint 强制的绝对无环分层;不要把本记录扩大成“shared 永远不能引用 feature”的新规则,除非另行设计并验证迁移方案。- 空目录或历史遗留目录不是规范证据;优先以
AGENTS.md、specs/、当前有效导入关系和可执行校验器为准。 - TanStack Query 只负责远程/服务端状态,不用来保存纯界面开关;Zustand 只负责客户端状态,不作为请求缓存或远程数据副本。
- 局部、短生命周期且只由一个组件树消费的交互状态不必放入 Zustand;优先保留为 React 本地 state。
pnpm validate:resources只验证带 Resource Spec 的生成式资源模块,不能替代整个应用的类型检查、lint、构建或业务测试。
Failed approaches
- 按全局
components/、hooks/、services/平铺所有业务代码:同一资源的修改会散落到多个顶层目录,资源规格与生成结果也难以成组验证。 - 把可复用性未验证的业务组件提前放入
shared:会把具体资源语义伪装成公共抽象,增加依赖和后续拆分成本。 - 看到
feature-module.spec.md的目录清单就机械创建空目录:轻量 feature 和复杂 feature 的真实职责不同,空层级不会提高可发现性。 - 把请求结果同时存进 TanStack Query 和 Zustand:会产生两个数据源、重复失效逻辑和状态不同步问题。
- 页面组件直接调用
fetch:会绕过 feature API adapter、统一错误处理和 TanStack Query 缓存生命周期。 - 仅凭 CodeGraph 或目录名推断边界:索引可能未同步,目录也可能存在历史空壳;必须回到当前源码、项目规范和校验器核对。
Promotion record
- Not promoted. 当前规范已经由
usercenter-react/AGENTS.md、specs/和资源校验器共同承载;本记录先作为仓库级 candidate,待在另一个 React 管理后台独立复用并验证后,再考虑提炼为跨项目 pattern 或目录审计脚本。