跳到主要内容

项目结构(React)

架构背后的方法论

  • Composability 可组合性: 在设计上注重可组合性可以让你在不同组件间尽可能复用代码, 这也是 React 中一个很强大的概念, 并且这也是为什么现在将代码分拆和组合复用是个普遍实践的原因

  • Local-first 本地优先: 在开发组件时, 最好将只和该组件有关的代码放到这个组件目录下.authentication 功能组件包含一个 auth-form 组件和一些功能函数. 不同于将这些代码分拆到 components 或者 util 目录的做法, 我们会把这些文件就放到本目录下, 因为这些代码不会被其他组件用到, 这会防止 components 这样的目录随着项目迭代变得不合常理的巨大。

  • Flatter is better 越平越好: 每一次你为目录增加一个层级你其实都在为开发者理解代码增加心智负担, 并且这样做还有其他坏处, 所以尽量避免太多目录层级.

  • Nest as you grow 适时加层: 平展的目录是好的, 但是增加层级也有一定的好处, 比如当你的功能组件目录变得太大时, 你可能会想要按照某种逻辑将目录组织起来

各个目录的功能

  • components/
    • 包含用于组建 Feature 和 Page 组件的可复用组件
    • 这些组件经常是纯粹的 UI 组件, 不包含副作用(译者注: 也就是常说的 Dumb Component)
  • constants/
    • 包含复用或者不可变的字符串, 比如 URL 和正则表达式
    • 一个单独的 index 文件对于大多数项目就够了
  • contexts/
    • 包含可以为多层组件提供数据的可复用 context
    • 如果项目用了 Redux 这个目录可能不需要
  • features/
    • 包含可复用的功能(业务)组件. 一个 Feature 组件是一种为了实现某个功能聚合在单一目录下的组件(受 Redux 启发的概念). 一个 Feature 组件经常由其他组件组成, 有可能是本地的也有可能是共享组件, 也会用到各种本地或者共享的资源(utils,types,hooks 等等)
    • Feature 组件通常包含副作用
    • 如果项目有用到 Redux, 并且这个组件会和 Store 发生交互, Feature 组件通常会包含一个 slice 文件用于描述这个组件所需要的 Redux Store 中的部分.
  • layouts/
    • 包括可复用的 Layout 组件, 一个 Layout 组件描述着页面的布局, 这类组件经常会包括 app-bar, app-footer, app-side-nav
    • 如果你的项目只有一种布局的话, 这个目录就不是很必要了, Layout 组件可以放在 components 目录下就可以
  • hooks/
    • 包含可复用的 hooks
  • pages/
    • 包含页面组件, 一个页面组件会和一个路由相关联
    • 页面组件通过引入功能组件和普通组件构成了一个页面的内容
    • 一个页面组件也不应该包含太多的副作用, 它应当把副作用委托给功能组件
  • services/
    • 包含用于和 API 交互的复用代码, 通常以 hooks 形式出现, 并且理想情况下可以用到数据缓存工具例如 React Query 或者 RTK Query.
    • 不要把这个和 Angular 风格的 service 搞混, Angular 式的 service 是用于给组件注入功能的. React 框架中 context 和 hooks 是用来干这个事情的. 这个目录应该只包含调用 API 的逻辑
    • 根据 RTK Query 的建议, 最好将 API 定义放置在一个中心目录下. 这是唯一打破本地优先的特例, 我倾向于将 API 放置在各个功能模块下. 事实上比较常见的操作是将 API 放到 features/api 下而不是用一个 services 目录整体保存
  • styles/
    • 可复用或者全局样式
    • 也会包括一些配置, 样式 resets 和变量.
  • types/
    • 可复用的类型文件
    • 我也会把我的 zod schemas 放到 types 目录下(Zod 是一款用于进行数据校验的工具库)
  • utils/
    • 可复用的工具函数
    • 这些函数应该都是纯函数, 不产生任何副作用
  • store.ts
    • 如果使用 redux, 这个文件用来引入各个功能的 slice, 并且在这个文件中设置 Redux store.

额外建议(仅供参考)

  • 每次开启一个新项目时都阅读官方文档, 你永远不知道在你上一次使用过一个库后这个库增加了哪些新工具

  • 停止使用驼峰命名文件, 而是使用短-横线命名方法

  • 使用一个服务端状态工具比如 React Query 或者 Redux Toolkit Query

  • 使用 UI 组件库(MUI)

  • 常用 hooks 场景使用 hooks 库(usehooks-ts)

  • 对于这篇文章, 可以着重参考的是其设计目录背后的原则(可组合性/本地优先), 然后结合作者给的例子和读者所在业务的实际情况, 设计出合适自身的目录结构才是比较好的实践.

  • Flatter is better 和 nest as you grow 是看似相对矛盾的两条原则, 而在实际设计中如何合理的设置目录层级是一门艺术, 如果文件不多的时候尽可能的将文件置于同一层级是方便开发的(容易找到文件),当你一旦加入层级, 开发者就要面临“我上哪找一个文件?”, “这个目录是干啥的?”又或者“我这个文件应该放哪?”这样的问题; 但是项目经过一段时间的迭代, 某一个目录确实有可能会有比较多的文件, 这时根据一定的逻辑(业务模块/功能类型)去组织目录就变得很有必要了, 尤以 utils 类目录较容易发生这种问题

  • 作者将组件分成了三种层次, Pages(页面)-Feature(业务)-Components(普通), 在目前的实践中, 没有对 Feature Component 单独设置目录, 而是将部分业务模块(或者叫区块)放在 Pages 目录下, 或者放到 Components 目录下, 后续可以按照文章的逻辑对 Pages、Features、Components 做更准确和收敛的定位, 将有副作用的组件尽量放到 Features 里

  • Services 目录只放 API 和团队现有的实践也不太一样, 如果我们依赖了某些第三方 SDK, 也会将相关的代码放在这里(或者 utils), 不全是 API 逻辑.

  • React-query 或者 RTK Query 可以尝试使用一下