关于 FDD-architecture
面向 React + Vite 项目的 FDD 架构技能,按业务能力组织代码,将组件、Hooks、类型和测试集中在独立功能模块中。提供模块边界、导入规范、命名规则及共享代码管理指导。
主要功能
- 01按业务能力组织功能模块
- 02通过 index.ts 定义模块公共 API
- 03规定模块内外导入方式
- 04文件与目录命名规范
- 05共享代码提升规则
适用场景
- 01为 React + Vite 项目规划目录结构
- 02创建或重构功能模块
- 03审查跨模块导入和公共 API 边界
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jenishshrestha/ ai-skills --skill fdd-architecture安装后在 Claude Code 中输入/fdd-architecture确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.jsonevals/evals.jsonreferences/migration.mdrules/named-imports.mdnaming-consistency.mdlocality-co-location.mdlocality-depth.mdintra-feature-imports.mdhook-extraction.mdapi-boundary.mdshared-component-organization.mdshared-global-move.md详细介绍
React + Vite 的特性驱动架构(FDD)
按业务能力而非技术类型组织代码。每个特性都是一个独立模块,包含自己的组件、Hook、类型和测试。
项目结构
src/
├── features/ # Feature modules (business capabilities)
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── lib/
│ │ ├── types/
│ │ ├── index.ts # Public API
│ │ └── auth.test.tsx
│ └── dashboard/
│ └── ...
├── shared/ # Shared infrastructure (3+ feature rule)
│ ├── components/
│ │ ├── ui/ # Primitive components (Button, Input)
│ │ ├── layouts/ # Layout wrappers (Header, Sidebar)
│ │ └── providers/ # Global providers (ThemeProvider)
│ ├── hooks/
│ ├── lib/
│ └── types/
└── app/ # App entry and routing
├── routes/
└── main.tsx
React 质量原则
这些原则适用于本项目中的所有代码,而不仅仅是架构。
KISS — 保持简单
优先使用标准的 React 模式,而不是巧妙的抽象。如果逻辑难以理解,需要靠注释才能看懂,就把它简化。能用 useState 解决时,不要使用复杂的状态管理。
YAGNI — 你不会需要它
不要添加“以防万一”的属性、推测性的抽象或过早优化。只针对当前需求构建。如果某个属性目前没有使用者,就移除它。
组合优于配置
当一个组件累积了 10+ 个布尔属性时,就意味着应该使用复合组件模式,将其拆分为可组合的部分:
// ❌ Configuration overload
<Card showHeader showFooter isCollapsible hasBorder variant="outlined" />
// ✅ Composition
<Card>
<Card.Header>Title</Card.Header>
<Card.Content>Body</Card.Content>
<Card.Footer>Actions</Card.Footer>
</Card>
明确且可预测
避免在 useEffect 中隐藏副作用。优先使用明确的事件处理器和稳定的回调引用。当某件事发生时,读者应该能够追溯原因,而不必费力查找 effect 的依赖数组。
// ❌ Hidden side-effect — runs on every query change
useEffect(() => {
trackAnalytics("search", { query });
}, [query]);
// ✅ Explicit — fires on user action
const handleSearch = (query: string) => {
setQuery(query);
trackAnalytics("search", { query });
};
无障碍(a11y)
每个交互元素都需要支持键盘操作、具备适当的 ARIA 属性,并显示焦点指示器。使用语义化 HTML 元素(button、nav、main),而不是带有 role 属性的通用 div。
FDD 规则 — 快速参考
1. 特性内聚性(关键)
所有特性代码都放在 src/features/[feature-name]/ 中。嵌套最多不超过 3 层。
src/features/user-profile/
├── components/
│ ├── profile-card.tsx
│ └── avatar-upload.tsx
├── hooks/
│ └── use-profile.ts
├── lib/
│ └── format-name.ts
├── types/
│ └── profile.ts
├── index.ts
└── user-profile.test.tsx
2. 公共 API 边界(高)
每个特性都通过 index.ts 导出。绝不要从特性的内部模块导入。
// src/features/auth/index.ts
export { LoginForm } from "./components/login-form";
export { useAuth } from "./hooks/use-auth";
export type { AuthUser, AuthState } from "./types";
// ❌ Importing from internals
import { LoginButton } from "@/features/auth/components/login-button";
// ✅ Importing from public API
import { LoginButton } from "@/features/auth";
3. 导入约定(高)
特性内部 — 使用相对路径:
import { useAuth } from "../hooks/use-auth";
特性之间 — 通过公共 API 使用别名:
import { useAuth } from "@/features/auth";
类型 — 始终使用 import type:
import type { User } from "./types";
4. 命名约定(中)
| 类型 | 约定 | 示例 |
|---|---|---|
组件文件(.tsx) | PascalCase | ProductCard.tsx、DataTable.tsx |
Hook 文件(.ts) | camelCase,以 use 为前缀 | useDataTable.ts、useUserProfile.ts |
| 类型文件 | kebab-case + .types.ts | product.types.ts、user-profile.types.ts |
| Schema 文件 | kebab-case + .schema.ts | auth.schema.ts、product.schema.ts |
| 文件夹 | kebab-case | user-profile/、data-table/ |
| 工具函数 / lib / api / config | kebab-case | fetch-users.ts、utils.ts |
仅使用命名导入,不使用 import *。详情和特殊情况请参阅 rules/naming-consistency.md。
5. 共享基础设施(中)
三次原则 — 只有在 3+ 个特性使用后,才将其提升到 src/shared/。
- 1 个特性 → 保留在特性文件夹中
- 2 个特性 → 保留在原处;如果逻辑可能出现差异,也可以复制
- 3+ 个特性 → 移至
src/shared/
6. 提取 Hook
出现以下情况时,从组件中提取业务逻辑:
- 组件包含 5+ 行非渲染逻辑
- 逻辑可在特性内部复用
- 需要独立测试逻辑
// ❌ Logic mixed with UI
function ProfileCard() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/user').then(res => res.json()).then(setUser);
}, []);
return <div>{user?.name}</div>;
}
// ✅ Logic extracted to hook
function ProfileCard() {
const { user, loading } = useUserProfile();
return <div>{user?.name}</div>;
}
深入了解规则
如需详细说明、示例和边缘情况,请阅读 rules/ 中的相关规则文件。只加载你需要的内容:
| 规则文件 | 适用场景 |
|---|---|
locality-co-location.md | 创建新功能、确定文件的归属位置 |
locality-depth.md | 功能目录层级过深,需要重构 |
api-boundary.md | 设置 index.ts 导出、审查跨功能导入 |
intra-feature-imports.md | 决定使用相对导入路径还是绝对导入路径 |
naming-consistency.md | 为新文件或文件夹命名 |
named-imports.md | 导入风格问题、设置 barrel 文件 |
shared-global-move.md | 决定是否将代码提升到 shared |
shared-component-organization.md | 组织 src/shared/components/ 子目录 |
hook-extraction.md | 从复杂组件中提取逻辑 |
工具
Vite 路径别名(vite.config.ts)
export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@/features": path.resolve(__dirname, "./src/features"),
"@/shared": path.resolve(__dirname, "./src/shared"),
},
},
});
TypeScript 路径(tsconfig.json)
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/features/*": ["./src/features/*"],
"@/shared/*": ["./src/shared/*"]
}
}
}
ESLint — 强制执行公共 API 边界
{
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["@/features/*/components/*", "@/features/*/hooks/*"],
"message": "Import from feature's public API (index.ts) only"
}
]
}
]
}
}
基于功能的代码拆分(TanStack Router)
const dashboardRoute = createRoute({
path: "/dashboard",
component: () => import("@/features/dashboard").then((m) => m.DashboardPage),
});
有关迁移指南(扁平结构 → FDD、Next.js → Vite),请参阅 references/migration.md。