MST

星途 面试题库

面试题:Angular中如何利用TypeScript的装饰器优化组件开发

TypeScript的装饰器在Angular开发里起着重要作用。请详细描述Angular内置的一些装饰器(如@Component、@Input、@Output等)的工作原理,并说明如何自定义装饰器来为组件添加额外功能,比如日志记录、权限验证等。给出具体的代码示例,并解释装饰器对组件性能和可维护性的影响。
17.3万 热度难度
前端开发Angular

知识考点

AI 面试

面试题答案

一键面试

Angular内置装饰器工作原理

  1. @Component
    • 原理:它用于定义一个Angular组件。它将一个普通的TypeScript类标记为Angular组件,并配置该组件的元数据,如选择器(用于在HTML中标识该组件)、模板(定义组件的视图结构)、样式(定义组件的样式)等。Angular在解析模板时,会根据选择器找到对应的组件类,并将模板与组件逻辑进行关联。
    • 示例
import { Component } from '@angular/core';

@Component({
  selector: 'app-my - component',
  templateUrl: './my - component.html',
  styleUrls: ['./my - component.css']
})
export class MyComponent {
  // 组件逻辑
}
  1. @Input
    • 原理:用于从父组件向子组件传递数据。当在子组件类的属性上使用@Input装饰器时,Angular会在组件初始化时,将父组件模板中绑定到该子组件输入属性的值赋给子组件的相应属性。这使得子组件能够接收并使用父组件传递的数据。
    • 示例
// 子组件
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app - child - component',
  templateUrl: './child - component.html'
})
export class ChildComponent {
  @Input() dataFromParent: string;
}

// 父组件模板
<app - child - component [dataFromParent]="parentData"></app - child - component>
  1. @Output
    • 原理:用于子组件向父组件发送事件。它将一个属性标记为一个事件发射器(EventEmitter类型)。当子组件内部发生特定事件时,可以通过这个事件发射器发射事件,父组件可以在模板中通过绑定事件处理函数来捕获这些事件。
    • 示例
// 子组件
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app - child - component',
  templateUrl: './child - component.html'
})
export class ChildComponent {
  @Output() customEvent = new EventEmitter();

  triggerEvent() {
    this.customEvent.emit('Some data');
  }
}

// 父组件模板
<app - child - component (customEvent)="handleChildEvent($event)"></app - child - component>

自定义装饰器

  1. 日志记录装饰器
    • 代码示例
import { ReflectiveInjector, Injectable } from '@angular/core';

// 定义装饰器工厂函数
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function(...args: any[]) {
    console.log(`Calling method ${propertyKey} with args:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${propertyKey} returned:`, result);
    return result;
  };

  return descriptor;
}

@Injectable()
class MyService {
  @Log
  myMethod(a: number, b: number) {
    return a + b;
  }
}

// 使用
const injector = ReflectiveInjector.resolveAndCreate([MyService]);
const myService = injector.get(MyService);
myService.myMethod(1, 2);
- **解释**:上述代码定义了一个`Log`装饰器,它可以在方法调用前后打印日志。通过保存原始方法,在新的方法中添加日志记录逻辑,然后返回新的方法来替换原方法。

2. 权限验证装饰器 - 代码示例

// 模拟权限服务
@Injectable()
class AuthService {
  hasPermission(permission: string): boolean {
    // 实际实现中从后端或本地存储等获取权限信息
    return true;
  }
}

// 权限验证装饰器工厂函数
function HasPermission(permission: string) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
      const injector = ReflectiveInjector.resolveAndCreate([AuthService]);
      const authService = injector.get(AuthService);
      if (authService.hasPermission(permission)) {
        return originalMethod.apply(this, args);
      } else {
        console.log('No permission to execute this method');
        return null;
      }
    };

    return descriptor;
  };
}

@Injectable()
class MyProtectedService {
  @HasPermission('admin - only')
  protectedMethod() {
    return 'This is a protected method';
  }
}

// 使用
const protectedInjector = ReflectiveInjector.resolveAndCreate([MyProtectedService]);
const myProtectedService = protectedInjector.get(MyProtectedService);
myProtectedService.protectedMethod();
- **解释**:`HasPermission`装饰器工厂函数接收一个权限字符串作为参数。它返回一个装饰器,该装饰器在方法调用前检查用户是否有相应权限,有权限则执行原方法,否则打印提示信息。

对组件性能和可维护性的影响

  1. 性能影响
    • 积极方面:装饰器本身在合理使用的情况下,对性能影响较小。例如@Input@Output的机制是Angular框架内部优化过的,数据传递和事件发射的性能开销相对较低。自定义装饰器如果逻辑简单,也不会引入显著的性能问题。
    • 消极方面:如果自定义装饰器中包含复杂的逻辑,如大量计算、频繁的DOM操作或异步操作,可能会影响组件的渲染性能和响应速度。例如,在每个方法调用时执行复杂的权限验证逻辑可能导致性能瓶颈。
  2. 可维护性影响
    • 积极方面:装饰器提高了代码的可维护性。例如,@Component将组件的配置和逻辑分离,使得代码结构更清晰。自定义装饰器可以将通用功能(如日志记录、权限验证)从组件业务逻辑中分离出来,提高了代码的复用性和可测试性。如果需要修改日志记录或权限验证的逻辑,只需要在装饰器中修改,而不需要在每个使用的地方修改。
    • 消极方面:过多或复杂的装饰器嵌套可能会使代码难以理解和调试。如果装饰器之间存在依赖关系或者装饰器逻辑复杂,排查问题时可能需要深入多个装饰器内部,增加了维护成本。