redux动态注入与路由拆分

910 阅读3分钟

所用版本:

  • react 15.3.2
  • react-router 2.8.1

创建src/store

import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from './common/middlewares/promiseMiddleware';
import apiMiddleware from './common/middlewares/api';
let ReduxLogger = require('ReduxLogger');

// 根据不同环境加载所需的 middlewares
let middlewares;
let initialState;
if (process.env.NODE_ENV === 'production') {
    middlewares = [
        thunkMiddleware,
        apiMiddleware,
        promiseMiddleware
    ];
    initialState = {};
} else {
    const logger = ReduxLogger.createLogger({
        collapsed: true
    });
    middlewares = [
        thunkMiddleware,
        apiMiddleware,
        promiseMiddleware,
        logger
    ];
    initialState = window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
}

// 定义将始终存在于应用程序中的 Reducer
// 注意这里的reducer需要引入
const staticReducers = {
  common: commonReducer,
  app: appReducer
}

function createReducer(asyncReducers) {
  return combineReducers({
    ...staticReducers,
    ...asyncReducers
  })
}

// 创建并配置store
function configureStore(initialState) {
    const store = createStore(createReducer(), initialState, applyMiddleware(...middlewares));
    // 添加一个对象,跟踪已注册的异步 Reducer
    store.asyncReducers = {};
    return store;
}

const store = configureStore(initialState);

// 关键:注入 reducer 函数
// 此函数添加 async reducer,并创建一个新的组合 reducer
export const injectAsyncReducer = (store, name, asyncReducer) => {
    if(typeof name === 'string' && typeof asyncReducer === 'function') {
        store.asyncReducers[name] = asyncReducer;
    }
    if(Object.prototype.toString.call(name) === "[object Object]") {
        store.asyncReducers = name;
    }
    // 用新的store取代之前的
    store.replaceReducer(createReducer(store.asyncReducers));
};

export default store;

现在调用injectAsyncReducer函数就可以注入其他reducer,配合路由:在每个界面下新建单独的route/index和reducer/index来进行管理。

界面reducer/index

根据情况将界面内reducer拆分,然后用combineReducers组合起来

import {combineReducers} from 'redux';
import scroll from './scroll';
import result from './result';

const moduleReducer = combineReducers({
    scroll,
    result
});

export default moduleReducer;

界面route/index

import store, { injectAsyncReducer } from '../../../store';

module.exports = {
    path: '路由地址/和之前的路由写法一样',
    getComponent(nextState, cb) {
        require.ensure([], require => {
            const reducer = require('../reducers/index');
            injectAsyncReducer(store, 'reducer', reducer);
            cb(null, require('../index.js'));
        }, 'webpackChunkName');
    }
};

getComponent 对应 <Route path="/path" component="component"/> 中的 component 属性,只不过 getComponent 是异步的,当路由匹配时,才会调用这个方法。 如果需要返回多个子组件,可以用 getComponents 的方法,将多个子组件作为对象的属性通过 callback 的形式返回即可。

require.ensure

require.ensure(dependencies, callback, chunkName)

这是webpack提供的方法,也是按需加载的核心方法,第一个参数是依赖,第二个是回调,第三个是打包时的chunkName,用来指定chunk file的名字。

创建route

import React, {Component} from 'react';
import { Provider } from 'react-redux';
import { Router, Route, IndexRoute, useRouterHistory } from 'react-router';
import { createHashHistory } from 'history';

import store from './store';
import App from './modules/app';
import NoMatch from './modules/app/NoMatch';

const routes =  {
    path: '/',
    // App中挂载了一些全局组件,比如Loading,Toast,Confirm,通过this.props.children来装载子组件
    component: App,
    indexRoute: {
    	// NoMatch 组件无内容,只有跳转到首页的功能
        component: NoMatch
    },
    getChildRoutes(nextState, cb) {
        require.ensure([], require => {
            cb(null, [
            	// 加载界面路由
                require('./pagePath/routes/index'),
                // 其他界面
                ...
            ])
        }, 'dynamicRoutes');
    }
};

const history = useRouterHistory(createHashHistory)({
    queryKey: false
});

export default class Root extends Component {
    render() {
        return (
            <Provider store={store}>
                <Router history={history} routes={routes}></Router>
            </Provider>
        );
    }
}


getChildRoutes 和对象配置路由中的 childRoutes 相同,但是异步并且接收partialNextState

挂载

import Root from './routes';
...
render(<Root />, document.getElementById('app'));

react router 4+

以下是为了配合 react16+ 和 react-router-dom4+ 版本进行的改造

版本4以上的路由,在路由写法上跟之前有些不同,需要采用标签的形式,配合 react 中的 lazy 函数。 需要注意的是,lazy 函数引入的组件必须用 Suspense 来包裹。

import React, { Suspense, lazy } from "react";
import { HashRouter, Route, Switch } from "react-router-dom";

import TrainerHome from "../pages/trainerHome/route"
const MyInformation = lazy(() => import(/* webpackChunkName: "MyInformation" */ "../pages/MyInformation"))

export default <Suspense fallback={""}>
    <HashRouter>
        <Switch>
            <Route exact path="/info" component={MyInformation} />
            <Route exact path="/" component={MyInformation} />
        </Switch>
    </HashRouter>
</Suspense>

不需要注入 redux 的界面,直接用 lazy 来引入即可,需要注入的界面,其关键还是上面的 injectAsyncReducer 函数。

import store, { injectAsyncReducer } from '../../../store/index';
import {lazy} from "react";
import reducer from "../reducers/result"

// 注入reducer
injectAsyncReducer(store, 'trainerData', reducer);

export default lazy(() => import(/* webpackChunkName: "TrainerHome" */ "../index"))