面试题答案
一键面试自定义指令与组件通信方法
- 双向数据绑定
- 在Angular中,可以通过
@Input()
和@Output()
装饰器来实现自定义指令与组件的双向数据绑定。 - 父组件与自定义指令双向绑定:
- 在自定义指令中定义
@Input()
属性用于接收父组件传入的数据,同时定义@Output()
事件发射器用于向父组件传递数据变化。 - 父组件模板中使用
[(ngModel)]
类似的语法(可自定义双向绑定语法,需结合@Input()
和@Output()
)。例如:- 自定义指令代码:
import { Directive, Input, Output, EventEmitter } from '@angular/core'; @Directive({ selector: '[appMyDirective]' }) export class MyDirective { @Input() appMyDirective: any; @Output() appMyDirectiveChange = new EventEmitter<any>(); onSomeAction() { // 当指令内部数据变化时,发射事件通知父组件 this.appMyDirectiveChange.emit(newValue); } }
- 父组件模板代码:
<div [appMyDirective]="parentData" (appMyDirectiveChange)="parentData = $event"> <!-- 这里进行指令相关操作 --> </div>
- 在自定义指令中定义
- 与子组件双向绑定:可以在父组件中使用
ViewChild
获取子组件实例,然后通过实例的属性和方法进行数据交互。子组件通过@Input()
和@Output()
与父组件通信,自定义指令如果在父组件模板中,可间接与子组件通信。例如:- 子组件代码:
import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: 'app - child - component', templateUrl: './child - component.html' }) export class ChildComponent { @Input() childInput: any; @Output() childOutput = new EventEmitter<any>(); onChildAction() { this.childOutput.emit(newValue); } }
- 父组件代码:
import { Component, ViewChild } from '@angular/core'; import { ChildComponent } from './child - component'; @Component({ selector: 'app - parent - component', templateUrl: './parent - component.html' }) export class ParentComponent { @ViewChild(ChildComponent) child: ChildComponent; ngAfterViewInit() { // 父组件通过子组件实例访问子组件数据和方法 this.child.childInput = someData; this.child.childOutput.subscribe((value) => { // 处理子组件传递的数据 }); } }
- 与兄弟组件双向绑定:通过共享服务(
@Injectable()
)来实现。兄弟组件都注入该服务,通过服务中的属性和方法进行数据共享和事件传递。例如:- 共享服务代码:
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class SharedService { private sharedData = new BehaviorSubject<any>(null); currentData = this.sharedData.asObservable(); changeData(data: any) { this.sharedData.next(data); } }
- 兄弟组件1代码:
import { Component } from '@angular/core'; import { SharedService } from './shared.service'; @Component({ selector: 'app - sibling - component1', templateUrl: './sibling - component1.html' }) export class SiblingComponent1 { constructor(private sharedService: SharedService) {} onSomeAction() { this.sharedService.changeData(newValue); } }
- 兄弟组件2代码:
import { Component } from '@angular/core'; import { SharedService } from './shared.service'; @Component({ selector: 'app - sibling - component2', templateUrl: './sibling - component2.html' }) export class SiblingComponent2 { constructor(private sharedService: SharedService) { this.sharedService.currentData.subscribe((data) => { // 处理共享数据 }); } }
- 在Angular中,可以通过
- 事件传递
- 对于自定义指令与父组件的事件传递,使用
@Output()
装饰器定义事件发射器,父组件在模板中监听该事件。如上述自定义指令与父组件双向绑定示例中,指令内onSomeAction
方法发射事件,父组件在模板中监听(appMyDirectiveChange)
。 - 与子组件的事件传递,子组件通过
@Output()
发射事件,父组件在模板中监听,自定义指令可间接通过父组件监听子组件事件。 - 与兄弟组件的事件传递,通过共享服务的事件发射器来实现。例如共享服务中定义
private eventEmitter = new EventEmitter<any>();
,一个兄弟组件调用this.sharedService.eventEmitter.emit(eventData)
发射事件,另一个兄弟组件订阅this.sharedService.eventEmitter.subscribe((data) => { /* 处理事件 */ })
。
- 对于自定义指令与父组件的事件传递,使用
性能优势
- 基于
@Input()
和@Output()
的双向数据绑定:- 精准更新:Angular的变更检测机制能够精准检测到
@Input()
属性的变化,只更新受影响的部分,避免了不必要的DOM重绘和重排。例如在父组件与自定义指令双向绑定中,只有当appMyDirective
属性变化时,才会触发相关的DOM更新,而不是整个组件树的更新。 - 高效的事件处理:
@Output()
事件发射器在发射事件时,Angular的事件绑定机制能够高效地将事件传递给相应的处理函数,减少了事件处理的开销。
- 精准更新:Angular的变更检测机制能够精准检测到
- 共享服务实现兄弟组件通信:
- 解耦与高效数据共享:通过共享服务,兄弟组件之间解耦,不需要直接依赖。而且共享服务的数据存储和事件发射是基于内存的操作,相比于通过复杂的组件树传递数据和事件,减少了大量的中间处理过程,提高了性能。
自定义指令性能优化策略及代码示例
- 避免不必要的DOM操作
- 策略:尽量减少指令内部对DOM的直接操作频率。如果必须操作DOM,使用
Renderer2
服务,它提供了安全、高效的DOM操作方法,并且能够更好地与Angular的变更检测机制协同工作。同时,使用ngZone
来控制DOM操作的时机,避免在Angular变更检测周期内进行不必要的操作。 - 代码示例:
import { Directive, ElementRef, Renderer2, NgZone } from '@angular/core'; @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { constructor(private el: ElementRef, private renderer: Renderer2, private ngZone: NgZone) {} ngOnInit() { this.ngZone.runOutsideAngular(() => { // 在Angular变更检测周期外进行DOM操作 this.renderer.setStyle(this.el.nativeElement, 'background - color', 'yellow'); }); } }
- 策略:尽量减少指令内部对DOM的直接操作频率。如果必须操作DOM,使用
- 防止内存泄漏
- 策略:
- 正确取消订阅可观察对象(
Observable
)。在指令销毁时,确保取消所有订阅,避免内存泄漏。 - 避免在指令内部创建全局变量或引用不会被垃圾回收的对象。
- 正确取消订阅可观察对象(
- 代码示例:
import { Directive, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; @Directive({ selector: '[appAutoUnsubscribe]' }) export class AutoUnsubscribeDirective implements OnDestroy { private subscription: Subscription; constructor() { this.subscription = someObservable.subscribe((value) => { // 处理订阅数据 }); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } } }
- 策略:
- 优化变更检测
- 策略:对于自定义指令,合理设置变更检测策略。如果指令内部数据变化不频繁,可以将变更检测策略设置为
OnPush
,这样Angular只会在输入属性变化、事件触发或手动标记为脏时才检查该指令及其子组件。 - 代码示例:
import { Directive, ChangeDetectionStrategy } from '@angular/core'; @Directive({ selector: '[appOnPushDirective]', changeDetection: ChangeDetectionStrategy.OnPush }) export class OnPushDirective {}
- 策略:对于自定义指令,合理设置变更检测策略。如果指令内部数据变化不频繁,可以将变更检测策略设置为