>  기사  >  웹 프론트엔드  >  Angular의 의존성 주입 시스템의 기본 개념에 대해 이야기해 보겠습니다.

Angular의 의존성 주입 시스템의 기본 개념에 대해 이야기해 보겠습니다.

青灯夜游
青灯夜游앞으로
2022-03-03 11:06:051929검색

이 글에서는 Angular에 대해 이야기하고 의존성 주입의 기본 개념을 소개하겠습니다. 모든 사람에게 도움이 되기를 바랍니다.

Angular의 의존성 주입 시스템의 기본 개념에 대해 이야기해 보겠습니다.

"대규모 프런트엔드 프로젝트용"으로 설계된 프런트엔드 프레임워크인 Angular에는 실제로 참고하고 배울 만한 디자인이 많이 있습니다. 이 시리즈는 주로 이러한 디자인과 기능의 구현 원리를 연구하는 데 사용됩니다. 이번 글에서는 Angular의 가장 큰 특징인 의존성 주입에 초점을 맞췄습니다. 먼저 Angular 의존성 주입 시스템의 몇 가지 기본 개념을 소개하겠습니다.

종속성 주입

Angular 프레임워크의 종속성 주입 설계를 소개할 예정이므로 먼저 종속성 주입의 기본 개념에 대한 기초를 다져 보겠습니다. 우리는 DIP(Dependency Inversion Principal), IoC(Inversion of Control), DI(Dependency Injection)의 개념을 혼동하는 경우가 많으므로 여기서는 간단히 소개하겠습니다. [관련 튜토리얼 추천: "angular Tutorial"]

종속성 역전 원리, 제어 역전, 종속성 주입

낮은 결합도와 높은 응집력은 아마도 모든 시스템의 설계 목표 중 하나일 것입니다. 의존성 역전 원리와 제어 역전의 디자인 아이디어를 포함한 많은 디자인 패턴과 개념이 있습니다.

(1) 종속성 반전 원칙(DIP).

종속성 반전 원칙의 원래 정의는 다음과 같습니다.

  • 상위 수준 모듈은 하위 수준 모듈에 의존해서는 안 되며 둘 다 추상화에 의존해야 합니다.
  • 추상화는 세부 사항에 의존해서는 안 되며 세부 사항은 다음에 의존해야 합니다. 추상화.

간단히 말하면: 모듈은 서로 직접적으로 의존해서는 안 되며 추상 규칙(인터페이스 또는 추상 클래스)에 의존해야 합니다.

(2) 제어 반전(IoC).

제어 반전의 정의는 모듈 간의 종속성이 프로그램 내부에서 외부로 인스턴스화되고 관리된다는 것입니다. 즉, 객체가 생성되면 시스템의 모든 객체를 제어하는 ​​외부 엔터티에 의해 제어되며, 해당 객체가 의존하는 객체의 참조가 해당 객체에 전달(주입)됩니다.

제어 반전을 구현하는 두 가지 주요 방법이 있습니다.

  • 종속성 주입: 종속 객체를 수동적으로 수신
  • 종속성 조회: 종속 객체를 적극적으로 요청

(3) 종속성 주입.

종속성 주입은 제어 반전의 가장 일반적인 기술입니다.

종속성 반전과 제어 반전은 서로를 보완하며 종종 함께 사용하여 모듈 간의 결합을 효과적으로 줄일 수 있습니다.

Angular의 종속성 주입

Angular에서는 클래스를 인스턴스화할 때 DI 프레임워크가 클래스에서 선언한 종속성을 제공합니다(종속성: 클래스에 서비스 또는 개체가 필요한 경우 참조). 기능을 수행하기 위해).

Angular의 종속성 주입은 기본적으로 구성 요소나 모듈을 중심으로 이루어지며 주로 새로 생성된 구성 요소에 종속성을 제공하는 데 사용됩니다.

Angular의 주요 종속성 주입 메커니즘은 인젝터 메커니즘입니다.

  • 애플리케이션에 필요한 모든 종속성은 애플리케이션의 인젝터를 사용하여 공급자를 등록해야 인젝터가 이 공급자를 사용하여 새 인스턴스를 생성할 수 있습니다.
  • Angular 시작 프로세스 중에 전체 애플리케이션 수준 인젝터와 기타 필수 인젝터를 생성합니다

여기에는 주로 Injector injectorProvider Provider라는 두 가지 개념이 포함됩니다. 살펴보겠습니다.

Injector

Injector는 종속성을 생성하고 이러한 종속성을 관리하기 위해 컨테이너를 유지하며 가능한 한 많이 재사용하는 데 사용됩니다. 인젝터는 종속성의 싱글톤을 제공하고 이 싱글톤 개체를 여러 구성 요소에 주입합니다.

분명히 종속성을 생성, 관리 및 유지하는 데 사용되는 컨테이너로서 인젝터의 기능은 간단합니다. 종속성 인스턴스 생성, 종속성 인스턴스 획득, 종속성 인스턴스 관리입니다. 추상 클래스 Injector의 소스 코드에서도 이를 확인할 수 있습니다. Injector的源码中看出来:

export abstract class Injector {
  // 找不到依赖
  static THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND;
  // NullInjector 是树的顶部
  // 如果你在树中向上走了很远,以至于要在 NullInjector 中寻找服务,那么将收到错误消息,或者对于 @Optional(),返回 null
  static NULL: Injector = new NullInjector();

  // 根据提供的 Token 从 Injector 检索实例
  abstract get<T>(
    token: Type<T> | AbstractType<T> | InjectionToken<T>,
    notFoundValue?: T,
    flags?: InjectFlags
  ): T;

  // 创建一个新的 Injector 实例,该实例提供一个或多个依赖项
  static create(options: {
    providers: StaticProvider[];
    parent?: Injector;
    name?: string;
  }): Injector;

  // ɵɵdefineInjectable 用于构造一个 InjectableDef
  // 它定义 DI 系统将如何构造 Token,并且在哪些 Injector 中可用
  static ɵprov = ɵɵdefineInjectable({
    token: Injector,
    providedIn: "any" as any,
    // ɵɵinject 生成的指令:从当前活动的 Injector 注入 Token
    factory: () => ɵɵinject(INJECTOR),
  });

  static __NG_ELEMENT_ID__ = InjectorMarkers.Injector;
}

也就是说,我们可以将需要共享的依赖实例添加到注入器中,并通过 Token 查询和检索注入器来获取相应的依赖实例。

需要注意的是,Angular 中的注入器是分层的,因此查找依赖的过程也是向上遍历注入器树的过程。

这是因为在 Angular 中,应用是以模块的方式组织的,具体可以参考5.模块化组织篇。一般来说,页面的 DOM 是以html

// 创建一个新的 Injector 实例,可传入 parent 父注入器
static create(options: {providers: StaticProvider[], parent?: Injector, name?: string}): Injector;

즉, 공유해야 하는 종속성 인스턴스를 인젝터에 추가하고 해당 인스턴스를 쿼리하고 검색할 수 있습니다. 토큰을 통해 인젝터를 가져옵니다.

Angular의 인젝터는 계층적이므로 종속성을 찾는 프로세스도 인젝터 트리를 위쪽으로 탐색하는 프로세스라는 점에 유의해야 합니다. 🎜🎜Angular에서는 애플리케이션이 모듈로 구성되어 있기 때문입니다. 자세한 내용은 를 참고하세요. 5. 모듈식 조직 🎜. 일반적으로 페이지의 DOM은 html을 루트 노드로 하는 트리 구조입니다. 이를 기반으로 Angular 애플리케이션의 구성 요소와 모듈도 그에 수반되는 트리 구조로 되어 있습니다. 🎜🎜인젝터는 부품과 모듈을 담당하며, 모듈과 조직의 트리 구조로 탑재되기도 합니다. 따라서 인젝터도 모듈 수준과 구성 요소 수준으로 구분되며, 이는 각각 구성 요소와 모듈에 대한 특정 종속성 인스턴스를 제공할 수 있습니다. 인젝터는 상속 가능합니다. 즉, 지정된 인젝터가 종속성을 해결할 수 없으면 상위 인젝터에게 이를 해결하도록 요청합니다. 위의 인젝터 생성 코드에서도 볼 수 있듯이: 🎜
// 创建一个新的 Injector 实例,可传入 parent 父注入器
static create(options: {providers: StaticProvider[], parent?: Injector, name?: string}): Injector;

在某个注入器的范围内,服务是单例的。也就是说,在指定的注入器中最多只有某个服务的最多一个实例。如果不希望在所有地方都使用该服务的同一个实例,则可以通过注册多个注入器、并按照需要关联到组件和模块中的方式,来按需共享某个服务依赖的实例。

我们可以看到创建一个新的Injector实例时,传入的参数包括Provider,这是因为Injector不会直接创建依赖,而是通过Provider来完成的。每个注入器会维护一个提供者的列表,并根据组件或其它服务的需要,用它们来提供服务的实例。

Provider 提供者

Provider 提供者用来告诉注入器应该如何获取或创建依赖,要想让注入器能够创建服务(或提供其它类型的依赖),必须使用某个提供者配置好注入器。

一个提供者对象定义了如何获取与 DI 令牌(token) 相关联的可注入依赖,而注入器会使用这个提供者来创建它所依赖的那些类的实例。

关于 DI 令牌:

  • 当使用提供者配置注入器时,就会把提供者和一个 DI 令牌关联起来;
  • 注入器维护一个内部令牌-提供者的映射表,当请求一个依赖项时就会引用它,令牌就是这个映射表的键。

提供者的类型很多,从官方文档中可以阅读它们的具体定义:

export type Provider =
  | TypeProvider
  | ValueProvider
  | ClassProvider
  | ConstructorProvider
  | ExistingProvider
  | FactoryProvider
  | any[];

提供者的解析过程如下:

function resolveReflectiveFactory(
  provider: NormalizedProvider
): ResolvedReflectiveFactory {
  let factoryFn: Function;
  let resolvedDeps: ReflectiveDependency[];
  if (provider.useClass) {
    // 使用类来提供依赖
    const useClass = resolveForwardRef(provider.useClass);
    factoryFn = reflector.factory(useClass);
    resolvedDeps = _dependenciesFor(useClass);
  } else if (provider.useExisting) {
    // 使用已有依赖
    factoryFn = (aliasInstance: any) => aliasInstance;
    // 从根据 token 获取具体的依赖
    resolvedDeps = [
      ReflectiveDependency.fromKey(ReflectiveKey.get(provider.useExisting)),
    ];
  } else if (provider.useFactory) {
    // 使用工厂方法提供依赖
    factoryFn = provider.useFactory;
    resolvedDeps = constructDependencies(provider.useFactory, provider.deps);
  } else {
    // 使用提供者具体的值作为依赖
    factoryFn = () => provider.useValue;
    resolvedDeps = _EMPTY_LIST;
  }
  //
  return new ResolvedReflectiveFactory(factoryFn, resolvedDeps);
}

根据不同类型的提供者,通过解析之后,得到由注入器 Injector 使用的提供者的内部解析表示形式:

export interface ResolvedReflectiveProvider {
  // 键,包括系统范围内的唯一 id,以及一个 token
  key: ReflectiveKey;
  // 可以返回由键表示的对象的实例的工厂函数
  resolvedFactories: ResolvedReflectiveFactory[];
  // 指示提供者是多提供者,还是常规提供者
  multiProvider: boolean;
}

提供者可以是服务类ClassProvider本身,如果把服务类指定为提供者令牌,那么注入器的默认行为是用new来实例化那个类。

Angular 中的依赖注入服务

在 Angular 中,服务就是一个带有@Injectable装饰器的类,它封装了可以在应用程序中复用的非 UI 逻辑和代码。Angular 把组件和服务分开,是为了增进模块化程度和可复用性。

@Injectable标记一个类,以确保编译器将在注入类时生成必要的元数据(元数据在 Angular 中也是很重要的一部分),以创建类的依赖项。

@Injectable装饰器的类会在编译之后,得到 Angular 可注入对象:

// 根据其 Injectable 元数据,编译 Angular 可注入对象,并对结果进行修补
export function compileInjectable(type: Type<any>, srcMeta?: Injectable): void {
  // 该编译过程依赖 @angular/compiler
  // 可参考编译器中的 compileFactoryFunction compileInjectable 实现
}

Angular 中可注入对象(InjectableDef)定义 DI 系统将如何构造 token 令牌,以及在哪些注入器(如果有)中可用:

export interface ɵɵInjectableDef<T> {
  // 指定给定类型属于特定注入器,包括 root/platform/any/null 以及特定的 NgModule
  providedIn: InjectorType<any> | "root" | "platform" | "any" | null;
  // 此定义所属的令牌
  token: unknown;
  // 要执行以创建可注入实例的工厂方法
  factory: (t?: Type<any>) => T;
  // 在没有显式注入器的情况下,存储可注入实例的位置
  value: T | undefined;
}

使用@Injectable()providedIn时,优化工具可以进行 Tree-shaking 优化,从而删除应用程序中未使用的服务,以减小捆绑包尺寸。

总结

本文简单介绍了在 Angular 依赖注入体系中比较关键的几个概念,主要包括InjectorProviderInjectable

对于注入器、提供者和可注入服务,我们可以简单地这样理解:

  • 注入器用于创建依赖,会维护一个容器来管理这些依赖,并尽可能地复用它们。

  • 一个注入器中的依赖服务,只有一个实例。

  • 注入器需要使用提供者来管理依赖,并通过 token(DI 令牌)来进行关联。

  • 提供者用于高速注入器应该如何获取或创建依赖。

  • 可注入服务类会根据元数据编译后,得到可注入对象,该对象可用于创建实例。

更多编程相关知识,请访问:编程入门!!

위 내용은 Angular의 의존성 주입 시스템의 기본 개념에 대해 이야기해 보겠습니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제