feature-sliced-design

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))时使用。

65Star
0Fork
更新于 2026/7/11
SKILL.md
只读
名称
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))时使用。

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.tsshared/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.tsutils.tshelpers.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. 对实体保持保守

实体层高度可访问(几乎所有其他层都可以从中导入),因此变更传播广泛。

  1. 从没有实体开始。 shared/ + pages/ + app/ 是有效的 FSD。瘦客户端应用很少需要实体。
  2. 不要过早拆分切片。 将代码保留在页面中。仅当相同代码当前被多个消费者使用且边界稳定时,才提取到实体。
  3. 业务逻辑并不自动需要实体。 将类型保留在 shared/api 中,逻辑保留在当前切片的 model/ 段中可能就足够了。
  4. 将 CRUD 放在 shared/api/ 中。 CRUD 是基础设施,不是实体。
  5. 将认证数据放在 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)

featureswidgets 中,根据上下文选择:

  • 策略 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
  • 最小 FSDapp/ + pages/ + shared/
  • 何时创建实体:当相同的业务领域模型当前跨多个页面、功能或部件使用,且边界稳定时。
  • 何时创建功能:当相同的用户交互当前跨多个页面或部件使用,且边界稳定时。
  • 打破规则:仅作为有意的设计选择。在代码中记录原因(注释或 ADR)。
  • 交叉导入解决(实体):首先合并边界;@x 是必要的妥协,不推荐。
  • 交叉导入解决(功能/部件):策略 A(合并)、B(推送到实体)、C(从上层组合)或 D(公共 API)。@x 符号仅用于实体。
  • 文件命名:基于领域(user.tsorder.ts)。绝不要使用技术角色(types.tsutils.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,请避免同时加载此文件。先处理结构,然后在后续步骤中根据需要加载模式。