為什么使用reselect?
說來話長留攒,一切要從redux說起煤惩,redux在每一次dispatch之后都會(huì)讓注冊的回調(diào)都執(zhí)行一遍,然后就是connect函數(shù)的鍋了炼邀,connect實(shí)際上就是一個(gè)高階組件魄揉,來看看connect的簡單實(shí)現(xiàn)
export const connect = (mapStateToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = { allProps: {} }
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps()) // 這里可以發(fā)現(xiàn)connect函數(shù)返回的這個(gè)高階組件幫我們在redux的store里面注冊了一個(gè)函數(shù),而這個(gè)函數(shù)的作用就是獲取新的state和props拭宁,然后觸發(fā)一次setState洛退,這就必然會(huì)導(dǎo)致這個(gè)高階組件的重新render,如果子組件不是繼承自PureComponent或做過其他處理杰标,那么子組件也必然會(huì)重新render兵怯,即使可能該組件涉及到的state和props都沒有發(fā)生變化,這樣一來就產(chǎn)生了性能問題腔剂,其實(shí)這個(gè)問題還好解決媒区,通過繼承PureComponent或者自己在shouldCOmponentUpdate里面做判斷即可解決。但是另一個(gè)不可避免的性能問題在于mapStateToProps函數(shù)的執(zhí)行,如果前端管理的數(shù)據(jù)十分復(fù)雜袜漩,每次dispatch以后所有用到store的組件都要計(jì)算mapStateToProps自然就會(huì)浪費(fèi)性能谅畅,解決這個(gè)問題的方法改造mapStateToProps的入?yún)⒑瘮?shù),在入?yún)⒑瘮?shù)里面緩存一個(gè)舊值噪服,然后每次執(zhí)行mapStateToProps的時(shí)候就利用新值和舊值緩存的一個(gè)淺比較來判斷是否返回原值毡泻,如果淺比較相同就直接返回原值,這樣就不用再做計(jì)算粘优,節(jié)省了性能仇味。這樣對于性能的提高往往是很大的,因?yàn)橐淮蝑ispatch一般只改變很少的內(nèi)容雹顺。
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps(store.getState(), this.props) // 額外傳入 props丹墨,讓獲取數(shù)據(jù)更加靈活方便
this.setState({
allProps: { // 整合普通的 props 和從 state 生成的 props
...stateProps,
...this.props
}
})
}
render () {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect;
}
于是乎,reselect就出現(xiàn)了嬉愧,來看看reselect的源碼贩挣,注釋里面寫了解讀:
function defaultEqualityCheck(a, b) {
return a === b
}
function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
if (prev === null || next === null || prev.length !== next.length) {
return false
}
// Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
const length = prev.length
for (let i = 0; i < length; i++) {
if (!equalityCheck(prev[i], next[i])) {
return false
}
}
return true
}
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
// defaultMemoize被調(diào)用了兩次,一次是執(zhí)行函數(shù)返回一個(gè)selector没酣,每次dispatch之后傳入selector的參數(shù)是state和props王财,而state總是會(huì)發(fā)生變化的,所以前面的判斷總是會(huì)進(jìn)入到這里
// 第二次調(diào)用時(shí)裕便,這里的arguments是dependency函數(shù)的運(yùn)算結(jié)果绒净,而前面的判斷就是看這些運(yùn)算結(jié)果是否發(fā)生了變化,如果依賴項(xiàng)沒有發(fā)生變化偿衰,及直接返回舊值
}
lastArgs = arguments
return lastResult
}
}
function getDependencies(funcs) {
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs
if (!dependencies.every(dep => typeof dep === 'function')) {
const dependencyTypes = dependencies.map(
dep => typeof dep
).join(', ')
throw new Error(
'Selector creators expect all input-selectors to be functions, ' +
`instead received the following types: [${dependencyTypes}]`
)
}
return dependencies
}
export function createSelectorCreator(memoize, ...memoizeOptions) {
// 返回的這個(gè)函數(shù)就是最后導(dǎo)出的createSelector挂疆,memorize是傳入的defaultMemorize函數(shù)
// createSelector的入?yún)⑹莇ependency函數(shù)和一個(gè)獲取最終數(shù)據(jù)的函數(shù)
// dependency函數(shù)可以放在一個(gè)數(shù)組里面也可以直接傳入
return (...funcs) => {
let recomputations = 0
const resultFunc = funcs.pop() // pop出來的就是最后獲取數(shù)據(jù)的函數(shù)
const dependencies = getDependencies(funcs) // 獲取dependency數(shù)組
const memoizedResultFunc = memoize(
function () {
recomputations++
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments)
},
...memoizeOptions
)
// If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
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)
// params數(shù)組存放著dependency函數(shù)的運(yùn)算結(jié)果,被當(dāng)做arguments傳入memoizedResultFunc下翎,其實(shí)每次dispatch之后都會(huì)觸發(fā)dependency函數(shù)的重新計(jì)算缤言,至于控制性能的問題是在memoizedResultFunc里面實(shí)現(xiàn)的
})
selector.resultFunc = resultFunc
selector.dependencies = dependencies
selector.recomputations = () => recomputations
selector.resetRecomputations = () => recomputations = 0
return selector
}
}
export const createSelector = createSelectorCreator(defaultMemoize)
// 這里export的createSelector函數(shù)就是我們所使用的函數(shù)
export function createStructuredSelector(selectors, selectorCreator = createSelector) {
if (typeof selectors !== 'object') {
throw new Error(
'createStructuredSelector expects first argument to be an object ' +
`where each property is a selector, instead received a ${typeof selectors}`
)
}
const objectKeys = Object.keys(selectors)
return selectorCreator(
objectKeys.map(key => selectors[key]),
(...values) => {
return values.reduce((composition, value, index) => {
composition[objectKeys[index]] = value
return composition
}, {})
}
)
}
總結(jié)
-
關(guān)于為什么redux每次dispatch一個(gè)action之后總是返回一個(gè)新的state?
- 如果總是修改原來的state视事,則可能無法觸發(fā)新的渲染
- 可以實(shí)現(xiàn)回滾
reselect的出現(xiàn)就是為了避免一些不必要的mapStateToProps的計(jì)算胆萧,提升性能