react实现路由集中管理和redux的分模块技巧

2,667 阅读4分钟

Suspense + lazy懒加载组件

Suspense懒加载组件之前的操作。 lazy用来懒加载组件。实现代码分割

这样达到的效果就是在OtherComponent未成功加载完成之前会有一个loading效果

// antd中的加载
import { Spin } from 'antd'; 
// 懒加载组件
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const App = () => {
  return (
    <Suspense fallback={<Spin size="large" className="layout__loading" />}>
      <OtherComponent />
    </Suspense>
  )
}

redux-thunk,redux-logger中间件的使用

  • redux-thunk 解决异步问题。
  • redux-logger限于在开发环境下使用。每次action修改state时,都会在控制台打印出关键信息,便于开发者追溯状态。

1. state 仓库

  // user.js
  const user = {
    user_name: ''
  }
  export default user;

2. reducer工人

  // userReducer.js
  import (* as initState) from '../state/user'
  export default function app(state = initState, action) {
    const { type, payload } = action;
    switch (type) {
      case "SET_USER_NAME":
         return {
          ...state,
          user_name: payload,
        };
      default:
        return {
          ...state,
        };
    }
  }
  
  // index.js 
  import { combineReducers } from 'redux'
  import appReducer from './user'
  const rootReducer = combineReducers({
    user: userReducer
  })
  export default rootReducer;

3. action 操控数据

export const login = (user) => ({
  type: 'SET_USER_NAME',
  payload: user,
})

4. store

import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from './reducers'
const composeEnhancers =
  typeof window === 'object' &&
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
  }) : compose

const middlewares = [
  thunkMiddleware
]

if (process.env.NODE_ENV === 'development') {
  middlewares.push(reduxLogger);
}

const enhancer = composeEnhancers(
  applyMiddleware(...middlewares)
)

export function configStore() {
  const store = createStore(rootReducer, enhancer)
  return store
}

const store = configStore()

export export store

5.挂载

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';

import App from './App';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
   <App />
  </Provider>,
  document.getElementById('root'),
);

6.使用

  import { connect } from 'react-redux'
  import { login } from "@/store/action/user"
  const Home = (props) => {
    const onSubmit = (userInfo) => {
      props.login(userInfo)
    }
    return (
      <button type="button" onClick={() => onSubmit()}">登陆</button>
    )
  }
  // 属性映射
  const mapStateToProps = ({ user }) => ({
    user_name: user.user_name
  })
  // 方法映射
  const maoDispatchToProps = () => ({
    login
  })
  export default connect(mapStateToProps, maoDispatchToProps)(Home)

对于 react-router 集中管理方案

react-router集中式管理让路由的结构更加明显

router路由结构

import React from 'react';

/**
 * 第一级路由负责最外层的路由渲染
 * path 路由
 * component 组件 lazy懒加载的形势
 * meta.title  标题, 也可以是路由侧边栏的值
 * meta.icon   路由侧边栏的图标
 * meta.auth   需要权限的路由
 */

const routes = [
  {
    path: '/login',
    component: React.lazy(() => import('../views/login')),
    meta: {
      title: '登录',
    },
  },
  {
    path: '/',
    component: React.lazy(() => import('../layout/index')),
    redirect: '/home/index',
    children: [
      {
        path: '/home/index',
        component: React.lazy(() => import('../views/home')),
        meta: {
          title: '首页',
          auth: ['admin'],
          icon: 'read',
        },
      },
      {
        path: '*',
        auth: false,
        component: React.lazy(() => import('../views/error/404')),
        meta: {
          title: '页面不存在',
        },
      },
    ],
  },
  
];

export default routes;

router解析

import routes from './index';
/**
 *
 * 将路由转换为一维数组
 * @param routeList 路由
 * @param deep 是否深层转化
 * @param auth 路由是否需要检查授权, 路由配置的auth优先级比这里高
 */
export function flattenRoute(routeList, deep, auth) {
  const result = [];

  for (let i = 0; i < routeList.length; i += 1) {
    const route = routeList[i];
    // 加入路由
    result.push({
      ...route
    });
    // 深度遍历
    if (route.children && deep) {
      result.push(...flattenRoute(route.children, deep, auth));
    }
  }

  return result;
}

function getLayoutRouteList() {
  return flattenRoute(routes, false, false);
}

function getBusinessRouteList() {
  const routeList = routes.filter(route => route.path === '/');

  if (routeList.length > 0) {
    return flattenRoute(routeList, true, true);
  }
  return [];
}
// 解析最外层的路由
export const layoutRouteList = getLayoutRouteList();
// 解析业务路由,为 / 路由下的业务路由
export const businessRouteList = getBusinessRouteList();
// 获取路由的标题
export function getPageTitle(routeList) {
  const route = routeList.find(child => child.path === window.location.pathname);

  return (route && route.meta )? route.meta.title : '';
}

解析第一层的路由

首先我们应该知道第一层路由需要放到根组件中,可以放在app.jsx。这个时候我们会获取第一层的路由 /login/,当然也可以放其他路由。

import React, { Suspense } from 'react';
import { Spin } from 'antd';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { layoutRouteList } from './router/utils';

function App() {
  return (
    <Suspense 
      fallback={<Spin size="large" className="layout__loading" />}
    >
      <Router>
        <Switch>
          {layoutRouteList.map((route) => (
            <Route
              key={route.path}
              path={route.path}
              component={route.component}
            ></Route>
          ))}
        </Switch>
      </Router>
    </Suspense>
  );
}

export default App;

深度解析路由

根据第一层的路由解析,必然会到/路由下,此时对应的component的是layout.我们可以将layout设置布局如下。主要的是MainRoutes,路由会放在这个地方。

// LayoutRouter.jsx
import React from "react";
import MainRoutes from "./components/MainRoutes";
const LayoutRouter = () => {
  return (
    <Layout>
      <Silder />
      <Layout>
        <Header />
        <Content>
           <MainRoutes/>
        </Content>
      </Layout>
    </Layout>
  )
}
  1. useHistory 2. DocumentTitle设置标题 3. CheckRouter高阶组件有两个作用a. 检测token b. 重定向路由
import React, { memo } from "react";
import { Route, Switch, useHistory } from "react-router-dom";
import DocumentTitle from "react-document-title";
import { connect } from "react-redux";
import { businessRouteList, getPageTitle } from "../../router/utils";
import CheckRouter from "./CheckRouter";

function MainRoutes({ role }) {
  const { location } = useHistory();
  const handleFilter = (route) => {
    if (role === "admin") {
      // 管理员查看所有
      return true;
    }

    if (!route.meta) {
      // meta没值跳过
      return true;
    }

    if (!route.meta.auth) {
      // auth没值跳过
      return true;
    } else {
      //  auth有值校检
      return route.meta.auth.includes(role)
    }
  };
  return (
    <DocumentTitle title={getPageTitle(businessRouteList)}>
      <Switch location={location}>
        { businessRouteList.map((route) => {
          const { component: Component } = route;
          return (
            handleFilter(route) && (
              <Route
                key={route.path}
                exact={route.path !== "*"}
                path={route.path}
                render={(props) => (
                  <CheckRouter {...props} route={route}>
                    <Component {...props} />
                  </CheckRouter>
                )}
              ></Route>
            )
          );
        })}
      </Switch>
    </DocumentTitle>
  );
}
// 登陆时获取的角色
const mapStateToProps = ({ user }) => ({
  role: user.role,
});

export default connect(mapStateToProps)(memo(MainRoutes));
import React, { memo } from 'react';
import { Redirect } from 'react-router-dom';
import { getToken } from '../../utils/cookie';

function CheckRouter(props) {
  // 未登录
  if (!getToken()) {
    return (
      <Redirect
        to={`/login?redirectURL=${encodeURIComponent(
          window.location.origin +
            props.location.pathname +
            props.location.search,
        )}`}
      />
    );
  }
  if (props.route.redirect) {
    return <Redirect to={props.route.redirect} push />;
  }
  return <>{props.children}</>;
}
export default memo(CheckRouter);

本文使用 mdnice 排版