《Angular 在生產環境》系列的第 4 部分
在處理較大型的 Angular 應用程式後,我注意到元件很少會在一夜之間變得難以維護,它是逐漸發生的。當你加入一個功能又另一個功能、新的對話框、新的 API 呼叫以及新的權限檢查時。沒有人會刻意決定建立一個有上千行的元件。它只是因為加入一點邏輯總是感覺比建立另一個抽象更容易而發生。一開始,這似乎不是問題,應用程式仍然可以運作。但最終你開啟檔案時,會發現你不再看到一個 UI 元件。你看到的是一個整個功能被打包到單一類別中。通常就是從這個點開始,每一個新的變更都花費比應有的時間更長。
元件大小是症狀,而不是問題
我過去曾犯過的一個錯誤是透過行數來衡量元件。擁有 800 行的元件不一定不好。同樣地,擁有 150 行的元件也不一定好。重要的是責任。我曾見過相對小的元件負責:
- 載入資料
- 轉換資料
- 驗證表單
- 處理權限
- 開啟對話框
- 管理應用程式狀態
- 格式化數值
- 對路由變更做出反應
- 呼叫多個 API
在技術上,它們不是很大,但在架構上,它們正在執行應用程式中幾個不同部分的工作。
現在,當我開啟一個元件時,我通常會問自己這個元件是否有明確的責任,如果答案不明顯,那通常表示元件已經開始朝錯誤的方向成長。
商業邏輯逐漸接管
大多數過大的元件一開始並不是從商業邏輯開始。它是一次一個功能地加入。像這樣感覺非常合理:
loadUsers() {
this.userService.getUsers().subscribe(users=> {
this.users=users;
});
}
進入全螢幕模式 退出全螢幕模式
幾週後,這個方法已經演變為:
loadUsers(){
this.userService.getUsers().subscribe(users=> {
this.users=users.filter(user=> user.active);
this.totalRevenue=users.reduce(
(sum, user) => sum + user.revenue,
0
);
this.canExport=
users.length < this.subscription.maxUsers;
this.chartData = this.buildChart(users);
});
}
進入全螢幕模式 退出全螢幕模式
這裡沒有任何地方一定是錯的。每一個新需求在加入時都有意義。問題是元件已經悄悄地不再僅負責呈現使用者。現在它正在過濾資料、計算商業指標、檢查訂閱限制並準備圖表資料。
隨著專案成長,這些方法通常會持續擴展。最終,改變一塊邏輯意味著要理解周圍發生的所有事情。現在,每當我注意到商業規則累積在元件內部時,我會試著將它們移到更能反映其目的的地方。
而不是這樣:
loadUsers() {
this.userService.getUsers().subscribe(users => {
this.users = users.filter(user => user.active);
this.totalRevenue = users.reduce(
(sum, user) => sum+user.revenue,
0
);
});
}
進入全螢幕模式 退出全螢幕模式
我更喜歡類似於:
loadUsers() {
this.dashboardService
.getDashboardData()
.subscribe(data => {
this.user = data.users;
this.totalRevenue = data.totalRevenue;
});
}
進入全螢幕模式 退出全螢幕模式
元件不會因為行數變少而變得更簡單。它變得更簡單是因為它不再擁有商業決策。它的任務只是顯示資訊。
元件應該協調,而不是實作一切
隨著時間推移,我開始將 Angular 元件視為協調者。它們的責任是連接應用程式的不同部分,而不是自己實作每一部分。
例如,一個儀表板元件可能會請求資料、將資料傳遞給子元件、對使用者互動做出反應並觸發導航。這已經是足夠的責任。它不需要知道,例如發票如何計算、權限如何評估、報表如何產生或匯出如何格式化。
每當我看到這些方法一起存在於同一個元件中:
calculateInvoice()
validatePermissions()
buildChartData()
generateStatistics()
formatExport()
sendNotification()
進入全螢幕模式 退出全螢幕模式
我會立即開始詢問這些責任是否屬於其他地方。
目標不是為了「這樣比較乾淨」而將程式碼移到服務中。目標是讓每個檔案負責解決一種類型的問題。這通常會讓未來的變更變得更容易,因為你不再害怕破壞不相關的功能。
單一畫面不代表單一元件
我早期的一個誤解是假設每個頁面都應該對應到一個大型元件。畢竟,它是一個畫面。為什麼要分割它?然後這些頁面開始成長。想像一個儀表板包含:
├─ 使用者摘要
├─ 銷售圖表
├─ 最近訂單
├─ 通知
├─ 活動摘要
├─ 快速操作
└─ 報表
進入全螢幕模式 退出全螢幕模式
從使用者的角度來看,這是一個頁面。但從開發的角度來看,它是幾個獨立的功能。每個功能最終可能需要
- 自己的 API 請求
- 自己的載入狀態
- 自己的權限
- 自己的互動
- 自己的測試。
僅僅因為它們出現在同一個畫面上而將所有內容保留在單一元件中,通常會造成不必要的耦合。將功能分割到專用元件中不只是減少檔案大小。它讓所有權變得更加明確。
處理通知的人不應該需要理解報表如何產生。同樣地,改變銷售圖表不應該冒著破壞最近訂單的風險。隨著應用程式成長,這些邊界變得越來越有價值。
大型模板通常隱藏相同的問題
類似的情況也發生在模板中。一開始,HTML 很容易理解。然後出現一些條件。幾個迴圈。一些權限檢查。根據使用者角色顯示不同的佈局。最終,你會發現自己捲動數百行只是為了理解頁面的單一部分如何呈現。
一個簡化的範例可能看起來像這樣:
</app-admin-panel>
<app-basic-panel
*ngIf="dashboardMode === 'basic'">
</app-basic-panel>
</div>
<app-user-panel
*ngIf="!user.permissions.includes('admin')">
</app-user-panel>
</div>
進入全螢幕模式 退出全螢幕模式
這些條件單獨來看都沒有錯。問題是每一個新需求都會在模板中加入另一個分支。最終,理解頁面顯示什麼變得幾乎和理解元件本身一樣困難。每當一個區段開始擁有自己的邏輯時,我通常會停止問「我可以在這裡繼續加入條件嗎?」而開始問「這是否應該成為自己的元件?」多數情況下,答案是肯定的。
輸入和輸出可以揭示設計問題
在元件之間傳遞資料是完全正常的。這正是 Angular 元件的用途。但隨著時間推移,我注意到一些有趣的事情。當一個元件開始接收太多輸入時,通常是因為它負責太多。例如:
@Component({
selector: 'app-dashboard-card'
})
進入全螢幕模式 退出全螢幕模式
[user]="user"
[permissions]="permissions"
[settings]="settings"
[statistics]="statistics"
[reports]="reports"
[loading]="loading"
[theme]="theme"
[subscription]="subscription"
(refresh)="refresh()"
(delete)="deleteUser()"
(export)="exportDat()">
</app-dashboard-card>
進入全螢幕模式 退出全螢幕模式
沒有任何神奇數字會讓這變成錯誤。但每當我發現自己將幾乎整個頁面狀態傳遞到單一子元件時,我通常會暫停。有時子元件不再真正是一個元件。它是另一個值得進一步分割的功能。輸出也適用同樣的原則。如果一個子元件發出五或六個不同的事件,值得詢問它是否試圖做太多不同的事情。
表單傾向於比其他一切成長得更快
如果有一個地方最常出現過大的元件,那就是表單。它們從小開始,有幾個欄位、一個提交按鈕。然後商業需求到來。條件欄位、動態驗證、基於角色的可見性、自動儲存、附件、外部查詢...不久之後,負責表單的元件也包含了應用程式的大部分商業規則。我學會了不要與此抗爭。大型表單本質上就是複雜的。有幫助的是圍繞它們分離責任。例如:
- 一個元件呈現個人資訊區段
- 另一個處理地址
- 另一個管理附件
- 共享驗證存在於專用服務中
- 可重複使用的控制項成為獨立元件
表單對使用者來說仍然表現為單一體驗。但程式碼庫變得更容易導航。
分割元件的最佳時機比你想像的更早
我花了一段時間才學到的一個教訓是,元件提取不應該是最後手段。很長一段時間,我等到元件變得痛苦才分割它。現在我試著更早做這件事。不是因為我喜歡建立更多檔案。因為較小、專注的元件傾向於獨立演進。這意味著更少的合併衝突、更簡單的審查、更清晰的所有權,以及在變更現有功能時更少的恐懼。諷刺的是,早期分割元件往往會導致整體編寫更少的程式碼。重複更少、分支更少,以及無關邏輯意外連接的地方更少。
結論
大型 Angular 元件很少因為 Angular 鼓勵不良實踐而變得難以維護。它們變得難以維護是因為功能不斷累積在同一個地方。每一個個別變更都感覺合理。綜合起來,它們慢慢將 UI 元件轉變為負責整個功能的東西。現在,我不會根據大小來決定分割元件。我會在注意到它解決多個不同問題時這樣做。這通常是未來變更會比需要的更困難的第一個跡象。保持責任小不僅可以改善可讀性。它還讓測試、除錯和加入新功能變得壓力更小。
本系列的下一篇文章
第 5 部分:只在生產環境中出現的 Angular 訂閱問題
本系列的先前文章
第 1 部分:為什麼 Angular 應用程式會隨著成長而變慢
第 2 部分:Angular 套件大小只是效能的一部分
第 3 部分:讓大型應用程式感覺緩慢的 Angular 變更偵測錯誤
感謝閱讀!如果你的 Angular 應用程式隨著時間累積了效能或可維護性問題,這就是我幫助團隊處理的工作類型:除錯現有程式碼庫、改善效能以及增量重構。你可以在我的 Dev 個人資料中找到我的 UpWork 連結。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.