Feature Sliced 架构设计
什么是 Feature Sliced?
FSD - Feature Sliced - (功能切片设计) 是一种用于构建前端应用程序的架构方法。可以将其理解为前端项目结构、代码约定和规则的规范。它的目的是让项目在应对不断变化的业务需求时,更加易于理解和组织,便于扩展和维护。
是否适合你的项目?
- 这种架构方法论只适用于前端项目,不适用与后端
- 仅适用于面向用户的应用程序,而不适用与库或者 UI 工具包
- 小项目可能不需要 FSD,并且可能会额外提高项目的复杂性
- 特别复杂的项目,也依然可以应用 FSD 的架构模式
分层结构
在 FSD 架构中,可以分为从下到下三层,分别为 Layers、Slices 以及 Segments。Layers
是顶层目录,它最多六层 (之前是七层),并且是标准化的,但是其中一些是可选的。
在 FSD 中,一个项目由 Layers
组成,Layers
由 Slices
组层, Slices
由 Segments
组成。
Layers
app
: 应用程序开始的地方,这里定义AppProvider
、Routers
、全局样式、全局类型声明等,是应用程序的入口。pages
: 页面入口,将entities(实体)
、features(特征)
和widgets(小部件)
组合起来的完整页面,对应着应用路由,这里组合成的东西提供给app
使用widgets
: 小部件组合层,并非 UI Compoennts,将entities
和features
组合成有意义的业务模块,例如 用户信息卡片、问题列表、用户设置等features
: 与用户有交互的模块,可以为用户带来价值的行为,例如发送评论、加入购物车、搜索等,该层可根据项目具体需求选择entities
: 业务实体层,不同实体的定义目录,例如用户、产品、订单等,也是可选层shared
: 共享模块,与项目、业务的具体内容分离,可重复使用的功能,例如组件库、工具库、API 等
[!TIP] Layers 并非是固定不变的,对于不复杂的项目或者极其复杂的项目来说,可以根据项目自身需求动态的减少或者增加 Layers
分层结构是 FSD
的一个关键特点:entities
不能使用来自 features
的功能,因为 fetures
在层次结构中层级更高,层级由高到低的顺序如图所示: 层级低的Layer
不能引用层级高的Layer
,例如:
可以在 pages
目录中使用 features
或者 shared
中的模块
但是无法在 entities
中引用 features
。
pages
pages
对应着应用程序的路由结构,在 FSD 架构中,pages
之间不可以互相引用,一个页面不能从另外一个页面目录中导入代码
shared
共享层与其他层不同在于它不包含Slices
,而是直接由 Segment
组成
UI
: 基础组件,例如不包含业务逻辑的Buttom
、Input
、Modal
组件等API
: 封装的fetch
请求方法,用来与后端 API 交互Config
:环境变量、应用程序配置等Il8n
:多语言配置 (如果有的话)Router
:路由配置 (如果有的话)Constant
:业务常量- …
Slices
Slices
(切片) 由 Segment
(段) 组成,主要目的是按照业务领域代码进行分区,通过将逻辑相关的模块放在一起,让代码更容易导航,切片不能在同一层上使用其他切片,这有助于实现 高内聚性 和 低耦合性。
切片中连接的并不是抽象的逻辑实物,而是具体的业务实体 (entities)
Slices
有一些需要注意的事项:
- 同一 Layer 的 Slices 不能相互引用
- 在大多数情况下,应该避免在 Slices 中嵌套,并且只使用文件夹进行结构分组
一个较为完整的 Slices 示例:
Segment
Segment
是微小的模块,目的在于帮助切片内的代码分离,最常见的 Segment
有 UI
、Model(store、actions)
、Api
和 Lib
,同时还可以根据需要省略一些或者添加其他模块
- ui: 只包含 UI 视图,不包含业务逻辑
- lib: 基础工具库
- config:配置
- api:api 请求的逻辑,或者 api 实例
统一导出索引、公共 API
每个 pages
、slices
都应该有一个统一的对外导出索引,一般情况下为当前目录下的 index
文件,在这个文件下统一进行模块的导出,与其他模块进行引用交互。而对于没有 slices
的 shared
层,则不需要统一导出索引,只需要为每个模块的定义单独的导出。
└── src
├── app
├── entities
│ ├── account
│ │ └── index.ts
│ ├── post
│ │ └── index.ts
│ ├── product
│ │ └── index.ts
│ └── user
│ └── index.ts
├── features
│ ├── auth-login
│ │ └── index.ts
│ ├── create-account
│ │ └── index.ts
│ └── payment
│ └── index.ts
├── pages
│ ├── about
│ │ └── index.ts
│ ├── cart
│ │ └── index.ts
│ ├── home
│ │ └── index.ts
│ ├── posts
│ │ └── index.ts
│ └── user
│ └── index.ts
├── shared
│ ├── api
│ │ └── index.ts
│ ├── config
│ │ └── index.ts
│ └── ui
│ └── index.tsx
└── widgets
├── cart-box
├── footer
└── header
常规架构
在许多语言和框架中,大家都习惯将类似功能的代码放在一起,例如
├── src
│ ├── actions
│ ├── common
│ ├── components
│ ├── controller
│ ├── pages
│ ├── services
│ └── utils
└
相信大家很熟悉这种目录结构了,常规架构简单易懂,一眼就能看出来某个模块是用来做什么的。 但是明显的缺点就是模块之间的隐式连接和模块混乱,使得项目会变得难以维护,随着时间的推移,常规架构的缺点就会变得越来越明显,项目发展的越久,架构就会越来越难以梳理和维护。
- 有时候不清楚一个应该把功能模块放在模块还是组件中
- 在另一个模块中使用其他模块时遇到困难
- 存储业务实体的问题
FSD 主要解决的问题
Feature-SLiced 架构的主要目的在于实现 高内聚性 和 低耦合性 , 它根据代码的作用以及对项目的贡献来决定它们的位置。 在 FSD 中,较低的层一般是属于比较抽象的,它们可以在较高的层中重复、多次的使用,这样就实现了 抽象和多态 因为高层可以重复使用低层,所以也实现了 继承
通过公共 API index
,统一模块对外的入口,限制对 Slice 和 Segment 内部的访问,实现了 封装
所以可以发现,FSD 通过约定的方式,就已经在结构层面实现了 多态、封装、继承 和 抽象的概念,这些概念确保了代码的隔离、可重用性和多功能性。
FSD 通过其概念和标准,可以避免常规架构存在的问题,但是需要前期的了解
优点
统一化
- 代码按照影响范围(Layers)、领域(Slices)、和 段(Segment)进行组织,一个标准化的架构 逻辑的受控重用
- 每个组件模块都有其目的和可预测的依赖关系
- 便于尊徐 DRY 原则和适应可能性之间保持平衡 功能变化和重构时的稳定性 特定层上的模块不能使用同一层上的其他模块,也不能使用上面的层,可以进行单独的、不影响其他模块的修改,从而避免产生意外的后果。 业务和用户需求导向 当应用程序被分割成业务领域时,通过浏览代码就可以发现和深入的了解项目的所有功能。
缺点
- 与常规架构相比,有一定的上手成本
- 需要团队统一规范和遵守
- 不太适合 MVP 项目或者小的项目,长期来看并没有多大的好处,还需额外付出
- 出现问题需要理科解决,而不能等到“以后”
Feature-Sliced 架构并非适用与所有的前端项目,尤其当前大多数框架、库都提供了自己的官方标准模板,例如 NextJS 和 Sveltekit 等更是对目录结构的要求有一些限制。 同时,FSD 架构的应用还需要考虑到项目、团队等诸多要素,所以 FSD 也许并不是最适合你的项目大,但是它的概念以及一些规范,则非常适合去学习和借鉴并应用到自己的项目中。
示例
TODO:占个坑,等应用了之后再来补充。