天天看點

NgRx Selector 的 Memoization 特性學習筆記Selecting Feature States

在計算機程式設計領域中,memoization 或 memoisation 是一種優化技術,主要用于通過存儲昂貴的函數調用的結果并在再次出現相同的輸入時傳回緩存的結果來加速計算機程式。

Memoization 也已用于其他上下文(以及速度增益以外的目的),例如在簡單的互相遞歸下降解析中。盡管與緩存有關,但記憶化是指此優化的特定情況,将其與緩存或頁面替換等緩存形式區分開來。在某些邏輯程式設計語言的上下文中,記憶化也稱為 Tabling.

記憶功能“記住”與某些特定輸入集相對應的結果。使用記住輸入的後續調用傳回記住的結果而不是重新計算它,進而消除了使用給定參數調用的主要成本,除了第一次使用這些參數調用函數。記住的關聯集可以是由替換算法控制的固定大小的集,也可以是固定集,這取決于函數的性質及其用途。一個函數隻有在引用透明的情況下才能被記憶;也就是說,僅當調用該函數與用其傳回值替換該函數調用具有完全相同的效果時。 (然而,存在此限制的特殊情況例外。)雖然與查找表相關,但由于記憶在其實作中經常使用此類表,是以記憶會根據需要即時而不是提前填充其結果緩存。

記憶化是一種以犧牲空間成本的方法來降低函數時間成本的優化方式;也就是說,記憶化的函數會針對速度進行優化,付出的代價是對計算機記憶體空間的更高使用率。算法的時間/空間“成本”在計算中有一個特定的名稱:計算複雜度。所有函數在時間(即它們需要時間來執行)和空間上都具有計算複雜性。

什麼是 NgRx selector

選擇器是用于擷取存儲狀态切片( store state slices)的純函數。@ngrx/store 提供了一些幫助函數來優化這個選擇。

Selector 也符合單一職責及 Single responsibility,一個 selector 隻 touch State 的一個片段。

使用 createSelector 和 createFeatureSelector 函數時,@ngrx/store 會跟蹤調用選擇器函數的最新參數。 因為選擇器是純函數,當參數比對時可以傳回最後一個結果,而無需重新調用選擇器函數。這可以提供性能優勢,特别是對于執行昂貴計算的選擇器。這種做法被稱為記憶。

換言之,我們雖然實作了 Selector,但是運作時,這些 selector 可能隻會被執行一次,因為如果輸入參數相同,NgRx 架構會使用緩存的結果直接傳回給調用者,而不會重複調用我們的 Selector.

例子:Using a selector for one piece of state

import { createSelector } from '@ngrx/store';

export interface FeatureState {

 counter: number;

}

export interface AppState {

 feature: FeatureState;

export const selectFeature = (state: AppState) => state.feature;

export const selectFeatureCount = createSelector(

 selectFeature,

 (state: FeatureState) => state.counter

);

console.log('ok');

例子:Using selectors for multiple pieces of state

createSelector 可用于基于相同狀态(State)的多個切片從狀态中選擇一些資料。

createSelector 函數最多可以使用 8 個選擇器函數,用于更完整的狀态選擇。

例如,假設您在狀态中有一個 selectedUser 對象。 您還有一個 allBooks 書籍對象數組。

并且您想顯示目前使用者的所有書籍。

您可以使用 createSelector 來實作這一點。即使您在 allBooks 中更新它們,您的可見書籍也将始終是最新的。 如果選擇了一個,它們将始終顯示屬于您的使用者的書籍,如果沒有選擇使用者,它們将顯示所有書籍。

CreateSelector 裡傳入純函數。

export interface User {

 id: number;

 name: string;

export interface Book {

 userId: number;

 selectedUser: User;

 allBooks: Book[];

export const selectUser = (state: AppState) => state.selectedUser;

export const selectAllBooks = (state: AppState) => state.allBooks;

export const selectVisibleBooks = createSelector(

 selectUser,

 selectAllBooks,

 (selectedUser: User, allBooks: Book[]) => {

   if (selectedUser && allBooks) {

     return allBooks.filter((book: Book) => book.userId === selectedUser.id);

   } else {

     return allBooks;

   }

 }

Selecting Feature States

createFeatureSelector 是傳回頂級功能狀态(Top Level Feature State)的便捷方法。它為狀态的特征切片傳回一個類型化的選擇器函數。

import { createSelector, createFeatureSelector } from '@ngrx/store';

export const featureKey = 'feature';

export const selectFeature = createFeatureSelector(featureKey);

Resetting Memoized Selectors

通過調用 createSelector 或 createFeatureSelector 傳回的選擇器函數最初的記憶值為 null。 在第一次調用選擇器後,其記憶值存儲在記憶體中。 如果随後使用相同的參數調用選擇器,它将傳回記憶值。 如果随後使用不同的參數調用選擇器,它将重新計算并更新其記憶值。

一個例子:

export interface State {

 counter1: number;

 counter2: number;

export const selectCounter1 = (state: State) => state.counter1;

export const selectCounter2 = (state: State) => state.counter2;

export const selectTotal = createSelector(

 selectCounter1,

 selectCounter2,

 (counter1, counter2) => counter1 + counter2

); // selectTotal has a memoized value of null, because it has not yet been invoked.

let state = { counter1: 3, counter2: 4 };

selectTotal(state); // computes the sum of 3 & 4, returning 7. selectTotal now has a memoized value of 7

selectTotal(state); // does not compute the sum of 3 & 4. selectTotal instead returns the memoized value of 7

state = { ...state, counter2: 5 };

selectTotal(state); // computes the sum of 3 & 5, returning 8. selectTotal now has a memoized value of 8

選擇器的記憶值無限期地保留在記憶體中。 例如,如果記憶值是不再需要的大資料集,則可以将記憶值重置為 null,以便可以從記憶體中删除大資料集。 這可以通過調用選擇器上的 release 方法來完成。

selectTotal(state); // returns the memoized value of 8

selectTotal.release(); // memoized value of selectTotal is now null

繼續閱讀