3. 组件通信与父子组件
组件通信是 Angular 面试必考点,也是构建复杂应用的基础。面试中主要考察四种方式:@Input/@Output(父子直接传递)、EventEmitter(事件绑定)、ViewChild(模板引用获取实例)、服务通信(跨层级共享状态)。掌握每种方式的适用场景、实现步骤和潜在陷阱即可从容应答。
📌 组件通信方式一览表
| 方式 | 方向 | 核心 API | 典型场景 |
|---|---|---|---|
| @Input/@Output | 父→子 / 子→父 | @Input 接收值,@Output 发出事件 | 表单控件、数据展示组件 |
| EventEmitter | 子→父 | new EventEmitter() + .emit() | 按钮点击、列表行操作 |
| ViewChild | 父→子(反向调用) | @ViewChild() + 子组件引用 | 调用子组件方法(如打开弹窗) |
| 服务通信 | 任意方向 | Service + Subject/Observable | 用户状态、购物车、跨模块共享 |
🎯 面试问答 1
面试问题:请解释 @Input 和 @Output 是如何工作的?在实际项目中你如何选择使用它们?
面试官考察点:是否理解单向数据流与事件驱动的设计原则;能否区分输入输出场景;是否能结合项目说明使用规范。
回答框架:定义 → 方向 → 代码示例 → 适用场景 → 注意事项(命名规范、类型检查、变更检测)。
// 子组件
@Input() userName: string = '';
@Output() userChange = new EventEmitter<string>();
onUpdate(value: string) {
this.userChange.emit(value);
}
// 父组件模板
<app-child [userName]="currentUser"
(userChange)="handleChange($event)"></app-child>
简短示例回答(约 90 字):@Input 从父组件接收数据,@Output 配合 EventEmitter 向父组件发射事件,遵循单向数据流。我通常在表单控件、列表行操作等明确父子关系中使用,且会为 @Output 使用 onXxx 命名,避免与原生事件冲突。
可能追问:如何对 @Input 做 setter 拦截?@Input 的变更检测如何触发?如果需要同时传多个值怎么办?
🎯 面试问答 2
面试问题:EventEmitter 在 Angular 中的角色是什么?它和普通的 DOM 事件有何不同?
面试官考察点:对 Angular 自定义事件机制的理解;是否知道 EventEmitter 继承自 Subject;能否区分事件发射与订阅。
回答框架:定义 → 属于 RxJS Subject → 仅在 @Output 中使用 → emit 触发 → 父组件通过 (event) 绑定 → 对比原生事件:可携带任意类型数据、支持异步。
// 子组件
private notify = new EventEmitter<string>();
sendMessage(msg: string) {
this.notify.emit(msg);
}
简短示例回答(约 85 字):EventEmitter 本质是一个 RxJS Subject,常用于 @Output 发射自定义事件。相比 DOM 事件,它可以传递任意类型数据,且发射时机完全由组件逻辑控制。在实际项目中,我会用它实现子组件“通知父组件执行操作”的模式,比如订单状态变更后的刷新回调。
可能追问:EventEmitter 为什么必须是泛型?可以手动调用 unsubscribe 吗?如果父组件不订阅会怎样?
🎯 面试问答 3
面试问题:ViewChild 和服务通信分别适用于什么场景?请举例说明。
面试官考察点:能否区分模板引用与依赖注入两种模式的适用边界;是否理解 ViewChild 仅用于父子,服务通信是跨组件最佳实践。
回答框架:分两点阐述 → ViewChild:获取子组件实例、调用方法、访问属性,生命周期用 ngAfterViewInit → 服务通信:创建共享服务 + Subject,通过订阅实现任意组件数据联动 → 对比:一个强耦合但直接,一个松耦合但需要管理订阅。
// ViewChild 示例
@ViewChild('childRef') childComp!: ChildComponent;
ngAfterViewInit() {
this.childComp.someMethod();
}
// 服务通信示例
@Injectable()
export class SharedService {
private data$ = new Subject<string>();
sendData(v: string) { this.data$.next(v); }
getData() { return this.data$.asObservable(); }
}
简短示例回答(约 95 字):ViewChild 用于父组件直接调用子组件的公共方法,比如打开子组件的模态框;服务通信则用于不相关的兄弟组件或跨层级组件,比如用户登录状态共享。我会优先用服务通信实现松耦合,只在确实需要调用子组件方法时才用 ViewChild。
可能追问:ViewChild 为什么必须在 ngAfterViewInit 之后才能访问?服务通信中 Subject 和 BehaviorSubject 的选择?如果组件销毁订阅未取消会怎样?