
feature-sliced-design
官方 Feature-Sliced Design (FSD) v2.1 技能,用于将方法论应用于前端项目。当任务涉及使用 FSD 层组织项目结构、决定代码归属、放置静态资源(图片、图标、字体、PDF)、分组紧密相关的切片、定义公共 API 和导入边界、解决交叉导入或评估 @x 模式、决定是否创建或移除实体、评估是否需要 entities 层、决定逻辑应保持本地还是提取、从 FSD v2.0 或非 FSD 代码库迁移、将 FSD 与框架(Next.js App Router 和 Pages Router、Nuxt、Vite、Astro)集成,或在 FSD 内实现常见模式(如认证、API 处理、Redux 和 TanStack Query(React Query))时使用。
Official Feature-Sliced Design (FSD) v2.1 skill for applying the methodology to frontend projects. Use when the task involves organizing project structure with FSD layers, deciding where code belongs, placing static assets (images, icons, fonts, PDFs), grouping closely related slices, defining public APIs and import boundaries, resolving cross-imports or evaluating the @x pattern, deciding whether to create or remove an entity, evaluating whether the entities layer is needed at all, deciding whether logic should remain local or be extracted, migrating from FSD v2.0 or a non-FSD codebase, integrating FSD with frameworks (Next.js App Router and Pages Router, Nuxt, Vite, Astro), or implementing common patterns such as authentication, API handling, Redux, and TanStack Query (React Query) within FSD.
Feature-Sliced Design (FSD) v2.1
来源: fsd.how | 严格程度可根据项目规模和团队上下文调整。
1. 核心理念与层概述
FSD v2.1 核心原则:"从简单开始,需要时再提取。"
首先将代码放在 pages/ 中。跨页面的重复是可接受的,并不自动要求提取到更低层。仅当相同代码当前在多个地方使用(而非假设)、这些使用并非总是一起变化、且边界具有明确职责时,才进行提取。
并非所有层都是必需的。 大多数项目只需 shared/、pages/ 和 app/ 即可开始。仅在提供明确价值时才添加 widgets/、features/、entities/。不要"以防万一"创建空层文件夹。
FSD 使用 6 个标准化层,按从高到低列出:
app/ → 应用初始化、提供者、路由
pages/ → 路由级组合,拥有自己的逻辑
widgets/ → 跨多个页面复用的大型复合 UI 块
features/ → 可复用的用户交互(仅在 2 个以上地方使用时)
entities/ → 可复用的业务领域模型(仅在 2 个以上地方使用时)
shared/ → 无业务逻辑的基础设施(UI 套件、工具函数、API 客户端)
导入规则:模块只能从严格低于它的层导入。同层切片之间的交叉导入禁止。
// ✅ 允许
import { Button } from "@/shared/ui/Button"; // features → shared
import { useUser } from "@/entities/user"; // pages → entities
// ❌ 违规
import { loginUser } from "@/features/auth"; // entities → features
import { likePost } from "@/features/like-post"; // features → features
注意:processes/ 层在 v2.1 中已弃用。迁移详情请阅读 references/migration-guide.md。
2. 决策框架
编写新代码时,遵循以下决策树:
步骤 1:这段代码在哪里使用?
- 仅在一个页面中使用 → 保留在该
pages/切片中。 - 在 2 个以上页面中使用但重复可管理 → 在每个页面中保留独立副本也是有效的。
- 一个实体或功能仅在一个页面中使用 → 保留在该页面中(Steiger:
insignificant-slice)。
步骤 2:它是可复用的无业务逻辑的基础设施吗?
- UI 组件 →
shared/ui/ - 工具函数 →
shared/lib/ - API 客户端、路由常量 →
shared/api/或shared/config/ - 认证令牌、会话管理 →
shared/auth/ - CRUD 操作 →
shared/api/
步骤 3:它是一个当前在多个地方使用、边界稳定的完整用户操作吗?
- 是 →
features/ - 不确定、单次使用或推测性复用 → 保留在页面中。
步骤 4:它是一个当前在多个地方使用、边界稳定的业务领域模型吗?
- 是 →
entities/ - 不确定、单次使用或推测性复用 → 保留在页面中。
步骤 5:它是应用级配置吗?
- 全局提供者、路由器、主题 →
app/
黄金法则:有疑问时,保留在 pages/ 中。仅当相同代码当前在多个地方使用且边界清晰时才提取。
3. 快速放置表
| 场景 | 单次使用 | 确认的多处使用 |
|---|---|---|
| 用户资料表单 | pages/profile/ui/ProfileForm.tsx |
features/profile-form/ |
| 产品卡片 | pages/products/ui/ProductCard.tsx |
entities/product/ui/ProductCard.tsx |
| 产品数据获取 | pages/product-detail/api/fetch-product.ts |
entities/product/api/ |
| 认证令牌/会话 | shared/auth/(始终) |
shared/auth/(始终) |
| 认证登录表单 | pages/login/ui/LoginForm.tsx |
features/auth/ |
| CRUD 操作 | shared/api/(始终) |
shared/api/(始终) |
| 通用卡片布局 | shared/ui/Card/ |
|
| 模态框管理器 | shared/ui/modal-manager/ |
|
| 模态框内容 | pages/[page]/ui/SomeModal.tsx |
|
| 日期格式化工具函数 | shared/lib/format-date.ts |
4. 架构规则(必须遵守)
这些规则是 FSD 的基础。违反会削弱架构。如果必须打破规则,请确保是有意的设计决策,并在代码中记录原因(注释或 ADR)。
4-1. 仅从较低层导入
app → pages → widgets → features → entities → shared。
禁止向上导入以及同层切片之间的交叉导入。
4-2. 公共 API:每个切片通过 index.ts 导出
外部消费者只能从切片的 index.ts 导入。禁止直接导入内部文件。
// ✅ 正确
import { LoginForm } from "@/features/auth";
// ❌ 违规:绕过公共 API
import { LoginForm } from "@/features/auth/ui/LoginForm";
Shared 层:Shared 没有切片。按段定义独立的公共 API(shared/ui/index.ts、shared/api/index.ts 等),而不是一个顶层的 shared/index.ts。这使从 Shared 的导入按意图组织。
环境特定的公共 API
切片通常应通过单个 index.ts 暴露其公共 API。不建议临时定制。
如果单个 index.ts 无法保持运行时边界,可添加环境特定的入口点,如 index.server.ts。参见 references/framework-integration.md。
4-3. 同层切片之间禁止交叉导入
如果同层两个切片需要共享逻辑,请遵循第 7 节中的解决顺序。不要创建直接导入。
4-4. 基于领域的文件命名(禁止去分割)
根据文件所代表的业务领域命名,而非其技术角色。技术角色名称如 types.ts、utils.ts、helpers.ts 会将不相关的领域混合在单个文件中,降低内聚性。
// ❌ 技术角色命名
model/types.ts ← 哪些类型?用户?订单?混合?
model/utils.ts
// ✅ 基于领域命名
model/user.ts ← 用户类型 + 相关逻辑
model/order.ts ← 订单类型 + 相关逻辑
api/fetch-profile.ts ← 明确用途
4-5. shared/ 中禁止业务逻辑
Shared 仅包含基础设施:UI 套件、工具函数、API 客户端设置、路由常量、资源。业务计算、领域规则和工作流属于 entities/ 或更高层。
// ❌ 业务逻辑在 shared 中
// shared/lib/userHelpers.ts
export const calculateUserReputation = (user) => { ... };
// ✅ 移动到所属领域
// entities/user/lib/reputation.ts
export const calculateUserReputation = (user) => { ... };
5. 建议(应该遵守)
5-1. 页面优先:将代码放在使用的地方
首先将代码放在 pages/ 中。仅在真正需要时提取到较低层。提取是影响整个项目的设计决策,因此门槛应较高。
保留在页面中的内容:
- 仅在一个页面中使用的大型 UI 块
- 页面特定的表单、验证、数据获取、状态管理
- 页面特定的业务逻辑和 API 集成
- 看似可复用但保持本地更简单的代码
演进模式:开始时将所有内容放在 pages/profile/ 中。当相同用户数据被另一个页面使用时(非假设),将共享模型提取到 entities/user/。将页面特定的 API 调用和 UI 保留在页面中。
5-2. 对实体保持保守
实体层高度可访问(几乎所有其他层都可以从中导入),因此变更传播广泛。
- 从没有实体开始。
shared/+pages/+app/是有效的 FSD。瘦客户端应用很少需要实体。 - 不要过早拆分切片。 将代码保留在页面中。仅当相同代码当前被多个消费者使用且边界稳定时,才提取到实体。
- 业务逻辑并不自动需要实体。 将类型保留在
shared/api中,逻辑保留在当前切片的model/段中可能就足够了。 - 将 CRUD 放在
shared/api/中。 CRUD 是基础设施,不是实体。 - 将认证数据放在
shared/auth/或shared/api/中。 令牌和登录 DTO 依赖于认证上下文,很少在认证之外复用。
关于保持实体层清洁的详细指导(何时完全跳过它、如何隔离业务上下文、为什么 CRUD 属于 shared/api),请参见 references/excessive-entities.md。
5-3. 从最小层开始
// ✅ 有效的最小 FSD 项目
src/
app/ ← 提供者、路由
pages/ ← 所有页面级代码
shared/ ← UI 套件、工具函数、API 客户端
// 仅当实际用例需要时才添加层:
// + widgets/ ← 当前跨多个页面复用的 UI 块
// + features/ ← 当前跨多个页面复用的用户交互
// + entities/ ← 当前跨页面或功能复用的领域模型
5-4. 使用 Steiger 检查器验证
Steiger 是官方 FSD 检查器。关键规则:
insignificant-slice:建议将仅被一个页面使用的实体/功能合并到该页面。excessive-slicing:建议在层中切片过多时进行合并或分组。
npm install -D @feature-sliced/steiger
npx steiger src
6. 反模式(避免)
- 不要过早创建实体。 仅在一个地方使用的数据结构应属于该地方。
- 不要将 CRUD 放在实体中。 使用
shared/api/。仅对复杂的事务逻辑考虑实体。 - 不要仅为认证数据创建
user实体。 令牌和登录 DTO 属于shared/auth/或shared/api/。 - 不要滥用
@x。 它是必要的妥协,而非推荐模式。该符号仅用于实体层,且仅在边界合并确实不可能时使用。功能和部件通过策略 A–D 处理交叉导入(见第 7 节)。 - 不要提取单次使用的代码。 仅被一个页面使用的功能或实体应保留在该页面中。
- 不要使用技术角色文件名。 使用基于领域的名称(见规则 4-4)。
- 向实体添加 UI 时要谨慎。 实体 UI 会诱使其他实体进行交叉导入。如果向实体添加 UI 段,只能从更高层(功能、部件、页面)导入,绝不能从其他实体导入。
- 不要创建上帝切片。 职责过于宽泛的切片应拆分为聚焦的切片(例如,将
user-management/拆分为auth/、profile-edit/、password-reset/)。 - 不要创建顶层
assets/段。 将静态资源放在使用它们的代码旁边。参见references/asset-handling.md。
7. 交叉导入解决
交叉导入是代码异味,而非绝对禁止。正确的策略取决于层和情况。
实体层:优先合并边界,@x 是最后手段
实体中的交叉导入通常是由于实体拆分过于细粒度。在考虑 @x 之前,先考虑是否应合并边界。
@x 是必要的妥协,而非推荐方法。仅在边界确实无法合并时使用,并记录原因。过度使用会将实体边界锁定在一起,增加重构成本。
功能和部件:四种策略(A、B、C、D)
在 features 和 widgets 中,根据上下文选择:
- 策略 A:切片合并。 两个切片总是一起变化 → 合并。
- 策略 B:推送到实体。 共享领域逻辑 → 移动到
entities/,将 UI 保留在功能/部件中。 - 策略 C:从上层组合(IoC)。 父级(页面或应用)导入两个切片并通过渲染属性、插槽或 DI 连接它们。
- 策略 D:公共 API 访问。 当复用确实不可避免时,仅通过切片的
index.ts允许。绝不要深入到model/、store/或内部文件。
@x 符号仅用于实体层。功能和部件使用上述策略 A–D。
严格程度取决于项目上下文
交叉导入通常是最好避免的依赖关系,但有时会被有意使用。严格程度因项目上下文而异:
- 早期产品 大量实验:允许一些交叉导入可能是务实的速度权衡。
- 长期或受监管系统(金融科技、大规模服务):更严格的边界在可维护性和稳定性方面更值得。
如果引入了交叉导入,将其视为有意选择,并在代码中记录理由(解释为什么其他策略不适用的注释)。
有关每种策略的详细代码示例,请阅读 references/cross-import-patterns.md。
8. 段与结构规则
标准段
段按技术目的对切片内的代码进行分组:
ui/:UI 组件、样式、显示相关代码model/:数据模型、状态存储、业务逻辑、验证api/:后端集成、请求函数、API 特定类型lib/:该切片的内部工具函数config/:配置、功能开关
层结构规则
- App 和 Shared:无切片,直接按段组织。这些层内的段可以相互导入。
- Pages、Widgets、Features、Entities:先有切片,然后每个切片内部有段。
- 切片组(可选):组文件夹可以包含同一层上的相关切片,仅用于导航目的。组没有段,也没有公共 API。详情参见
references/layer-structure.md。
段内文件命名
始终使用基于领域的名称,描述代码是关于什么的:
model/user.ts ← 用户类型 + 逻辑 + 存储
model/order.ts ← 订单类型 + 逻辑 + 存储
api/fetch-profile.ts ← 个人资料获取
api/update-settings.ts ← 设置更新
如果某个段只有一个领域关注点,文件名可以与切片名匹配(例如,features/auth/model/auth.ts)。
9. Shared 层指南
Shared 包含无业务逻辑的基础设施。它仅按段组织(无切片)。Shared 内的段可以相互导入。
Shared 中允许的内容:
ui/:UI 套件(Button、Input、Modal、Card)lib/:工具函数(formatDate、debounce、classnames)api/:API 客户端、路由常量、CRUD 助手、基础类型auth/:认证令牌、登录工具函数、会话管理config/:环境变量、应用设置assets/:跨应用共享的品牌资源(谨慎使用;参见references/asset-handling.md)
Shared 可以包含应用感知代码(路由常量、API 端点、品牌资源、公共类型)。它绝不能包含业务逻辑、功能特定代码或实体特定代码。
10. 快速参考
- 导入方向:
app → pages → widgets → features → entities → shared - 最小 FSD:
app/+pages/+shared/ - 何时创建实体:当相同的业务领域模型当前跨多个页面、功能或部件使用,且边界稳定时。
- 何时创建功能:当相同的用户交互当前跨多个页面或部件使用,且边界稳定时。
- 打破规则:仅作为有意的设计选择。在代码中记录原因(注释或 ADR)。
- 交叉导入解决(实体):首先合并边界;
@x是必要的妥协,不推荐。 - 交叉导入解决(功能/部件):策略 A(合并)、B(推送到实体)、C(从上层组合)或 D(公共 API)。
@x符号仅用于实体。 - 文件命名:基于领域(
user.ts、order.ts)。绝不要使用技术角色(types.ts、utils.ts)。 - 资源放置:放在使用它们的代码旁边;复用内容放入
shared/ui/;全局样式表和字体放入app/。 - 切片组:大型层的可选导航辅助;组文件夹没有段和公共 API。
- Processes 层:已弃用。参见
references/migration-guide.md。
11. 条件参考
仅在特定情况适用时阅读以下参考文件。不要预加载所有参考。
-
当创建、审查或重组 FSD 层和切片的文件夹和文件结构,包括将紧密相关的切片分组到父文件夹以进行导航(例如,“设置项目结构”、“此文件夹放在哪里”、“如何对这些支付实体进行分组”):
→ 阅读references/layer-structure.md -
当解决同层切片之间的交叉导入问题、评估
@x模式、为功能和部件选择策略 A/B/C/D,或决定是否应合并边界时:
→ 阅读references/cross-import-patterns.md -
当决定是否创建或移除实体、处理过多实体、评估是否完全跳过实体层、放置 CRUD 操作、决定认证数据归属,或隔离业务上下文以避免
@x链时:
→ 阅读references/excessive-entities.md -
当决定将静态资源(图片、图标、字体、PDF、样式表)放置在单个切片、跨切片共享或全局时:
→ 阅读references/asset-handling.md -
当从 FSD v2.0 迁移到 v2.1、将非 FSD 代码库转换为 FSD,或弃用 processes 层时:
→ 阅读references/migration-guide.md -
当将 FSD 与特定框架集成(Next.js App Router 或 Pages Router、Nuxt、Vite、CRA、Astro)以将路由连接到 FSD 页面、放置中间件/仪表文件、构建 API 路由处理程序或配置路径别名时:
→ 阅读references/framework-integration.md -
当在 FSD 结构内实现具体代码模式,如认证、API 请求处理、类型定义或状态管理(Redux、TanStack Query / React Query,包括查询工厂、无限滚动、Suspense 模式和
useMutationState)时:
→ 阅读references/practical-examples.md
注意:如果在此对话中已加载layer-structure.md,请避免同时加载此文件。先处理结构,然后在后续步骤中根据需要加载模式。





