【dva】dva使用与实现(六)

前言

  • 接上篇,这次是extraEnhancers

extraEnhancers

  • extraEnhancers其实相当于外部中间件,我们通过学习一个持久化插件来学习它。
  • 前面我们持久化是利用onStateChange钩子当状态发生变更就写入localStorage,而取出数据使用initialState传入。
  • 有个插件叫redux-persist可以实现持久化。
  • 先安装,然后看如何使用:
cnpm i  redux-persist -S
  • 然后index.js里加入这些:
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import { PersistGate } from 'redux-persist/integration/react'
  • 这个persisitStore是需要把store传给它。
  • persisReducer则是需要把persistConfig和reducer传给它。类似于reducer中间件,增加reducer功能。
  • storage是配置在persistConfig里的。指定存储引擎,默认localStorage。
  • 最后gate大门,大写字母开头,不用说就是包裹组件的。
const persistConfig = {
    key: 'root',
    storage
}
let app = dva({
    onReducer: reducer => {
        let undoReducer = undoable(reducer)
        let rootReducer = persistReducer(persistConfig, undoReducer)
        return function (state, action) {
            let newState = rootReducer(state, action)
            let router = newState.present.router ? newState.present.router : newState.present.routing
            return { ...newState, router: router }
        }
    },
    history: createBrowserHistory()
})
app.router(({ history, app }) => {
    let persistor = persistStore(app._store)
    return (
        <PersistGate persistor={persistor}>
            <Router history={history}>
                <><ul>
                    <li><Link to='/dynamic'>dynamic</Link></li>
                    <li><Link to='/counter1'>counter1</Link></li>
                    <li><Link to='/counter2'>counter2</Link></li>
                </ul>
                    <Route path='/counter1' component={ConnectedCounter1}></Route>
                    <Route path='/counter2' component={ConnectedCounter2}></Route>
                    <Route path='/dynamic' component={DynamicPage}></Route>
                </>
            </Router>
        </PersistGate>
    )
})
  • 然后看浏览器里的localStorage的key是不是persist:root,再进行变更state,刷新浏览器,如果状态没变,就ok。
  • 下面先实现这个插件,首先先仿照导入的文件把目录和文件建出来。

lib/storage.js

export default {
    setItem(key, value) {
        localStorage.setItem(key, value)
    },
    getItem(key) {
        return localStorage.getItem(key)
    }
}
  • storage也可以改成数据库什么的,主要就是一个存,一个取。

persistReducer.js

const PERSIST_INIT = 'PERSIST_INIT'
export default function (persistConfig, reducer) {
    let initialized = false
    let persistKey = `persist:${persistConfig.key}`
    return function (state, action) {
        switch (action.type) {
            case PERSIST_INIT:
                initialized = true
                let value = persistConfig.storage.getItem(persistKey)
                state = value ? JSON.parse(value) : undefined
                return reducer(state, action)//有初始值就读出来给reducer
            default:
                if (initialized) {//已经初始化过的就写入即可
                    state = reducer(state, action)
                    persistConfig.storage.setItem(persistKey, JSON.stringify(state))
                    return state
                }
                return reducer(state, action)
        }
    }
}
  • 这个增强reducer的函数需要设定读storage数据和写storage数据的时机。
  • 先给个是否初始化,存储在storage的键名也是这里指定。
  • 返回的reducer就能把是否初始化作为闭包,更改其状态。
  • 当刷新时,派发初始化的action,便会读storage状态,然后传给reducer。
  • 当初始化完成后,每次有action来都会写入state。其实这里可以扩展一下,不然状态没变也写感觉有点浪费。

integration/react.js

import React, { Component } from 'react'
class PersistGate extends Component {
    componentDidMount() {
        this.props.persistor.initState()
    }

    render() {
        return this.props.children
    }
}
export { PersistGate }
  • 这个大门就是个高阶组件,只要在其加载时派发初始化的action就行了。

persistStore.js

export default function (store) {
    let persistor = {
        ...store,
        initState() {
            persistor.dispatch({
                type: 'PERSIST_INIT'
            })
        }
    }
    return persistor
}
  • 这就把store上再挂个方法,让前面大门能拿到就可以。
  • 总结下就是包装store加入派发初始化方法,用高阶组件加载后派发它,在reducer里对action处理变更reducer内部状态,从而产生写入storage和读取storage的逻辑,storage就一个读一个写2方法。
  • 但是这个取persistor的方式不优雅,利用extraEnhancer可以优雅的把persistor挂到store上:
    extraEnhancers: [
        createStore => (...args) => {
            const store = createStore(...args)
            let persistor = persistStore(store)
            store.persistor = persistor
            return store
        }
    ],
  • 这个钩子有点特殊,是个数组,写的时候稍微注意下,这个写法有没有很熟悉?都是createStore=>reducer=>store,就是个applyMiddleWare写法,只不过中间件的第二个参数写的reducer,实际这个createStore除了reducer还有初始状态以及enhancer,而最后返回值我们是把加的已经放到store上了,中间件是把store里的dispatch进行改写。
  • 这样自然我们可以使用createStore拿到store,再把persistor的派发方法加到store上。这样大门在取的时候,就变成这样:
app.router(({ history, app }) =>(
        <PersistGate persistor={app._store.persistor}>
            <Router history={history}>
                <><ul>
                    <li><Link to='/dynamic'>dynamic</Link></li>
                    <li><Link to='/counter1'>counter1</Link></li>
                    <li><Link to='/counter2'>counter2</Link></li>
                </ul>
                    <Route path='/counter1' component={ConnectedCounter1}></Route>
                    <Route path='/counter2' component={ConnectedCounter2}></Route>
                    <Route path='/dynamic' component={DynamicPage}></Route>
                </>
            </Router>
        </PersistGate>
    )
)
  • 既然是钩子,我们实现dva也要加上,由于传入是数组,所以在use时,把不要push进去,直接覆盖。所以这样后面的配置会盖掉前面配置,这个钩子只能在一个地方配置。

plugin.js

    use(plugin) {
        const { hooks } = this
        for (let key in plugin) {
            if (key === 'extraEnhancers') {
                hooks[key] = plugin[key]
            } else {
                hooks[key].push(plugin[key])
            }
        }//{hook:[fn|obj]}
    }
  • 然后去整合中间件传给createStore:
        let sagaMiddleware = createSagaMiddleware()
        let extraMiddleware = plugin.get('onAction')
        let extraEnhancers = plugin.get('extraEnhancers')
        let enhancers = [...extraEnhancers, applyMiddleware(routerMiddleware(history),
            sagaMiddleware, ...extraMiddleware)]
        let store = (createStore)(reducer, opts.initialState, compose(...enhancers))
  • 这里可能有点绕,不过只要把握主线就行。前面说extraEnhancers写法跟applyMiddleWare一样,那么就相当于这2个函数是同级别的,需要compose把这2函数组合成一个复合函数再去使用。
  • 所以,上面那种写法也相当于这样:
   let sagaMiddleware = createSagaMiddleware()
   let extraMiddleware = plugin.get('onAction')
   let extraEnhancers = plugin.get('extraEnhancers')
   let enhancers = [...extraEnhancers, applyMiddleware(routerMiddleware(history), sagaMiddleware, ...extraMiddleware)]
   let composedMiddleWare = compose(...enhancers)
   let store = composedMiddleWare(createStore)(reducer, opts.initialState)
  • 这就跟以前那种写法差不多,实测也是ok的。
  • 这篇先写到这。
发布了163 篇原创文章 · 获赞 9 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/yehuozhili/article/details/104114906
dva
今日推荐