iT邦幫忙

2024 iThome 鐵人賽

DAY 4
1
JavaScript

Signal API in Angular系列 第 4

Day 04 - Signal的技巧與陷阱

  • 分享至 

  • xImage
  •  

在第3天,我們介紹了Signal API、Computed Signal和Effect。今天,我們將介紹新用戶可能會遇到的Signal技巧和陷阱。

應用 equal 選項以提升性能

function signal(
 initialValue: T,
 options?: CreateSignalOptions<T>
): WritableSignal<T>;

interface CreateSignalOptions {
 equal?: ValueEqualityFn<T>;
}

type ValueEqualityFn = (a: T, b: T) => boolean

在初始化Signal時,可以將equal選項傳遞給函數的第二個參數。equal選項接受一個比較函數,用於判斷當前和新的Signal值是否相同。如果不使用equal選項,則使用默認實現(===)。

type Person = {
   firstName: string;
   lastName: string;
} 

const lowerCaseFullname = ({ firstName, lastName }: Person) => `${firstName}${lastName}`.toLowerCase();
person = signal<Person>({ firstName: 'Mary', lastName: 'Peterson' }, {
  Equal: (a, b) => lowerCaseFullName(a) === lowerCaseFullName(b)
});
person2 = signal<Person>({ firstName: 'Mary', lastName: 'Peterson' });
pColor = signal<'yellow' | 'goldenrod'>('goldenrod');
pColor2 = signal<'yellow' | 'goldenrod'>('goldenrod');
update() {
    this.person.set({ firstName: 'MaRy', lastName: 'PEterSON' });
    this.person2.set({ firstName: 'MaRy', lastName: 'PETerSON' });
}

person signal對其名字和姓氏執行不區分大小寫的比較。person2 signal使用默認實現來比較對象引用。update方法用混合大小寫的名稱覆蓋兩個信號。

constructor() {
    effect(() => {
        console.log('person 2', this.person2().firstName, this.person2().lastName);
        const color2 = untracked(this.pColor2);
        this.p2Color.set(color2 === 'yellow' ? 'goldenrod' : 'yellow');
    }, { allowSignalWrites: true });

    effect(() => {
        console.log('person 1', this.person().firstName, this.person().lastName);
        const color = untracked(this.pColor);
        this.pColor.set(color === 'yellow' ? 'goldenrod' : 'yellow');
    }, { allowSignalWrites: true });
}

第一個effect跟踪person2 signal並切換p2Color signal的背景顏色。第二個effect跟踪person signal並切換pColor信號的背景顏色。

@let p = person();
@let p2 = person2();
<p [style.background]="pColor()">Person: {{ p.firstName }} {{ p.lastName }}</p>
<p [style.background]="p2Color()">Person: {{ p2.firstName }} {{ p2.lastName }}</p>
<button (click)="update()">Click me to update signals<button>

當用戶點擊按鈕時,person signal不會更新,因為相等檢查函數返回true。因此,背景顏色不會改變。另一方面,person2 signal的object reference在每次更新後都不同;因此,默認相等函數返回false,其背景顏色會改變。
equal選項可以提高OnPush組件的性能,當signal持有object時。自定義相等函數比較屬性值,然後返回結果。當舊值和新值被認為相同時,組件不會被標記為髒,並且變化檢測(change detection)不會重新渲染它。

computed signal也有equal選項

類似於內建signal,computed signal也有equal選項。Computed signal可以根據自定義相等函數的結果有選擇地從其他信號重新計算。

count = signal(0);
oddCount = computed(() => this.count(),  {
   equal: (_, b) => b % 2 === 0,
});
update() {
    .....
    this.count.update((prev) => prev + 1);
}
<p>{{ oddCount() }}</p>

oddCount signal只有在count信號是奇數時才會重新計算新值。當count為 0,用戶點擊更新按鈕時,count增加至 1, oddCount的相等檢查函數為false,oddCount改變為1。當用戶再次點擊時,count變為2,相等檢查函數返回true。oddCount不會重新計算,範本繼續顯示緩存值。當用戶第三次點擊時,oddCount為3,範本顯示新值。

Computed signal不能寫入其他signal

oddCount = computed(() => {
    this.person.set({ firstName: 'John', lastName: 'Wayne' });  // it is not allowed
    return this.count();
}, { equal: (_, b) => b % 2 === 0,

Computed signal不能寫入其他signal。它不應該包含更新其他signal或組件實例成員的邏輯。

Computed signal的依賴項是動態的

count = signal(0);
showCount = signal(false);
showCountString = computed(() => {
     if (showCount()) {
          return `Count: ${this.count()}`; 
     } 
     return 'Nothing to show';
});

showCount為 false時,showCountString返回「Nothing to show」,且不讀取count。當count增加時,不會導致showCountString重新計算。
showCount切換為true時,showCountString會執行if分支並顯示count的值。當count增加時,showCountString會派生一個包含新count的新字符串。
showCount回復為false時,count的依賴關係再次被移除,且當count增加時,showCountString不會重新計算。

Effect

預設情況下,effect不允許寫入其他signal。為了繞過這個錯誤,可以將allowSignalWrites選項傳遞給 effect。然而,這不是最佳實踐(best practice),因為effect的設計目的是執行非反應式代碼,例如記錄。

effect(() => {
        console.log('person 1', this.person().firstName, this.person().lastName);
        const color = untracked(this.pColor);
        this.pColor.set(color === 'yellow' ? 'goldenrod' : 'yellow');
    }, { allowSignalWrites: true });
}

在將{ allowSignalWrites: true }傳遞給effect後,pColor信號可以覆寫coloruntracked(this.pColor)不僅返回其值,而且還確保pColor不是effect的依賴項。當pColor覆寫 color時,effect不會運行,避免了無限循環。

結論:

  • signal和computed signl具有equal選項,可以自訂邏輯來比較當前值和新值
  • computed signal無法更新其他signal
  • computed signal的依賴性是動態的
  • Effect無法更新其他signal,除非allowSignalWrites選項為true

這結束了鐵人賽的第4天。

參考:

Stackblitz Demo: https://stackblitz.com/edit/stackblitz-starters-skvuez?file=src%2Fmain.ts


上一篇
Day 03 - Angular 的 Signal API
下一篇
Day 05 - Observable 與 Signal 的互通性 第 1 部分 - toSignal
系列文
Signal API in Angular39
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言