Angular 项目做久了你会发现一个规律技术债不是哪一天突然爆发的而是每个版本迭代时顺手“打个补丁”攒出来的。尤其是团队超过五个人以后代码风格各写各的、目录结构随心所欲、PR 评审靠口头沟通这些问题会像滚雪球一样越滚越大。这篇内容是我在多个 Angular 项目里落地工程化实践的经验总结围绕代码规范、项目结构和团队协作三条主线展开既讲清楚“为什么这么做”也给出可以直接抄走的配置和流程。核心内容适合谁如果你正在带一个 Angular 团队或者你作为核心开发需要推动前端规范化落地又或者你刚接手一个代码已经乱成一团的老项目这篇文章都能帮上忙。我尽量少说空话多写实操。1. 代码规范先统一认知再统一代码代码规范这个东西最忌讳的就是“为规范而规范”。如果一个规范不能解决实际问题那它就是个摆设。我见过很多团队直接拷贝大厂的 ESLint 配置结果开发同学每天被几百条报错淹没最后全员eslint-disable场面极其难看。1.1 为什么代码规范是团队的第一生产力聊规范之前先算一笔账Code Review 时如果评审人把一半的时间花在“这里该用单引号还是双引号”“这个变量名能不能再短一点”这类问题上那真正该关注的业务逻辑和架构设计反而没时间看了。代码规范真正要解决的是两件事降低认知负荷统一风格后任何人打开任何文件都不会觉得陌生切入成本大幅降低。减少无效讨论格式问题交给机器去吵人只讨论“这样设计对不对”。据我观察规范执行得好的团队新人融入速度至少快一倍。原因很简单新人不依赖别人反复提醒看几个文件就知道怎么写符合要求提交的代码能被自动化检查兜住不会因为低级错误被反复打回。1.2 工具链选型ESLint Stylelint PrettierAngular 项目的规范工具链我建议直接以 ESLint 为核心angular-eslint作为 Angular 专属规则的来源配套 Stylelint 管样式、Prettier 兜底格式化。三者的关系是ESLint 管“代码对不对”Stylelint 管“样式规不规范”Prettier 管“长得好不好看”。ESLint 的重要性不需要多讲了它是 JavaScript 生态的事实标准。Angular 项目使用 ESLint 时要装angular-eslint/eslint-plugin和angular-eslint/eslint-plugin-template前者负责组件类代码的规则后者负责模板 HTML 的规则。比如模板里禁止any类型、禁止在事件绑定里写复杂表达式这些都是 Angular 特有的问题纯 JS 的 ESLint 规则覆盖不到。Stylelint 我选stylelint-config-standard-scss作为基础配置因为 Angular 项目绝大多数都用了 SCSS。你可以在上面叠加order/properties-order这类排序规则强制属性书写顺序配合编辑器的自动修复写样式的时候完全不用想顺序问题只管写内容。Prettier 的配置有一个容易踩坑的地方printWidth不要为了“少换行”而设成 100 以上。Angular 模板表达式往往很长如果宽度设太大模板会变得非常难读。我一般用默认的 80配合singleQuote: true和trailingComma: all。下面是 Angular 17 项目里我常用的.prettierrc.json配置{ singleQuote: true, trailingComma: all, printWidth: 80, tabWidth: 2, semi: true }然后通过 husky lint-staged在每次 commit 前对暂存区的文件自动执行格式化和检查。这是整套工具链里体验提升最明显的一环开发者不需要记命令不需要主动跑 lint提交代码时一切自动发生。我在package.json里一般这样配{ scripts: { lint: ng lint, format: prettier --write \src/**/*.{ts,html,scss,json}\ }, lint-staged: { *.ts: [ eslint --fix, prettier --write ], *.html: [ eslint --fix, prettier --write ], *.scss: [ stylelint --fix, prettier --write ] } }提示husky 从 v9 开始初始化方式变了直接用husky init生成.husky/pre-commit文件再在里面写npx lint-staged就行。网上很多老教程让你修改package.json里的husky钩子那个写法现在已经不推荐了。1.3 命名规范与组件规约工具只能拦住“格式错误”拦不住“思想错误”。命名规范属于典型的“非机器强制”约定但恰恰是影响代码可读性最大的因素。Angular 项目里我固定用这几条铁律组件类名用PascalCase文件名用kebab-case且文件名的后缀必须体现 Angular 类型.component.ts、.service.ts、.directive.ts、.pipe.ts。组件选择器强制加前缀。你的项目用什么品牌前缀就全员统一。比如app-user-card不允许出现没有前缀的选择器。属性和方法名用camelCase尽量写全语义不用缩写。getUserName()比getUN()好一万倍。私有方法、私有字段统一加private修饰符同时用_前缀区分。虽然 TypeScript 的private只是编译期约束但团队内部约定的_前缀能直接告诉阅读者“这是内部实现外部不要碰”。组件规约方面我强烈建议团队内部制定一个“最小组件清单”文档。内容包含每个组件必须有明确的单一职责。组件输入用Input装饰器输出的数据变更必须走OutputEventEmitter。组件内不允许直接操作全局状态如localStorage、sessionStorage必须通过 Service 中转。组件模板超过 200 行时必须拆子组件。这些约定不靠工具靠团队共识。怎么让共识真正落地靠 Code Review 流程这个后面细说。2. 项目结构目录拆得清楚代码就不容易乱Angular 项目结构没有银弹不是所有的项目都必须长成一个样。但有一条原则是共通的目录结构是给团队协作用的不是给框架炫耀能力的。结构好不好标准只有一个——新人能不能在三分钟内找到要改的文件。2.1 按功能域划分不按技术类型划分很多 Angular 新手项目的结构是这样src/app ├── components ├── services ├── models ├── pipes ├── directives这种按技术类型划分的结构看着整齐但项目一大就崩了。为什么因为功能是横切的。你要改一个用户中心的功能得在components里找组件去services里找对应服务再翻models找类型定义改动一个需求要在四五个目录之间来回跳。这种结构只适合几十个文件的演示项目。我推荐按功能域划分长这样src/app ├── core │ ├── interceptors │ ├── guards │ ├── services │ └── tokens ├── features │ ├── user │ │ ├── user-list │ │ ├── user-detail │ │ ├── user.service.ts │ │ └── user.model.ts │ ├── dashboard │ │ ├── dashboard.component.ts │ │ └── dashboard.routes.ts │ └── settings │ ├── settings.component.ts │ └── settings.routes.ts ├── shared │ ├── components │ ├── directives │ ├── pipes │ └── utils └── app.routes.ts核心变化有两点第一features目录每个子目录都是一个完整的垂直业务切片组件、服务、模型、路由都放一起。第二core和shared严格区分core只放应用级的单例服务拦截器、全局守卫、全局错误处理shared放可复用的无状态组件和工具函数。为什么core和shared要分开因为依赖方向要清晰features可以依赖core和sharedshared不依赖任何业务模块core可以被任何模块引用但core内部不引用业务模块。这其实就是简单的依赖规则遵守了它改动业务模块时不用担心把全局逻辑搞坏。2.2 路由懒加载与模块边界功能域划分配合路由懒加载才能发挥真正的作用。每个features下的功能模块独立成懒加载路由这样主包体积能控制住模块之间的边界也天然清晰起来。Angular 17 以后用 standalone component 写懒加载非常简洁直接在路由配置里用loadComponentexport const routes: Routes [ { path: user, loadComponent: () import(./features/user/user-list/user-list.component).then(m m.UserListComponent) } ];如果是 NgModule 方式则用loadChildrenexport const routes: Routes [ { path: user, loadChildren: () import(./features/user/user.module).then(m m.UserModule) } ];建议定一条铁律app.routes.ts里只保留顶层路由映射任何业务模块都不允许在根路由文件里堆路径。每个功能域的*.routes.ts自己管好自己的子路由这样路由变更不会互相干扰。2.3 shared 目录不是垃圾堆shared是最容易脏的目录。很多团队把“不知道放哪的”文件全丢进 shared最后 shared 变成一个无法维护的大杂烩。我管 shared 的几条经验shared/components只收纯 UI 组件按钮、弹窗、空状态、表格包装器这类与业务无关的组件。带业务含义的组件不要放 shared。比如“用户的头像下拉菜单”带有用户域的业务逻辑放features/user内部。shared/utils只收纯函数日期格式化、金额转换、校验判断这类无副作用的工具函数。任何一个要放 shared 的东西先问一句有没有第二个地方在用没有那放原处。我见过太多团队因为“先放 shared 以后复用”生生把自己的项目结构搞垮了。复用的前提是有人消费没人消费的 shared 代码就是死代码。3. 团队协作规范落地靠机制不靠自觉代码规范和项目结构写得再好如果没有配套的协作机制最多撑一个月就会被打回原形。人都有惰性都会在 deadline 压力下选择“最快路径”。协作机制的价值就是把“遵守规范”变成开发链路里无法跳过的一环。3.1 从 Commit 信息到 Code Review 的全链路约定Commit 信息不是写给 git 看的是写给同事和未来的自己看的。我要求团队统一用 Conventional Commits 规范提交信息格式固定为type(scope): subject。Type用途示例feat新功能feat(user): 增加用户批量导入fix修 bugfix(order): 修复订单金额精度丢失refactor重构不改变行为refactor(user-form): 提取公用校验逻辑style格式调整style(button): 调整内边距docs文档变动docs(readme): 补充部署说明test测试相关test(cart): 增加结算流程用例chore构建/工具链chore(deps): 升级 eslint 至 v9Commitlint 可以强行校验格式不让不符合规范的 commit 进代码库。配套的还有lint-staged在 commit 前做检查双管齐下。Code Review 是整套机制的咽喉环节。我的建议是每个 PR 都必须使用统一模板模板里至少包含这几项### 变更目的 这个 PR 要解决什么问题 ### 变更内容 - 列出主要改动点 ### 自测清单 - [ ] 本地开发环境验证通过 - [ ] 相关单元测试通过 - [ ] 已手动触发错误场景验证 ### 测试范围建议 建议 reviewer 重点回归哪些功能同时给团队一份 Review Checklist里面明确列出 reviewer 必须关注的点组件是否过多依赖全局状态、服务是否可复用、路由配置是否正确、内存泄漏风险订阅是否取消等。这样 review 才不至于沦为“哦我看过了没问题”。3.2 自动化 CI 检查把常犯的错误挡在合并之前本地 lint 只能管住开发者自己真正强制统一的是 CI。Angular 项目的 CI 流水线里我认为至少要有这么几道检查代码规范检查ng lint单元测试跑一遍ng test --watchfalse --browsersChromeHeadless生产构建验证ng build --configuration production依赖审查检查是否有已知漏洞的高危依赖如果这三个里面任何一个失败PR 不允许合入。这一点必须在分支保护里强制设置不能靠自觉。额外推荐一个工具nx或analogjs的增量检查。在大型 Angular 代码库中全量 lint 和全量测试会越来越慢CI 等待时间一长开发体验就很糟糕。用增量检查只扫描变更文件能把 CI 时间从十几分钟压到两三分钟。团队协作体验的提升效果非常明显。3.3 架构决策记录ADR与文档沉淀最让人头疼的事情是团队讨论了很久定下来的架构方案三个月后没人记得为什么这么做接着新来的人又提出一个“更好”的方案推翻重来。解决办法是写 ADRArchitecture Decision Record。不需要长篇大论一个决策记录包含几个要点就行背景当时遇到了什么问题决策我们选了哪个方案后果这个方案带来什么收益和代价备选方案我们否决了什么以及为什么比如团队决定“所有组件都使用 standalone API不用 NgModule”这个决策值得写一个 ADR。半年后有人问“为什么不用 NgModule”直接发 ADR 链接不用再开一个小时的会解释。ADR 文件我建议放在仓库根目录的docs/adr/下用 Markdown 编写文件名以日期和序号开头。组件文档同样不能省。我推荐每个 shared 组件都要有一份简单的 Markdown 说明包含输入输出参数含义、使用示例、注意事项。很多团队用 Storybook 做组件文档这当然好但如果维护精力有限先保证 Markdown 文档是齐全的。4. 在“老破乱”代码库中逐步推行规范化不是所有团队都有机会从新项目开始推行规范。现实情况是你手上是一个已经跑了三五年、代码几千个文件、风格五花八门的老项目。在这种项目里喊“我们要全面启用 ESLint Prettier 新结构”基本会遭到所有人的抵触。老项目改造我推荐渐进式策略。4.1 先加规则再动代码不要一开始就把所有规则全部打开。ESLint 规则一口气全开老的代码文件会瞬间冒出几百个 error开发者直接崩溃。正确做法是先打开不影响现有代码的规则比如禁console.log、禁止any让新增代码受到约束。存量代码的问题单独靠重构逐步解决。对于存量文件中已有的违规在落地初期可以用eslint-disable注释做豁免但要统一记录在案排期清理。我这里强调一点禁用的注释里必须写原因不能光秃秃一个// eslint-disable-next-line。// eslint-disable-next-line typescript-eslint/no-explicit-any -- 上游接口返回结构过于复杂暂时用 any 兜底待后端重构后移除 const payload rawData as any;这样做的意义是后来人知道这里有个“技术债”而且知道债主是谁。4.2 用边界隔离重构风险老项目里推行新目录结构最忌讳的就是一次性大搬移。文件动得越猛合并冲突越严重同事骂你越狠。我的做法是新模块、新功能一律按新目录结构放老代码按兵不动。在路由层面做隔离老的业务区继续用老目录新功能区的路由独立注册。通过模块边界把新旧代码隔离开等新建的功能多了、新结构有足够说服力了再逐步把老代码往新结构里迁移。每迁移一个功能区就问自己一个问题这个模块还有多少活跃改动如果三个月没有动过一次就不要迁不值得。老项目重构的资源永远优先给“正在被频繁修改”的代码这叫“热区重构”。4.3 引入 AI 辅助工具时要先立规矩最近不少团队在尝试用 Claude Code、Copilot 这类 AI 工具改大型 Angular 代码库。我自己也用了很长一段时间它们确实能提升效率但前提是“先立规矩”。我现在的个人实践是AI 生成的代码必须过三道检查编译不报错、lint 不报警、reviewer 人肉过一遍。AI 生成的代码往往“看起来对”但深层问题比如误用 change detection 策略、没有正确取消订阅、过度封装需要人眼才能发现。再加上一点不要给 AI “随意重构”的权限。我要求 AI 助手每次只改一个模块且改完必须跑测试。没有这个约束AI 很容易自作主张动很多不该动的文件最后合并的时候你欲哭无泪。5. 常见问题与排查技巧实录规范化落地的过程中你会遇到各种奇奇怪怪的问题。我把这几年踩过的坑和典型问题的排查方法整理成一份速查表希望你能少踩一遍。5.1 高频问题速查表症状可能原因处理方式ng lint报错显示没有可用的 lint 配置项目仍是 Angular 13 以下老版本自带 TSLint先用ng update升级再用angular-eslint的 schematic 自动迁移Prettier 与 ESLint 规则冲突两者都在管格式问题关闭 ESLint 里和格式相关的规则如indent、quotes格式问题全权交给 Prettierlint-staged 在 commit 时不生效husky 钩子没安装或 git 版本不支持检查.husky/pre-commit是否存在确认core.hooksPath指向正确路径某个老文件 ESLint 报错几百条无法合 PR存量代码未做兼容先配置规则对该文件关闭记入技术债清单排期单独清理团队有人总跳过 husky 钩子本地绕过--no-verify无法强制阻止但在 CI 里加 lint 检查兜底绕过本地钩子的代码会在 CI 阶段被拦下路由懒加载生效但首屏反而变慢懒加载分包过细导致加载碎片化分析打包产物适当合并路由组保持单个 chunk 在合理大小范围内5.2 几个我踩过的坑第一个坑直接引入大厂的 ESLint 配置。Airbnb 的配置确实写得很全但对 Angular 项目来说是水土不服的。很多规则在 React 场景下合理在 Angular 下反而徒增噪音。比如关于 JSX 的规则Angular 模板根本用不上。直接套用的结果是团队被错误淹没最后所有人对 lint 产生抵触。建议以angular-eslint/recommended为基底按需开发自己的规则叠加层。第二个坑共享目录过度设计。我曾经在一个项目中预设了很多“以后会用上”的共享组件结果半年后只有一个在真正被使用。剩下的组件不仅没有带来收益还成了持续的维护负担。现在我的原则是没有消费方就不进 shared。共享代码要有“用户”才能活没有用户的代码只是负债。第三个坑CI 里的测试跑得越来越慢最后大家都选择不看日志直接合入。这几乎是所有 Angular 团队的必经之路。解决方案就是前面提到的增量检查方案把 CI 做“瘦身”PR 阶段只跑受影响的测试主干全量测试放夜间流水线。别让 CI 成为团队的负担否则再严格的机制都会被绕过。第四个坑团队协作规则只写在文档里没有在实践中执行。很多团队把规范文档写得很漂亮但 Code Review 形同虚设commit 信息依然随意。问题的根源通常是“review 门槛太高”或者“review 没有明确清单”。把 Review 的范围缩小、清单化大家才愿意认真执行。宁可一次只 review 一百行代码也不要等到 PR 攒到两千行才看。结尾大概就是这些。Angular 项目的工程化没有终点代码规范、项目结构和团队协作中的每一项都永无止境但底子打好了后续所有迭代都会变得更加顺畅。我个人在实际操作中最深的体会是规范不是用来约束人的而是用来解放人的。好的规范能让你心无旁骛地去写业务代码而不用反复纠结那些本不该纠结的事情。希望这份经验能帮你在自己的团队里少走一些弯路。