# Angular Code Review Guide > Angular 17+ 代码审查指南,覆盖 Signals、Standalone 组件、RxJS 反模式、Zoneless 变更检测、模板最佳实践及性能优化等核心主题。 ## 目录 - [Signals 与变更检测](#signals-与变更检测) - [Standalone 组件迁移](#standalone-组件迁移) - [RxJS 反模式](#rxjs-反模式) - [Zoneless 变更检测](#zoneless-变更检测) - [模板最佳实践](#模板最佳实践) - [性能优化](#性能优化) - [Review Checklist](#review-checklist) --- ## Signals 与变更检测 ### Signal + OnPush 自动触发变更检测 ```typescript // ❌ 可变状态 + OnPush = 界面不更新 @Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: `
{{ data.name }}
`, }) export class UserProfile { data = { name: 'Alice' }; changeName() { this.data.name = 'Bob'; } // UI 不会更新! } // ✅ Signal + OnPush = 自动变更检测 @Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: `{{ name() }}
`, }) export class UserProfile { name = signal('Alice'); changeName() { this.name.set('Bob'); } // 自动触发 CD } ``` ### @Input() 对象变异不会被 OnPush 检测 ```typescript // ❌ 变异 Input 对象——引用不变,OnPush 不检测 @Input() config!: Config; updateConfig() { this.config.theme = 'dark'; } // ✅ 创建新引用 updateConfig() { this.config = { ...this.config, theme: 'dark' }; } ``` ### computed() 用于派生状态 ```typescript // ❌ effect 用于同步状态——反模式,可能触发额外 CD 周期 export class CartComponent { total = signal(0); discounted = signal(0); constructor() { effect(() => this.discounted.set(this.total() * 0.9)); } } // ✅ computed 用于派生状态——惰性计算,无副作用 export class CartComponent { total = signal(0); discounted = computed(() => this.total() * 0.9); } ``` ### effect() 中 Signal 读取在 await 后不会被追踪 ```typescript // ❌ await 之后读取 Signal——依赖未被追踪 effect(async () => { const data = await fetchUserData(); console.log(`Theme: ${theme()}`); // theme() 未被追踪! }); // ✅ 在 await 之前同步读取 effect(async () => { const currentTheme = theme(); // 同步读取,被追踪 const data = await fetchUserData(); console.log(`Theme: ${currentTheme}`); }); ``` ### effect 只在特定场景使用 ```typescript // ❌ 用 effect 同步两个 Signal——永远用 computed effect(() => { this.filtered.set(this.items().filter(i => i.active)); }); // ✅ effect 的合理场景:DOM 操作、分析日志、订阅外部源 effect(() => { const canvas = this.canvasRef.nativeElement; const ctx = canvas.getContext('2d'); ctx.fillStyle = this.color(); ctx.fillRect(0, 0, this.size(), this.size()); }); // 💡 "There are no situations where effect is good, // only situations where it is appropriate." ``` --- ## Standalone 组件迁移 ### Angular 19+ standalone 是默认值 ```typescript // ❌ Legacy NgModule 组件 @Component({ selector: 'old-component', standalone: false, }) export class OldComponent {} // ✅ 现代 Standalone 组件(Angular 19+ standalone 是默认值) @Component({ selector: 'user-profile', imports: [ProfilePhoto, RouterLink], template: `