解讀reselect

reselect使用文檔

為什么使用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é)

  1. 關(guān)于為什么redux每次dispatch一個(gè)action之后總是返回一個(gè)新的state?

    • 如果總是修改原來的state视事,則可能無法觸發(fā)新的渲染
    • 可以實(shí)現(xiàn)回滾
  2. reselect的出現(xiàn)就是為了避免一些不必要的mapStateToProps的計(jì)算胆萧,提升性能

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市郑口,隨后出現(xiàn)的幾起案子鸳碧,更是在濱河造成了極大的恐慌,老刑警劉巖犬性,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞻离,死亡現(xiàn)場離奇詭異,居然都是意外死亡乒裆,警方通過查閱死者的電腦和手機(jī)套利,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肉迫,你說我怎么就攤上這事验辞。” “怎么了喊衫?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵跌造,是天一觀的道長。 經(jīng)常有香客問我族购,道長壳贪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任寝杖,我火速辦了婚禮违施,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瑟幕。我一直安慰自己磕蒲,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布只盹。 她就那樣靜靜地躺著辣往,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹿霸。 梳的紋絲不亂的頭發(fā)上排吴,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音懦鼠,去河邊找鬼。 笑死屹堰,一個(gè)胖子當(dāng)著我的面吹牛肛冶,可吹牛的內(nèi)容都是我干的欺缘。 我是一名探鬼主播累驮,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼顾稀,長吁一口氣:“原來是場噩夢啊……” “哼绊茧!你這毒婦竟也來了媚污?” 一聲冷哼從身側(cè)響起伺帘,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤躲叼,失蹤者是張志新(化名)和其女友劉穎蔬捷,沒想到半個(gè)月后厉亏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體董习,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年爱只,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了皿淋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖窝趣,靈堂內(nèi)的尸體忽然破棺而出疯暑,到底是詐尸還是另有隱情,我是刑警寧澤哑舒,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布妇拯,位于F島的核電站,受9級特大地震影響洗鸵,放射性物質(zhì)發(fā)生泄漏乖阵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一预麸、第九天 我趴在偏房一處隱蔽的房頂上張望瞪浸。 院中可真熱鬧,春花似錦吏祸、人聲如沸对蒲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹈矮。三九已至,卻和暖如春鸣驱,著一層夾襖步出監(jiān)牢的瞬間泛鸟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工踊东, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留北滥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓闸翅,卻偏偏與公主長得像再芋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子坚冀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容