Angular Signals 是甚麼? 一篇文章讓你清楚前因後果
開始之前
Angular 從v16推出 dev-preview 到 v17的穩定版的新功能 - Signals , computed() 還有 effect()。為Angular的變更機制投下震撼彈,本篇文章帶你快速實戰,並解釋功能的來龍去脈。
Signals
counter = signal(0);
沒錯,定義一個signal就是這麼簡單
更嚴謹的寫法可以透過泛型將資料類別傳進去,回傳的型別跟上面一樣是WritableSignal
counter: WritableSignal<number> = signal<number>(0);
Signal() 及 Signal.set()
這次我們使用一個常見的範例 "計數器"
透過呼叫 add() function 將計數器加一,並將資料顯示在畫面上
add(): void {
this.counter.set(this.counter() + 1);
}
count {{ counter() }}
<button (click)="add()"> Add 1 </button>
另外,除了 Signal.set(),也可以透過 Signal.update() api 將更新資料
若將 add() function 改寫成 update 方式更新的程式碼如
add(): void {
this.counter.update((count) => count + 1);
}
computed() 及 effect()
延續範例
假設計數的是商品數量,可以透過 computed() 將數量計算為總金額
並在數量增加時,同步更新金額到畫面上
total = computed(() => this.counter()*1980);
值得注意的是,computed 產生的 total 是 Readonly 的 Signal
更新機制跟隨 this.counter signal 改變
<div> 總金額:${{ total() }} </div>
也可以透過 effect() 在商品增加時觸發其他事件
constructor() {
...
effect(() => console.log(`商品已加入,目前數量${this.counter}`));
...
}
因為 DI 的原故, effect() 需要寫在建構子內,否則Angular會提示錯誤
asReadonly()
Signal 大致分為 Writable 以及 Readonly(也就是Signal type) writable 是不管從何存取,都可以任意更改資料。
實際使用像是透過 service 存取資料時,我們可以將signal資料透過 asReadonly api 提供給其他程式,保持寫入資料的控管。
getCountSignal(): Signal<number> {
return this.counter.asReadonly();
}
結束了...嗎?
以上就是 Signals 相關的功能與簡單實作範例,是不是很簡單呢?
透過 signal() 建立實體,並呼叫 .set() 或 .update() 更新內容,取代舊有的 data-binding 方式以更可控的方式控制資料流
不過仔細想想,好像哪邊怪怪的...
「難道原本 data-binding 的寫法不香嗎?」
過去的版本,Angular 仰賴 NgZone (zone.js) 管控網頁的變更機制
在事件觸發後 NgZone 首先將事件攔截,並嘗試偵測相關資料是否變更,最後重新渲染畫面。整套流程下來事件的目的達到了,但影響的範圍可能過多。
另外對開發者而言從 "事件觸發" 到 "畫面渲染" 在舊有版本中,Angular 透過 NgZone 機制,將流程包在語法糖裡。
開發者通常只知道 「資料變更,畫面就會自動渲染了」,而對於極少數,資料有變更畫面卻沒有重新渲染的情形,常常會不知所云
這種情形的解法通常是把元件改成 pure component ,並以手動的方式觸發變更機制。
Signal 以及 OnPush
針對改善變更機制效能的新解方,如果放在 ChangeDetectionStrategy.OnPush 的元件內,Signal 的內容更新依然需要手動觸發嗎?
答案是: 不需要。
在啟用 OnPush 的元件內,所有的 Signal 或延伸的 ReadOnly Signal 皆會自動更新,畢竟兩種的觸發監控機制已經不同了。
總結與心得
Signal 提供一種能更完善管控資料流,並將變更機制侷限在範圍內的機制
雖然寫起來比舊版本的方式麻煩,但對於改善效能與減少資源浪費的課題,不乏是一個好的解決方案
資料與參考來源:
Angular v19 ChangeDetectorRef官方文件
作者:
RayLiu