Angular CLI構(gòu)建器

2022-07-15 11:04 更新

Angular CLI 構(gòu)建器(Builder)

很多 Angular CLI 命令都要在你的代碼上執(zhí)行一些復(fù)雜的處理,比如風(fēng)格檢查(lint)構(gòu)建或測試。這些命令會通過一個叫做建筑師(Architect)的內(nèi)部工具來運行 CLI 構(gòu)建器,而這些構(gòu)建器會運用一些第三方工具來完成目標(biāo)任務(wù)。

在 Angular 的版本 8 中,CLI 構(gòu)建器的 API 是穩(wěn)定的,想要通過添加或修改命令來自定義 Angular CLI 的開發(fā)人員可以使用它。比如,你可以提供一個構(gòu)建器來執(zhí)行全新的任務(wù),或者更改一個現(xiàn)有命令所使用的第三方工具。

本文檔介紹了 CLI 構(gòu)建器是如何與工作區(qū)配置文件集成的,還展示了如何創(chuàng)建你自己的構(gòu)建器。

可以在這個 GitHub 倉庫中的例子中找到代碼。

CLI 構(gòu)建器

內(nèi)部建筑師工具會把工作委托給名叫構(gòu)建器的處理器函數(shù)。處理器函數(shù)接收兩個參數(shù):一組 ?options ?輸入(JSON 對象)和一個 ?context?(?BuilderContext ?對象)。

這里對關(guān)注點的分離和原理圖中是一樣的,它也適用于其它要接觸(touch)代碼的 CLI 命令(比如 ?ng generate?)。

  • 此 ?options ?對象是由本 CLI 的用戶提供的,而 ?context ?對象則由 CLI 構(gòu)建器的 API 提供
  • 除了上下文信息之外,此 ?context ?對象(它是 ?BuilderContext ?的實例)還允許你訪問調(diào)度方法 ?context.scheduleTarget()?。調(diào)度器會用指定的目標(biāo)配置來執(zhí)行構(gòu)建器處理函數(shù)。

這個構(gòu)建器處理函數(shù)可以是同步的(返回一個值)或異步的(返回一個 Promise),也可以監(jiān)視并返回多個值(返回一個 Observable)。最終返回的值全都是 ?BuilderOutput ?類型的。該對象包含一個邏輯字段 ?success ?和一個可以包含錯誤信息的可選字段 ?error?。

Angular 提供了一些構(gòu)建器,供 CLI 命令使用,如 ?ng build? 和 ?ng test? 等。這些內(nèi)置 CLI 構(gòu)建器的默認(rèn)目標(biāo)配置可以在工作區(qū)配置文件 ?angular.json? 的 ?architect ?部分找到(并進(jìn)行自定義)。可以通過創(chuàng)建自己的構(gòu)建器來擴(kuò)展和自定義 Angular,你可以使用 ?ng run? CLI 命令來運行你自己的構(gòu)建器。

構(gòu)建器的項目結(jié)構(gòu)

構(gòu)建器位于一個 ?project ?文件夾中,該文件夾的結(jié)構(gòu)類似于 Angular 工作區(qū),包括位于頂層的全局配置文件,以及位于工作代碼所在源文件夾中的更具體的配置。比如,?myBuilder ?文件夾中可能包含如下文件。

文件

用途

src/my-builder.ts

這個構(gòu)建器定義的主要源碼。

src/my-builder.spec.ts

測試的源碼。

src/schema.json

構(gòu)建器輸入選項的定義。

builders.json

測試配置。

package.json

依賴包。參閱 https://docs.npmjs.com/files/package.json

tsconfig.json

TypeScript 配置文件。

將此構(gòu)建器發(fā)布到 ?npm?。如果你將其發(fā)布為 ?@example/my-builder?,請使用以下命令安裝它。

npm install @example/my-builder

創(chuàng)建構(gòu)建器

舉個例子,讓我們創(chuàng)建一個用來復(fù)制文件的構(gòu)建器。要創(chuàng)建構(gòu)建器,請使用 CLI 構(gòu)建器函數(shù) ?createBuilder()?,并返回一個 ?Promise<BuilderOutput>? 對象。

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(
  options: Options,
  context: BuilderContext,
): Promise<BuilderOutput> {
}

現(xiàn)在,讓我們?yōu)樗砑右恍┻壿?。下列代碼會從用戶選項中獲取源文件和目標(biāo)文件的路徑,并且把源文件復(fù)制到目標(biāo)文件(使用 NodeJS 內(nèi)置函數(shù)copyFile()的 Promise 版本)。如果文件操作失敗了,它會返回一個帶有底層錯誤信息的 error 對象。

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(
  options: Options,
  context: BuilderContext,
): Promise<BuilderOutput> {
  try {
    await fs.copyFile(options.source, options.destination);
  } catch (err) {
    return {
      success: false,
      error: err.message,
    };
  }

  return { success: true };
}

處理輸出

默認(rèn)情況下,?copyFile()? 方法不會往標(biāo)準(zhǔn)輸出或標(biāo)準(zhǔn)錯誤中打印任何信息。如果發(fā)生了錯誤,可能很難理解構(gòu)建器到底做了什么。可以使用 ?Logger ?API 來記錄一些額外的信息,以提供額外的上下文。這樣還能讓構(gòu)建器本身可以在一個單獨的進(jìn)程中執(zhí)行,即使其標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤被停用了也無所謂(就像在 Electron 應(yīng)用中一樣)。

你可以從上下文中檢索一個 ?Logger ?實例。

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(
  options: Options,
  context: BuilderContext,
): Promise<BuilderOutput> {
  try {
    await fs.copyFile(options.source, options.destination);
  } catch (err) {
    context.logger.error('Failed to copy file.');
    return {
      success: false,
      error: err.message,
    };
  }

  return { success: true };
}

進(jìn)度和狀態(tài)報告

CLI 構(gòu)建器 API 包含一些進(jìn)度報告和狀態(tài)報告工具,可以為某些函數(shù)和接口提供提示信息。

要報告進(jìn)度,請使用 ?context.reportProgress()? 方法,它接受一個當(dāng)前值(value)、一個(可選的)總值(total)和狀態(tài)(status)字符串作為參數(shù)??傊悼梢允侨我鈹?shù)字,比如,如果你知道有多少個文件需要處理,那么總值可能是這些文件的數(shù)量,而當(dāng)前值是已處理過的數(shù)量。除非傳入了新的字符串,否則這個狀態(tài)字符串不會改變。

你可以看看 ?tslint ?構(gòu)建器如何報告進(jìn)度的例子。

在我們的例子中,這種復(fù)制操作或者已完成或者正在執(zhí)行,所以不需要進(jìn)度報告,但是可以報告狀態(tài),以便調(diào)用此構(gòu)建器的父構(gòu)建器知道發(fā)生了什么??梢杂?nbsp;?context.reportStatus()? 方法生成一個任意長度的狀態(tài)字符串。

注意:
無法保證長字符串會完全顯示出來,可以裁剪它以適應(yīng)界面顯示。

傳入一個空字符串可以移除狀態(tài)。

import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(
  options: Options,
  context: BuilderContext,
): Promise<BuilderOutput> {
  context.reportStatus(`Copying ${options.source} to ${options.destination}.`);
  try {
    await fs.copyFile(options.source, options.destination);
  } catch (err) {
    context.logger.error('Failed to copy file.');
    return {
      success: false,
      error: err.message,
    };
  }

  context.reportStatus('Done.');
  return { success: true };
}

構(gòu)建器的輸入

你可以通過 CLI 命令間接調(diào)用一個構(gòu)建器,也可以直接用 Angular CLI 的 ?ng run? 命令來調(diào)用它。無論哪種情況,你都必須提供所需的輸入,但是可以用特定目標(biāo)中預(yù)配置的值作為其默認(rèn)值,然后指定一個預(yù)定義的、指定的配置進(jìn)行覆蓋,最后在命令行中進(jìn)一步覆蓋這些選項的值。

對輸入的驗證

你可以在該構(gòu)建器的相關(guān) JSON 模式中定義構(gòu)建器都有哪些輸入。建筑師工具會把解析后的輸入值收集到一個 ?options ?對象中,并在將其傳給構(gòu)建器函數(shù)之前先根據(jù)這個模式驗證它們的類型。(Schematics 庫也對用戶輸入做了同樣的驗證)。

對于這個范例構(gòu)建器,你希望 ?options ?的值是帶有兩個鍵的 ?JsonObject?:一個是 ?source?,一個是 ?destination?,它們都是字符串。

你可以提供如下模式來對這些值的類型進(jìn)行驗證。

{
  "$schema": "http://json-schema.org/schema",
  "type": "object",
  "properties": {
    "source": {
      "type": "string"
    },
    "destination": {
      "type": "string"
    }
  }
}

這是一個非常簡單的例子,但這種模式驗證也可以非常強(qiáng)大。欲知詳情,參閱 JSON 模式網(wǎng)站。

要把構(gòu)建器的實現(xiàn)與它的模式和名稱關(guān)聯(lián)起來,你需要創(chuàng)建一個構(gòu)建器定義文件,可以在 ?package.json? 中指向該文件。

創(chuàng)建一個名為 ?builders.json? 文件,它看起來像這樣。

{
  "builders": {
    "copy": {
      "implementation": "./dist/my-builder.js",
      "schema": "./src/schema.json",
      "description": "Copies a file."
    }
  }
}

在 ?package.json? 文件中,添加一個 ?builders ?鍵,告訴建筑師工具可以在哪里找到這個構(gòu)建器定義文件。

{
  "name": "@example/copy-file",
  "version": "1.0.0",
  "description": "Builder for copying files",
  "builders": "builders.json",
  "dependencies": {
    "@angular-devkit/architect": "~0.1200.0",
    "@angular-devkit/core": "^12.0.0"
  }
}

現(xiàn)在,這個構(gòu)建器的正式名字是 ?@example/copy-file:copy?。第一部分是包名(使用 node 方案進(jìn)行解析),第二部分是構(gòu)建器名稱(使用 ?builders.json? 文件進(jìn)行解析)。

使用某個 ?options ?是非常簡單的。在上一節(jié),你就曾用過 ?options.source? 和 ?options.destination?。

context.reportStatus(`Copying ${options.source} to ${options.destination}.`);
try {
  await fs.copyFile(options.source, options.destination);
} catch (err) {
  context.logger.error('Failed to copy file.');
  return {
    success: false,
    error: err.message,
  };
}

context.reportStatus('Done.');
return { success: true };

目標(biāo)配置

構(gòu)建器必須有一個已定義的目標(biāo),此目標(biāo)會把構(gòu)建器與特定的輸入配置和項目關(guān)聯(lián)起來。

目標(biāo)是在 CLI 配置文件 ?angular.json? 中定義的。目標(biāo)用于指定要使用的構(gòu)建器、默認(rèn)的選項配置,以及指定的備用配置。建筑師工具使用目標(biāo)定義來為一次特定的執(zhí)行解析輸入選項。

?angular.json? 文件中為每個項目都有一節(jié)配置,每個項目的 ?architect ?部分都會為 CLI 命令(比如 ?build?、?test ?和 ?lint?)配置構(gòu)建器目標(biāo)。默認(rèn)情況下,?build ?命令會運行 ?@angular-devkit/build-angular:browser? 構(gòu)建器來執(zhí)行 ?build ?任務(wù),并傳入 ?angular.json? 中為 ?build ?目標(biāo)指定的默認(rèn)選項值。

{
  "myApp": {
    …
    "architect": {
      "build": {
        "builder": "@angular-devkit/build-angular:browser",
        "options": {
          "outputPath": "dist/myApp",
          "index": "src/index.html",
          …
        },
        "configurations": {
          "production": {
            "fileReplacements": [
              {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.prod.ts"
              }
            ],
            "optimization": true,
            "outputHashing": "all",
            …
          }
        }
      },
      …

該命令會給構(gòu)建器傳遞 options 節(jié)中指定的一組默認(rèn)選項。如果你傳入了 --configuration=production 標(biāo)志,它就會使用 production 備用配置中指定的值進(jìn)行覆蓋??梢栽诿钚兄袉为氈付ㄆ渌x項進(jìn)行覆蓋,還可以為 build 目標(biāo)添加更多備用配置,以定義其它環(huán)境,比如 stage 或 qa

目標(biāo)字符串

通用的 ?ng run? CLI 命令將以下格式的目標(biāo)字符串作為其第一個參數(shù)。

project:target[:configuration]

詳情

項目(project)

與此目標(biāo)關(guān)聯(lián)的 Angular CLI 項目的名稱。

目標(biāo)

angular.json 文件 architect 下的指定構(gòu)建器配置。

配置(configuration)

(可選)用于覆蓋指定目標(biāo)的具體配置名稱,如 angular.json 文件中的定義。

如果你的構(gòu)建器調(diào)用另一個構(gòu)建器,它可能需要讀取一個傳入的目標(biāo)字符串??梢允褂?nbsp;?@angular-devkit/architect? 中的工具函數(shù) ?targetFromTargetString()? 把這個字符串解析成一個對象。

調(diào)度并運行

建筑師會異步運行構(gòu)建器。要調(diào)用某個構(gòu)建器,就要在所有配置解析完成之后安排一個要運行的任務(wù)。

在調(diào)度器返回 ?BuilderRun ?控件對象之前,不會執(zhí)行該構(gòu)建器函數(shù)。CLI 通常會通過調(diào)用 ?context.scheduleTarget()? 函數(shù)來調(diào)度任務(wù),然后使用 ?angular.json? 文件中的目標(biāo)定義來解析輸入選項。

建筑師會接受默認(rèn)的選項對象來解析指定目標(biāo)的輸入選項,然后覆蓋所用配置中的值(如果有的話),然后再從傳給 ?context.scheduleTarget()? 的覆蓋對象中覆蓋這些值。對于 Angular CLI,覆蓋對象是從命令行參數(shù)中構(gòu)建的。

建筑師會根據(jù)構(gòu)建器的模式對生成的選項值進(jìn)行驗證。如果輸入有效,建筑師會創(chuàng)建上下文并執(zhí)行該構(gòu)建器。

你還可以通過調(diào)用 ?context.scheduleBuilder()? 從另一個構(gòu)建器或測試中調(diào)用某個構(gòu)建器。你可以直接把 ?options ?對象傳給該方法,并且這些選項值會根據(jù)這個構(gòu)建器的模式進(jìn)行驗證,而無需進(jìn)一步調(diào)整。
只有 ?context.scheduleTarget()? 方法來解析這些配置和并通過 ?angular.json? 文件進(jìn)行覆蓋。

默認(rèn)建筑師配置

讓我們創(chuàng)建一個簡單的 ?angular.json? 文件,它會把目標(biāo)配置放到上下文中。

你可以把這個構(gòu)建器發(fā)布到 npm,并使用如下命令來安裝它:

npm install @example/copy-file

如果用 ?ng new builder-test? 創(chuàng)建一個新項目,那么生成的 ?angular.json? 文件就是這樣的,它只有默認(rèn)的構(gòu)建器參數(shù)。

{
  // …
  "projects": {
    // …
    "builder-test": {
      // …
      "architect": {
        // …
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            // … more options…
            "outputPath": "dist/builder-test",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json"
          },
          "configurations": {
            "production": {
              // … more options…
              "optimization": true,
              "aot": true,
              "buildOptimizer": true
            }
          }
        }
      }
    }
  }
  // …
}

添加一個目標(biāo)

添加一個新的目標(biāo),來運行我們的構(gòu)建器以復(fù)制文件。該目標(biāo)告訴構(gòu)建器,復(fù)制 ?package.json? 文件。

你需要更新 ?angular.json? 文件,把這個構(gòu)建器的目標(biāo)添加到新項目的 ?architect ?部分。

  • 我們會為項目的 ?architect ?對象添加一個新的目標(biāo)小節(jié)
  • 名為 ?copy-package? 的目標(biāo)使用了我們的構(gòu)建器,它發(fā)布到了 ?@example/copy-file?。
  • 這個配置對象為我們定義的兩個輸入提供了默認(rèn)值:?source?(你要復(fù)制的現(xiàn)有文件)和 ?destination?(你要復(fù)制到的路徑)
  • 這些配置鍵都是可選的,但我們先不展開
{
  "projects": {
    "builder-test": {
      "architect": {
        "copy-package": {
          "builder": "@example/copy-file:copy",
          "options": {
            "source": "package.json",
            "destination": "package-copy.json"
          }
        },
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/builder-test",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json"
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "aot": true,
              "buildOptimizer": true
            }
          }
        }
      }
    }
  }
}

運行這個構(gòu)建器

要想使用這個新目標(biāo)的默認(rèn)配置運行我們的構(gòu)建器,請使用以下 CLI 命令。

ng run builder-test:copy-package

這將把 ?package.json? 文件復(fù)制成 ?package-copy.json?。

你可以使用命令行參數(shù)來覆蓋已配置的默認(rèn)值。比如,要改用其它 ?destination ?值運行,請使用以下 CLI 命令。

ng run builder-test:copy-package --destination=package-other.json

這將把此文件復(fù)制為 ?package-other.json? 而不再是 ?package-copy.json?。因為我們沒有覆蓋 source 選項,所以它仍然會從 ?package.json? 文件復(fù)制(提供給該目標(biāo)的默認(rèn)值)。

測試一個構(gòu)建器

對構(gòu)建器進(jìn)行集成測試,以便你可以使用建筑師的調(diào)度器來創(chuàng)建一個上下文,就像這個例子中一樣。

  • 在構(gòu)建器的源碼目錄下,你創(chuàng)建了一個新的測試文件 ?my-builder.spec.ts?。該代碼創(chuàng)建了 ?JsonSchemaRegistry?(用于模式驗證)、?TestingArchitectHost?(對 ?ArchitectHost ?的內(nèi)存實現(xiàn))和 ?Architect ?的新實例。
  • 我們緊挨著這個構(gòu)建器的 ?package.json? 文件添加了一個 ?builders.json? 文件,并修改了 ?package.json? 文件以指向它。

下面是運行此復(fù)制文件構(gòu)建器的測試范例。該測試使用該構(gòu)建器來復(fù)制 ?package.json? 文件,并驗證復(fù)制后的文件內(nèi)容與源文件相同。

import { Architect } from '@angular-devkit/architect';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { schema } from '@angular-devkit/core';
import { promises as fs } from 'fs';

describe('Copy File Builder', () => {
  let architect: Architect;
  let architectHost: TestingArchitectHost;

  beforeEach(async () => {
    const registry = new schema.CoreSchemaRegistry();
    registry.addPostTransform(schema.transforms.addUndefinedDefaults);

    // TestingArchitectHost() takes workspace and current directories.
    // Since we don't use those, both are the same in this case.
    architectHost = new TestingArchitectHost(__dirname, __dirname);
    architect = new Architect(architectHost, registry);

    // This will either take a Node package name, or a path to the directory
    // for the package.json file.
    await architectHost.addBuilderFromPackage('..');
  });

  it('can copy files', async () => {
    // A "run" can have multiple outputs, and contains progress information.
    const run = await architect.scheduleBuilder('@example/copy-file:copy', {
      source: 'package.json',
      destination: 'package-copy.json',
    });

    // The "result" member (of type BuilderOutput) is the next output.
    const output = await run.result;

    // Stop the builder from running. This stops Architect from keeping
    // the builder-associated states in memory, since builders keep waiting
    // to be scheduled.
    await run.stop();

    // Expect that the copied file is the same as its source.
    const sourceContent = await fs.readFile('package.json', 'utf8');
    const destinationContent = await fs.readFile('package-copy.json', 'utf8');
    expect(destinationContent).toBe(sourceContent);
  });
});

在你的倉庫中運行這個測試時,需要使用 ts-node 包。你可以把 ?index.spec.ts? 重命名為 ?index.spec.js? 來回避它。

監(jiān)視(watch)模式

建筑師希望構(gòu)建器運行一次(默認(rèn)情況下)并返回。這種行為與那些需要監(jiān)視文件更改的構(gòu)建器(比如 Webpack)并不完全兼容。建筑師可以支持監(jiān)視模式,但要注意一些問題。

  • 要在監(jiān)視模式下使用,構(gòu)建器處理函數(shù)應(yīng)返回一個 Observable。建筑師會訂閱這個 Observable,直到這個 Observable 完成(complete)為止。此外,如果使用相同的參數(shù)再次調(diào)度這個構(gòu)建器,建筑師還能復(fù)用這個 Observable。
  • 這個構(gòu)建器應(yīng)該總是在每次執(zhí)行后發(fā)出一個 ?BuilderOutput ?對象。一旦它被執(zhí)行,就會進(jìn)入一個由外部事件觸發(fā)的監(jiān)視模式。如果一個事件導(dǎo)致它重啟,那么此構(gòu)建器應(yīng)該執(zhí)行 ?context.reportRunning()? 函數(shù)來告訴建筑師再次運行它。如果調(diào)度器還計劃了另一次運行,就會阻止建筑師停掉這個構(gòu)建器。

當(dāng)你的構(gòu)建器通過調(diào)用 ?BuilderRun.stop()? 來退出監(jiān)視模式時,建筑師會從構(gòu)建器的 Observable 中取消訂閱,并調(diào)用構(gòu)建器的退出邏輯進(jìn)行清理。(這種行為也允許停止和清理運行時間過長的構(gòu)建。)

一般來說,如果你的構(gòu)建器正在監(jiān)視一個外部事件,你應(yīng)該把你的運行分成三個階段。

階段

詳情

運行

比如 webpack 編譯。這會在 webpack 完成并且你的構(gòu)建器發(fā)出 BuilderOutput 對象時結(jié)束。

監(jiān)視

在兩次運行之間監(jiān)視外部事件流。比如,webpack 會監(jiān)視文件系統(tǒng)是否發(fā)生了任何變化。這會在 webpack 重啟構(gòu)建時結(jié)束,并調(diào)用 context.reportRunning()。這樣就會再回到第 1 步。

完成

任務(wù)完全完成(比如,webpack 應(yīng)運行多次),或者構(gòu)建器停止運行(使用 BuilderRun.stop())。你的退出邏輯被調(diào)用了,建筑師也從你的構(gòu)建器的 Observable 中取消了訂閱。

總結(jié)

CLI 構(gòu)建器 API 提供了一種通過構(gòu)建器執(zhí)行自定義邏輯,以改變 Angular CLI 行為的新方式。

  • 構(gòu)建器既可以是同步的,也可以是異步的,它可以只執(zhí)行一次也可以監(jiān)視外部事件,還可以調(diào)度其它構(gòu)建器或目標(biāo)
  • 構(gòu)建器在 ?angular.json? 配置文件中指定了選項的默認(rèn)值,它可以被目標(biāo)的備用配置覆蓋,還可以進(jìn)一步被命令行標(biāo)志所覆蓋
  • 建議你使用集成測試來測試建筑師的構(gòu)建器。還可以用單元測試來驗證這個構(gòu)建器的執(zhí)行邏輯。
  • 如果你的構(gòu)建器返回一個 Observable,你應(yīng)該在那個 Observable 的退出邏輯中進(jìn)行清理


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號