这两年收获了很多,在构建大型项目上面积累了一些经验,虽然已经融入在了日常开发协作中,但依旧觉得有必要记录下来,希望对其他同学形成一些参考。
(可选)Monorepo
One Team, One Repository, One Guide
Monorepo 是一个包含多个不同项目的单一仓库,这不是必须的,但对于我们团队来讲,从中受益良多。
对于开发者来讲,常常会遇到以下实际场景:
维护的多个组件库、配置库或工具库提供给多个业务项目使用
- 由于一个项目对应一个仓库,开发者需要操作多个仓库以及分支
- 需要推动每一个业务接入方升级,更改无法及时扩散到其他项目
与这个问题类似的还有统一改造相关问题,如域名升级、基础库升级等等,一个项目对应一个仓库会导致此类场景很难推进,工作量大大增加。
流程重复建设
发包流水线,Gitlab CI 流水线等等,需要为每一个项目进行单独配置,存在很多重复工作。
通过 Monorepo 可以解决很多此类流程以及规范上的问题,整体团队达到一致与统一,降低沟通损耗,同时随着团队扩大以及项目的增多,模块抽离与复用变得十分容易。
笔者先前有过 Rush 的落地经验,在实践过程中,发现除了最基本的代码共享能力外,还应当至少具备三种能力,即:
- 依赖管理能力。随着依赖数量的增加,依旧能够保持依赖结构的正确性、稳定性以及安装效率。
- 任务编排能力。能够以最大的效率以及正确的顺序执行 Monorepo 内项目的任务(可以狭义理解为 npm scripts,如 build、test 以及 lint 等),且复杂度不会随着 Monorepo 内项目增多而增加。
- 版本发布能力。能够基于改动的项目,结合项目依赖关系,正确地进行版本号变更、CHANGELOG 生成以及项目发布。
如何选择合适自己的 Monorepo 工具链?
- Pnpm Workspace + Changesets:成本低,满足大多数场景
- Pnpm Workspace + Changesets + Turborepo/Lage:在 1 的基础上增强任务编排能力
- Rush:考虑全面,扩展性强
想要了解更多关于 Monorepo 的知识,可以翻阅笔者 Monorepo 系列文章:
🗄️ 项目结构
就近原则:将组件、函数、样式、状态等尽可能地放在其被使用的地方。
除了以类型维度划分组件 components、函数 utils、状态 models 以及 hooks 等模块,业务开发中更多时候应该以功能 feature 为维度组织项目。
feature 是服务于某个业务模块的 components、models 以及 utils 等模块的组合,如果是没有具体业务属性相关的通用模块就放外面。
这是构建大型项目的较佳实践,在可维护性与可读性上取得了较好的效果。
目录结构如下:
.
└── src
├── assets # 通用静态文件
├── components # 通用基础组件
├── config # 项目环境变量
├── features # 项目特性功能
│ ├── {feature1}
│ └── {feature2}
├── hooks # 通用基础 React hooks
├── models # 通用基础 model
├── pages # 项目路由文件夹
├── services # 项目接口请求(一般使用自动化工具生成)
├── types #全局类型文件
└── utils #通用基础工具函数
推荐阅读:
- [React Folder Structure in 5 Steps [2022]](https://www.robinwieruch.de/r...)
- bulletproof-react/project-structure
💅🏻 样式方案
推荐:Tailwind CSS + Styled Components
兼顾开发效率与视觉规范,优先使用 Tailwind CSS 处理样式,内置部门内视觉规范以及通用的样式方案。
遇到 Tailwind CSS 无法覆盖的场景,如一些复杂的伪类或覆盖第三方组件库的样式,请使用 Styled Components。
优点:
- 无需新建文件以及命名,配合自动提示开发效率高
- 工具类与视觉规范对齐,页面还原度高
- css 不会膨胀,打包体积小
缺点:
- 可读性可能较差。请合理进行组件拆分,抽离组件 或
renderXXX()
推荐阅读:
🗃️ 状态管理
推荐:React Query + Unstated Next
服务端状态交由 React Query(或 SWR)管理,客户端状态共享基于 React Context 即可。
对于绝大多数应用程序来说,在将所有的异步代码迁移到 React Query 之后,真正需要全局访问的客户端状态通常是非常少的。
为了保证可读性,你更应该使用 Unstated Next 而非直接使用 React Context。
如果对客户端状态管理能力有着较强诉求,推荐 zustand,和 unstated-next 一样,都是很精简的状态管理工具,但设计简单,使用方便。
推荐阅读:
📡 网络请求
推荐:暂无
除了最基础的封装 axios 以及 fetch 等网络请求库,还有两点需要重点关注:
- 自动生成相关 service 代码
- 接口变更同步
由于笔者使用的是公司内部方案,没有使用过开源方案,所以暂无推荐,只听说过 Pont,有兴趣的同学可以一试。
🌲 分支规范
分支规范需要结合当前团队的迭代节奏合理选择。对于 Monorepo 以及迭代节奏稳定、不过度追求灵活的团队来讲,推荐使用 Trunk Based Development 分支模型。
开发阶段:
- 从 master 拉出 feature 分支进行开发
- 开发完需求中的一个模块后提起 MR 并进行 Code Review 合入 master
- 重复 1 和 2,直到开发完所有模块
测试阶段:
同开发阶段一致
预上线阶段:
- 从 master 分支拉出 release 版本分支进行上线
hotfix:
- 从 release 版本分支拉出 hotfix 分支进行修复上线
- cherry-pick 对应的提交至 master 分支(可通过自动化工具完成)
优点:
- 操作简单
- 方便 code review。
10 lines = 10 issues; 500 lines = looks fine
- 合码越早风险暴露越早,特别是在 monorepo 场景下,越晚合入 master,风险越大,可能别的项目某个依赖的变化导致本项目构建失败
缺点:
- 不够灵活,开发阶段就合入 master,后续难以拆分功能点上线,feature flag 落地较为困难
- master 准入标准较高,需要通过严格的 CI 检测以及 code review
以上是推荐做法,但实际上往往不尽人意,以笔者目前团队为例,同一个业务项目存在了多个产品经理,产品经理的需求之间相互独立,但是会要求在同一天上线,同时希望需求 A 不会因为需求 B delay 而 delay。
- 从 master 拉出 feature 分支开发
- 测试环境测试
- 预发环境测试
- 所有需求合入 master
- 整体回归测试
- 上线
Q:为什么有回归环节?
A: 因为同一天会上线一个项目的多个需求,所以提前合入 master 回归(一般提前一天)这一步是为了避免多个需求同一天上线合码导致的一些 BUG,故而有了这个环节。
Q:为什么预发环境测试完才合入 master?
A: 是为了最大程度避免某个需求出现问题导致整体需求 delay,测完 PPE 基本已经有保障了。
代理工具
推荐:whistle / lightproxy
使用代理工具时,开发者一般会关注以下内容五点内容:
- 静态资源代理
- 登录状态代理
- api 接口代理
- 接口 Mock
- 移动端支持
构建工具内置的 proxy 往往无法全面满足,笔者使用的是公司内部基于 whistle 开发的一个代理软件,开源工具可以试试 whistle 以及 lightproxy。
其他
通过 CI 流水线保证代码质量、自动发包流水线提高发包效率也是很有必要的,但是笔者都是基于 Monorepo 仓库前提进行落地,参考价值可能不大,就不详细展开了。
彩蛋:如何从代码层面提高自己的不可替代性
- 坚持自己造轮子,且不写文档或注释
- 坚持写巨型组件(300 lines+),不合理拆分模块
- 组件内部使用多个 useEffect,或者一个useEffect里包含多个业务逻辑,依赖钩子满天飞,绝不抽离业务 hook
- 见到计算逻辑就使用 useMemo,见到函数就使用 useCallback
- 不好好给变量或函数命名
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。