在上一篇中介紹了在開發公司的組件庫以前須要的準備工做,主要包括:需求評審、調研、組件設計和提出 issue。 本篇主要介紹在具體的開發環節須要注意哪些事項。html
組件開發的時候須要不斷測試當前已開發的功能,這裏建議單獨配置一個私人 demo 項目用於本地開發。建立組件對應的初始庫(詳見Angular 的建立庫),執行ng build [庫名稱]
生成本地的組件庫。配置完成後,使用npm link
連接剛纔打包好的組件庫和 demo 項目,而後在 demo 項目的angular.json
中添加preserveSymlinks: true
(以下),完成建立開發環境。這是爲了防止本地庫的引用依賴錯誤。node
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"my-project": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "target",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
"polyfills": "src/showcase/polyfills.ts",
"preserveSymlinks": true,
...
複製代碼
組件開發分爲自行開發組件和基於第三方組件的二次封裝,這兩種在開發時略有不一樣。前者一般是從自身的業務抽離而來好比標註組件,後者一般會有比較成熟的開源方案可供使用好比表格、圖譜等。一般自行開發組件的成本會比較高,在業務相關性低,交互邏輯複雜,有比較成熟的開源方案的狀況下,最好基於第三方組件作二次封裝。react
二次封裝一般有兩種方式,能夠用設計模式來表示:裝飾者模式與中介者模式。git
裝飾者模式是指在不改變原類及不使用繼承的狀況下,動態的擴展一個對象的功能。一般來講就是用裝飾器來實現一些原第三方組件作不到的功能。這種模式並不經常使用,這裏很少介紹。github
咱們一般使用的是中介者模式。中介者模式是指用一箇中介對象來封裝一系列的對象交互,中介者使各對象不須要顯式地相互引用,從而使其耦合鬆散,並且能夠獨立地改變它們之間的交互。 好比下面的例子中,TableComponent
充當了一箇中介者,將輸入的data
屬性轉化爲第三方組件st
能夠接受的數據類型,所以能夠不加修改的直接引用原來的組件。typescript
// table.component.ts
export class TableComponent implements OnChanges {
@Input()
data: Data[];
_data: STData[];
get _data(): STData[] {
return this.data.map((item, index) => {
if (this.selectedRows && this.selectedRows.includes(index)) {
return {
checked: true,
...item
};
}
return item;
});
}
}
複製代碼
<st [data]="_data" ...></st>
複製代碼
基於第三方組件的開發除了上述兩種模式,其實還能夠用繼承來實現。Angular 的組件是使用@Component
裝飾器來實現的,所以組件的繼承等同於 TypeScript 中帶有類裝飾器的類的繼承。如下是一個組件繼承的簡單例子。像DerivedComponent
同樣,派生類若是擁有本身的@Component
,其實至關於被兩個@Component
裝飾器修飾過構造函數。根據 TypeScript 的裝飾器組合原則,最外層的@Component
被調用後對構造函數的修改會覆蓋裏層的@Component
,最終生效的只是派生類的@Component
中傳入的參數。所以,若是須要改變@Component
中的參數,則須要傳入組件所需的所有參數。特別是providers
中組件級的依賴,也是須要傳入的,但第三方組件每每不會導出它的組件級依賴,就會給開發形成困難。而若是直接繼承基類,像DirectDerivedComponent
,該組件會繼承@Component
中全部的元參數和 BaseComponent 提供的類型與構造函數。可是由於不能自定義組件的元參數,致使這種使用方式受限不少。npm
在 TypeScript 裏,當多個裝飾器應用在一個聲明上時會進行以下步驟的操做:json
- 由上至下依次對裝飾器表達式求值。
- 求值的結果會被看成函數,由下至上依次調用。
// BaseComponent 基類組件
@Component({
selector: 'app-base',
templateUrl: './base.component.html',
styleUrls: ['./base.component.less'],
providers: [BaseService]
})
export class BaseComponent {
construct(baseService: BaseService) {}
}
// DerivedComponent 派生類組件
@Component({
selector: 'app-derived',
templateUrl: './derived.component.html',
styleUrls: ['./derived.component.less'],
providers: [BaseService] // 裝飾器中的元參數會覆蓋基類組件,所以須要從新提供 providers
})
export class DerivedComponent extends BaseComponent {}
// DirectDerivedComponent 直接繼承的派生類組件
export class DirectDerivedComponent extends BaseComponent {}
複製代碼
以上分析說明,在 Angular 中使用繼承的方式開發組件是一件受限不少且複雜的事情。並且 Angular 官方也沒有提供關於組件繼承相關的文檔。只能從源碼分析猜想組件繼承的機制。所以,全部這些其實只是想說明一件事:儘可能避免使用組件繼承。若是你足夠細心,會發如今 Angular 在DI 實戰一節有此說明。(不只 Angular, React 官方也強調避免使用組件繼承。)固然也不是徹底不能使用,當組件遵循第三方組件的大多數交互邏輯只是在視圖層有所區別時,才建議考慮使用繼承的方式實現。segmentfault
想了解更多關於組件繼承,這篇文章可能會有幫助。設計模式
若是你肯定引入第三方組件,此時很容易忽略的一個問題就是第三方組件的 npm 包依賴問題。請選擇第三方組件一個合適版本引入,確保兼容公司組件庫的核心依賴庫。
另外值得說明的是,在開發庫的過程當中,請在組件庫根目錄的 package.json 中的 devDependencies
中安裝全部的同級依賴。而在目標庫的 package.json 中使用 peerDependencies
說明同級依賴。關於爲什麼這樣作的緣由能夠參考 Angular 文檔開發 Angualr 庫與 ng-packagr 的文檔dependencies。
在一切準備就緒以後,先不要馬上投入組件具體功能的實現中。我建議第一步實現一個最小的 demo,實現最基本最重要的功能,確保咱們的思路是可行的,至關於 POC。好比我在開發 table 組件的時候,初版的設計是這樣的。table 列是經過 jsx 語法來實現自定義的,列的 API 設計也依賴於此。若是你以前用 React 的話會以爲這看起來是很是天然的設計。
@Component({
selector: 'table-demo',
template: ` <bx-table [data]="data" [columns]="columns" ></bx-table> `
})
export class TableDemo {
// 表格行配置
columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name'
},
{
title: 'Action',
key: 'action',
template: (value, record, index) =>
`<a>Action 一 {{ record.name }}</a><nz-divider nzType="vertical"></nz-divider><a>Delete</a>`
}
];
}
複製代碼
然而 Angular 是不支持這種語法的,這是 React 的特性。致使我只能另尋他法。最終選擇仿照 delon 實現自定義模板,而後從新改寫 API。因此這個環節能夠防止咱們犯那些很嚴重的錯誤。每每咱們第一次設計的 API 有諸多不合理之處,這是一個很好的機會進行調整,同時成功的 demo 也讓咱們有信心繼續開發下去。
接下來就須要咱們慢慢逐步地實現 issue 中所列的功能了,在這期間可能會遇到不少意想不到的困難致使開發須要延期,也可能會須要調整不少次 API 以及增刪一些功能。咱們是組件的設計者也是開發者同時也是使用者,咱們應該對它有本身的想法,開發時不斷和你們溝通,能夠據理力爭也能夠虛懷若谷,對它負責到底。這會是一個比較漫長的過程。咱們會像西西弗斯同樣,不斷踩坑爬坑,踩坑爬坑,踩坑爬坑。由於造輪子會比用輪子複雜許多,可能常常會碰到咱們知識的邊界,會比較痛苦。可是當你最痛苦的時候,就是你進步最快的時候(也是頭髮掉的最快的時候)。
在開發 angular 庫的過程當中,必然要進行打包(ng build
)。因爲 angular 默認的打包工具(@angular-devkit/build-ng-packagr
)來自於ng-packagr,這裏把我在使用ng-packagr
時遇到的一個比較常見的坑和你們分享一下。
咱們常常須要用別名指代某個位置的文件,好比須要用@component/*
指代node_modules/component/dist/*
。像這樣的路徑映射 typescript 是經過 tsconfig.json 的compilerOptions
中baseUrl
與paths
來支持的。在 Angular 項目中會使用 angular.json 中指定的 tsconfig 文件,而 VS Code 編輯器中會使用根目錄下的 tsconfig.json 文件。所以有時會出現編輯器不能正確解析路徑映射的狀況,可是 angular 是能夠正常構建的。(來源見這裏)
在開發庫的時候,通常來講路徑映射應該在主目錄下的 tsconfig.json
中配置,但有時候須要在庫級別進行路徑映射,也就是在 tsconfig.lib.json
進行配置。這裏值得注意的是,paths
指向的路徑必須是已編譯的文件所在路徑,並且除了目標自己,也須要對目標的子文件進行映射。例如同處於projects
目錄下的lib-a
和lib-b
,lib-b
引入了lib-a
,此時假如咱們必需要在lib-b
裏進行路徑映射,那麼lib-b/tsconfig.lib.json
須要配置大體以下。(關於此問題的討論詳見這裏)
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"baseUrl": "./",
"paths": {
"lib-a": ["../../dist/lib-a"],
"lib-a/*": ["../../dist/lib-a/*"]
}
}
}
複製代碼
經歷一段時間辛苦的開發以後,組件終於能夠投入使用了。在組件發佈以前,請檢查如下工做是否完成。
.gitignore
使其再也不忽略相應的編譯文件,提交編譯文件。接下來只須要按照流程進行發佈便可。
咱們的項目通常採用 gitflow 做爲 git 工做流。它擁有兩個長期分支 develop 與 master 和一些臨時分支 feature分支、release 分支、hotfix 分支等。平常的開發在臨時分支上進行,開發完畢後合併入 develop 分支,更新版本的時候經過 release 分支分別合併入 develop 分支和 master 分支。下圖是 gitflow 的工做流圖。(瞭解更多移步這裏。)
不過組件庫項目開發通常採用github flow。github flow 與 gitflow 不一樣的是移除了 develop 分支,只需維護一個長期分支 master(實際上它更相似於 github flow,見下圖)。所以組件功能的更新不須要通過合入 develop 分支與切換 release 分支等操做,直接合入主分支便可。等到變更積累到必定量,會在主分支切出 release 分支,修改版本號而後打上 tag 進行發佈。如此設計緣由在於 gitflow 的工做流使用 develop (或 release 分支)來進行測試環境的更新,master 來進行生產環境的更新,適合於業務開發。可是組件庫不依賴於測試環境,所以也就無需 develop 分支。常見的開源項目通常都採用 github flow 的形式管理工做流。
至此全部的工做就都結束了,停下來歇一歇,總結一下開發過程當中的收穫,和同事們分享你的成長吧!
相關連接: 如何開發公司組件庫(上)