reselect是什麼? "reselect" 是配合 使用的一款輕量型的狀態選擇庫,目的在於當store中的state重新改變之後,使得局部未改變的狀態不會因為整體的state變化而全部重新渲染,功能有點類似於組件中的生命周期函數 ,但是它們並不是一個東西。下麵是官方的一些簡介: Selector ...
reselect是什麼?
reselect是配合redux
使用的一款輕量型的狀態選擇庫,目的在於當store中的state重新改變之後,使得局部未改變的狀態不會因為整體的state變化而全部重新渲染,功能有點類似於組件中的生命周期函數shouldComponentDidUpdate
,但是它們並不是一個東西。下麵是官方的一些簡介:
- Selectors can compute derived data, allowing Redux to store the minimal possible state.
- Selectors are efficient. A selector is not recomputed unless one of its arguments changes.
- Selectors are composable. They can be used as input to other selectors.
[註]:並不是reselect非要和redux綁定使用不可,可以說reselect只是一個enhancement,並不代表強耦合。
什麼時候用reselect?
store
狀態樹龐大且層次較深- 組件中的state需要經過複雜的計算才能呈現在界面上
個人認為符合這兩點就可以使用reselect,為什麼?簡單的state或許根本完全沒有必要引入redux,狀態管理組件內部就可以消化,再者reselect只是在參數級別的緩存,如果組件狀態邏輯並不是特別複雜,只是簡單的getter,那也可不必引入reselect。
[建議]:建議引入了redux就可以引入reselect,去看官方的源碼,總共加起來才短短的108行代碼,對測試並沒有什麼成本,同時加入也不會對打包體積造成什麼影響,但是有些時候對組件渲染的性能卻有很大的改善。
基本用法
這裡是直接copy的官方倉庫中的代碼
import { createSelector } from 'reselect'
const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
const taxSelector = createSelector(
subtotalSelector,
taxPercentSelector,
(subtotal, taxPercent) => subtotal * (taxPercent / 100)
)
export const totalSelector = createSelector(
subtotalSelector,
taxSelector,
(subtotal, tax) => ({ total: subtotal + tax })
)
let exampleState = {
shop: {
taxPercent: 8,
items: [
{ name: 'apple', value: 1.20 },
{ name: 'orange', value: 0.95 },
]
}
}
console.log(subtotalSelector(exampleState)) // 2.15
console.log(taxSelector(exampleState)) // 0.172
console.log(totalSelector(exampleState)) // { total: 2.322 }
reselect是怎麼優化代碼性能的?
- 整體store層級state的緩存
- 組件級別state的緩存
const selector = memoize(function () {
const params = []
const length = dependencies.length
for (let i = 0; i < length; i++) {
// apply arguments instead of spreading and mutate a local list of params for performance.
params.push(dependencies[i].apply(null, arguments))
}
// apply arguments instead of spreading for performance.
return memoizedResultFunc.apply(null, params)
})
selector.resultFunc = resultFunc
selector.dependencies = dependencies
selector.recomputations = () => recomputations
selector.resetRecomputations = () => recomputations = 0
return selector
函數整體返回的就是這個selector
,因為我們調用createSelector
,其實返回的是一個函數,所以memoize
返回的其實也是一個函數,那麼selector
中做了什麼?
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
// we reference arguments instead of spreading them for performance reasons
// 這裡作為返回的函數,傳入的參數即為state
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
}
lastArgs = arguments
return lastResult
}
}
memoize
是reselect中提供的預設緩存函數,可以的得知執行這個函數的時候,返回的函數即為上面代碼中的selector
,那麼arguments
即為傳入的state,通過areArgumentsShallowlyEqual
比較兩次傳入的參數是否相等,註意,這裡是淺比較,即第一層引用的比較
function defaultEqualityCheck(a, b) {
return a === b
}
當兩次傳入的值存在變化的時候,那麼就會執行
func.apply(null, arguments)
這裡會計算得到所有的依賴,然後得到下一輪緩存函數的params
。
就redux的reducer來講,這層緩存並沒有什麼作用,看看reducer代碼:
function reducer(state, action) {
switch (action.type):
case REQUEST_TODO_PENDING:
return { ...state, loading: true };
case REQUEST_TODO_LIST_SUCCESS:
return { ...state, list: ['todo'], loading: false };
// ...
// default
}
redux社區推崇所有的state都是不可變的,所以只要dispatch了一個action,每次返回的state必然會是一個新的對象,對於淺比較每次返回的結果必然是true
;
所以,緩存的關鍵還在第二層momoize
,因為這裡的state並不是每一次都必須變化:
const resultFunc = funcs.pop()
const dependencies = getDependencies(funcs)
const memoizedResultFunc = memoize(
function () {
recomputations++
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments)
},
...memoizeOptions
)
真正代碼的執行在resultFunc.apply(null, arguments)
,這裡緩存的邏輯跟上面沒什麼區別,這裡就不在講解了。resultFunc
是createSelector
中的最後一個參數
const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent
const subtotalSelector = createSelector(
shopItemsSelector,
items => items.reduce((acc, item) => acc + item.value, 0)
)
大家可以自行對照一下上面的這個例子,那麼arguments
就是第二個函數的參數,也就是第一步緩存函數中的params
。
總結
好了,就啰嗦這麼多了,最後,多讀書,多看報,少吃零食,多睡覺