この記事では、Angular について説明し、依存関係注入の基本的な概念を紹介します。
「大規模なフロントエンド プロジェクト向け」に設計されたフロントエンド フレームワークとして、Angular には実際に参考にして学ぶ価値のある設計が数多くあります。このシリーズは主に次の用途に使用されます。これらのデザインと機能を研究し、実現原理を研究します。この記事では、Angular の最大の機能である依存関係注入に焦点を当て、まず Angular の依存関係注入システムの基本的な概念をいくつか紹介します。
Angular フレームワークの依存性注入設計を紹介したいので、最初に依存性注入の基本概念を説明します。依存関係反転原則 (DIP)、制御反転 (IoC)、および依存関係注入 (DI) の概念はよく混同されるため、ここで簡単に説明します。 [関連チュートリアルの推奨事項: "angular チュートリアル"]
低結合、高凝集性です。これはおそらくあらゆるシステムの設計目標の 1 つであり、依存関係逆転原理や制御逆転の設計アイデアを含む、多くの設計パターンや概念がこの目的のために作成されてきました。
(1) 依存関係逆転原理 (DIP)。
依存関係逆転原則の元の定義は次のとおりです:
簡単に言うと、モジュールは相互に直接依存するべきではなく、抽象ルール (インターフェイスまたは抽象クラス) に依存する必要があります。
(2) 制御の反転 (IoC)。
制御の反転の定義は、モジュール間の依存関係がプログラム内から外部にインスタンス化および管理されることです。つまり、オブジェクトが作成されると、そのオブジェクトはシステム内のすべてのオブジェクトを制御する外部エンティティによって制御され、そのオブジェクトが依存するオブジェクトの参照がそのオブジェクトに渡されます (注入されます)。
制御の反転を実装するには、主に 2 つの方法があります。
##(3) 依存関係の注入。
依存性注入は、制御反転の最も一般的な手法です。 依存性反転と制御反転は相互に補完し合うため、多くの場合一緒に使用してモジュール間の結合を効果的に軽減できます。 Angular での依存関係の挿入Angular では、依存関係の挿入テクノロジも使用されます。クラスをインスタンス化するとき、DI フレームワークは、このクラスによって宣言された依存関係をクラスに提供します。(依存関係: を参照してください)クラスがその機能を実行するために必要なサービスまたはオブジェクト)。 Angular での依存関係の挿入は基本的にコンポーネントまたはモジュールを中心に展開し、主に新しく作成されたコンポーネントに依存関係を提供するために使用されます。 Angular の主な依存関係注入メカニズムは、インジェクター メカニズムです。:
プロバイダ Provider を見てみましょう。 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; }
のソース コードからもそれを確認できます。つまり、共有する必要がある依存関係インスタンスをインジェクターに追加し、クエリと取得を行うことができます。トークン インジェクターを介してそれらを取得し、対応する依存関係インスタンスを取得します。
Angular のインジェクターは階層的であるため、依存関係を見つけるプロセスは、インジェクター ツリーを上にたどるプロセスでもあることに注意してください。 これは、Angular ではアプリケーションがモジュールに編成されているためです。詳細については、「5. モジュール編成
」を参照してください。一般にページの DOM はhtml をルートノードとするツリー構造になっており、これを基に Angular アプリケーション内のコンポーネントやモジュールもそれに付随するツリー構造になります。 インジェクターはコンポーネントとモジュールにサービスを提供し、モジュールと組織のツリー構造にもマウントされます。したがって、Injector はモジュール レベルとコンポーネント レベルにも分割されており、それぞれコンポーネントとモジュールの依存関係の特定のインスタンスを提供できます。インジェクターは継承可能です。つまり、指定されたインジェクターが依存関係を解決できない場合、親インジェクターに依存関係を解決するよう要求します。また、上記のコードから、インジェクターを作成することがわかります。<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">// 创建一个新的 Injector 实例,可传入 parent 父注入器
static create(options: {providers: StaticProvider[], parent?: Injector, name?: string}): Injector;</pre><div class="contentsignin">ログイン後にコピー</div></div><p>在某个注入器的范围内,服务是单例的。也就是说,在指定的注入器中最多只有某个服务的最多一个实例。如果不希望在所有地方都使用该服务的同一个实例,则可以通过注册多个注入器、并按照需要关联到组件和模块中的方式,来按需共享某个服务依赖的实例。</p><p>我们可以看到创建一个新的<code>Injector
实例时,传入的参数包括Provider
,这是因为Injector
不会直接创建依赖,而是通过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 中,服务就是一个带有@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 依赖注入体系中比较关键的几个概念,主要包括Injector
、Provider
和Injectable
。
对于注入器、提供者和可注入服务,我们可以简单地这样理解:
注入器用于创建依赖,会维护一个容器来管理这些依赖,并尽可能地复用它们。
一个注入器中的依赖服务,只有一个实例。
注入器需要使用提供者来管理依赖,并通过 token(DI 令牌)来进行关联。
提供者用于高速注入器应该如何获取或创建依赖。
可注入服务类会根据元数据编译后,得到可注入对象,该对象可用于创建实例。
更多编程相关知识,请访问:编程入门!!
以上がAngular の依存関係注入システムの基本概念について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。