記憶化狀態reselect的實踐與思考

緩存數據

咱們寫後端代碼的時候, 爲了在密集查詢時候儘可能少的去操做數據庫, 一個方法就是在中間插入一個緩存層,當查詢的數據能夠在緩存中查詢到時候就直接用緩存裏面的數據,若是數據改變了再去修改緩存裏面的數據。javascript

隨着前端近些年的飛速發展,前端項目(特別在spa)應用中,前端須要儲存愈來愈多的數據內容,就像一個小型的數據庫。前端

對前端而言這些數據咱們都是直接存在內存中的, 因此查詢對咱們來講並非什麼難事, 可是頁面上要展現的內容每每是須要基礎數據通過複雜計算獲得的結果,每次從新計算都會消耗大量的性能。一種方法是咱們去創建一些存貯結果變量, 每次原始值發生改變時候咱們再去從新計算而後從新賦值。可是咱們又要去維護這些新的變量,在代碼維護和後期功能迭代時候有可能會給咱們帶來很大的困擾。可是每次都重新計算卻會大量消耗咱們計算機的性能。java

記憶化斐波那契函數

有作過這樣一個題目,斐波那契數列指的是相似於如下的數列:1, 1, 2, 3, 5, 8, 13, ....,也就是第 n 個數由數列的前兩個相加而來:f(n) = f(n - 1) + f(n -2), 請你完成 fibonacci 函數,接受 n 做爲參數,能夠獲取數列中第 n 個數。測試程序會從按順序依次獲取斐波那契數列中的數,請注意程序不要超時,也不要添加額外的全局變量。react

這個題目比較經典也很好理解, 我當時簡單的想到一個實現git

const fibonacci = n =>{
    let [a, b] = [0, 1]
    while(n--){[a, b] = [b, a + b]}
    return a
}

看起來彷佛沒什麼問題,可是當我提交答案時候老是提示超時。當時去網上搜索發現還有用 數學公式和二分矩陣方法 計算的,當時覺得題目是在考察這些還感受考察的有些偏。後面仔細看看了題目,上面說測試程序會依次獲取數列中的數。忽然想到我這個方法每一次執行其實都是要從頭開始計算一次,可是後面的數其實就是前兩次計算結果的和。有沒有什麼辦法能夠把前面計算的數存儲起來呢。後面想到這樣一種實現。github

const fibonacci = ((memory = {}) => n => {
    if(n < 2) return n
    if(memory[n-2] === undefined){
        memory[n-2] = fibonacci(n-2)
    }
    if(memory[n-1] === undefined){
        memory[n-1] = fibonacci(n-1)
    }
    return memory[n] = memory[n-1] + memory[n-2]
})()

這裏咱們用 iife 返回了一個函數。可是它每次執行都會把值存在內部的 memory 變量裏,咱們下次計算新的值這些已經計算過的值就不用子再去計算了。這樣就大大節省了計算量。算法

Reselect

咱們用 react 構建平常應用時候, 一般會用 redux 來存儲數據。咱們頁面上大部分展現都和 redux 中的數據有關,其中一部分是須要通過複雜的計算而後在進行展現。咱們知道改變組件的state或者組件裏用到的 redux 裏面的值都會從新渲染咱們的咱們的組件。若是咱們頁面裏的值是通過複雜計算的獲得的,每次從新渲染都會消耗大量的性能。能不能每次計算後都把結果緩存起來只有當計算條件發生改變了咱們再從新計算呢。 reselect 能夠幫助咱們去實現這個功能。數據庫

建立記憶化的選擇器

Reselect 提供的了個 createSelector 函數, 它能夠幫助咱們建立一個 記憶化的選擇器, createSelector 第一個參數是由數據選擇器函數組成的數組做,而後最後一個參數爲數據轉換器函數它的參數正是前面這些選擇器選擇到的值。簡單的看以下npm

import { createSelector } from 'reselect'

store = {
    a: 1,
    b: 2
}

const getA = store => store.a
const getB = store => store.b

const getSum = createSelector(
    [getA, getB],
    (a, b) => a + b
)

getSum(store) // 3

能夠看到最後的數據轉換函數,它接受的參數就是前面這些數據選擇函數選擇到的內容,固然這些函數接收到的參數都是最後咱們執行 getSum 函數時候傳入的參數, 有 getSum 生產的函數就會具備記憶功能,只有當選擇器函數的結果發生改變時候,它纔會去執行最後的數據轉換函數,不然則直接返回以前計算過的值。redux

實例中應用

假設咱們有一組選擇框,上面分別羅列了各個商品的名稱和價錢,咱們須要在底部展現咱們當前選擇商品的價格總和。這裏咱們用 redux 實現。
咱們在 state 中放置一個商品數組。相似list = [{name: 'item1', price: 12, checked: false},...],這裏咱們直接用 checked 屬性控制商品是否被選中。而後用兩種方法去計算當前選中項的價格總和

// input-selectors
const getList = state => state.home.list
/**
 * create select by reselect
 * @type {object} state
 */
const getTotal1 = createSelector(
    getList,
    items => {
        console.log('getTotal1 將要計算')
        return items.reduce((acc, item) => acc + (item.checked ? item.price : 0), 0)
    }
)

/**
 * create select smiple
 * @param  {array} items
 * @return {number} total checked value
 */
const getTotal2 = items => {
    console.log('getTotal2 將要計算')
    return items.reduce((acc, item) => acc + (item.checked ? item.price : 0), 0)
}

咱們經過 reselect 建立一個記憶化函數 getTotal1。而後本身在簡單的寫一個計算總和的函數 getTotal2。咱們在頁面里加入一個 input 輸入框它的值頁關聯到 reduxstate 中。咱們能夠看到當 getTotal1getTotal2 執行計算時候分別會 log 出相對應的信息。測試實際效果發現

reselect

當咱們改變 list裏面對值時候兩個計算都會發生,可是當咱們改變無關變量 input 的值時候,reselect 創造的選擇計算器內部的計算就不會發生,這是由於它的數據源對應的列表並無發生過變化。這樣就節省了很大的計算量。

小節

博客地址

實例在項目 react-ggsddu 運行 npm run reselect 體驗。

reselect 還有不少其餘的 api 以及使用場景,在他的 文檔 裏面已經說的很清楚了。

感受有時候看有些題目已經算法之類的總感受這些都是純粹爲了作題,可是後面本身發現不少思想和技巧其實都在這裏面。

every thing think twice

相關文章
相關標籤/搜索