开发中经常会碰到需要做权限控制的功能, 尤其是后台系统
这个时候通常会通过权限控制的方式展示该角色相应的可以查看的页面
在vue中可以使用addRouter这个api进行注册,将路由挂载到vuex中,发生改变实时进行变化
在react中我将vue的思维带了进来,利用redux和高阶组件进行相应的开发

这里我使用antd组件库进行页面的搭建,所以会有一些antd组件的代码

首先需要设计store

// @/store/routerList.js
export const CHANGE_ROUTER= "CHANGE_ROUTER"
export function Router(
    state = {
        router:[]
    },
    action
) {
    console.log(action)
    switch (action.type) {
        case CHANGE_ROUTER:
            return Object.assign({},{router: action.routerList});
        default:
            return state;
    }
}

export function mapRouterStateToProps(state) {
    return {
        router:state.router.router
    }
}

这里我设计的routerList的格式与vue-router要求的格式没有太大的变化,如下

[
	{
		path: "/",
		component: "pages/index/index.jsx",
		mate: {icon: "HomeOutlined", title: "首页"}
	},
	{
		path: "/home",
		mate: {icon: "ControlOutlined", title: "页面1"},
		children: [
			{
				path: "",
				component: "pages/home/home.jsx",
				mate: {title: "页面1-子页面1"},
			},
			{
				path: "/two",
				component: "pages/home/tow/tow.jsx",
				mate: {title: "页面1-子页面2"},
			}
		]
	}
]

routerListAction文件
在routerListAction文件中我们主要做两件事情

  • 请求数据
  • 保存数据

请求数据需要配合redux-chuck插件进行异步操作, 这里作为demo并没有真正对接后台, 以setTimeOut来模拟请求数据的过程

// @/store/action/router.js
import {CHANGE_ROUTER} from "../routerList"
import React from "react";

export function changeRouter() {
    console.log("进入changeRouter")
    return (dispatch, getState) => {
        setTimeout(() => {
            let data = [
                {
                    path: "/",
                    component: "pages/index/index.jsx",
                    mate: {icon: "HomeOutlined", title: "首页"}
                },
                {
                    path: "/home",
                    mate: {icon: "ControlOutlined", title: "页面1"},
                    children: [
                        {
                            path: "",
                            component: "pages/home/home.jsx",
                            mate: {title: "页面1-子页面1"},

                        },
                        {
                            path: "/two",
                            component: "pages/home/tow/tow.jsx",
                            mate: {title: "页面1-子页面2"},
                        }
                    ]
                }
            ]
            dispatch({
                type: CHANGE_ROUTER,
                routerList: data
            })
        }, 500)

    }
}

在store总入口文件合并进去router

// @/store/index.js
import {combineReducers} from "redux";
import {User} from "./user"
import {Router} from "./routerList"


const RootState = combineReducers({
    user:User,
    router:Router
})

export default RootState

上方为准备工作, 此时我们已经准备好了存储路由的仓库,以及修改路由的方法
接下来需要在用户登录进入主页面后拿着token向后台请求路由数据(当然我这里没有做请求,还是用setTimeOut来模拟的)
静态路由, 例如login页面,展示的首页等不需要权限控制的页面就直接在页面中写死

// @/index.js
<Provider store={store}>
      <Router>
          <Switch>
          	  {/* 下方的loading为静态路由 */}
              <Route exact path={"/login"} component={Login} />
              <Route component={App} />
          </Switch>
      </Router>
  </Provider>

可以看到, 我在index.js中直接渲染了App页面, App页面是layout页面, 其中包含了整个项目的骨架,大概长下面这个样子
整个项目骨架预览
左侧为导航栏, 通过路由渲染出来
路由解析参考我前面写的那篇文章react路由仿vue-router解析
这里做了动态注册路由, 所以有一些地方需要注意

  • import无法直接解析路由字符串, 所以需要做特殊处理, 否则会not find module
  • antd中menu组件无法直接动态渲染, 需要使用高阶组件进行代理

这两个问题困扰了几个小时, 好在想通了也就有了这篇博客
首先来说一下import的处理
在上篇文章中,做了组件懒加载的处理, 所用到的正是高阶组件代理, 利用import函数进行导入组件
这次需要对这个懒加载的高阶组件进行一些小小的改动

  • 传入组件路径而不是import函数
    改动如下
// 改动1 传入的是组件的路径
function AsyCom(loadComponent) {
    return class AsyncComponent extends Component {

        state = { Component: null }

        // 异步加载
        componentDidMount() {
            if (this.state.Component !== null) {
                return;
            }
			// 改动2 将调用改为下方方式
			// 注意 import中的参数不可以写成纯字符串,否则会not find module 原因不详 可能是webpack的解析规则 
			// loadComponent = pages/index/index.jsx
            import(`@/${loadComponent}`)
                .then(module => {
                    return module.default
                })
                .then((Component) => {
                    this.setState({ Component });
                })
                .catch((err) => {
                    console.error(`Cannot load component in <AsyncComponent />`);
                    throw err;
                });
        }

        render() {
            const { Component } = this.state;
            return (Component) ? <Component {...this.props} /> : null;
        }
    }
}

在渲染列表时需要注意, Menu.Item和SubMenu的icon无法直接渲染,也就是说无法通过<Icons[icon名] />这么写, 所以需要高阶组件进行一层代理

// icon渲染函数
function iconRender(iconName){
     class Icon extends Component{
        state = {
            Icon:null
        }

        componentDidMount() {
            this.setState({
                Icon: Icons[iconName]
            })
        }
        render() {
            const { Icon } = this.state;
            return (Icon) ? <Icon /> : null;
        }
    }
    return <Icon />
}

app.js代码如下

import React, {useState,useEffect,Component} from 'react';
import './App.scss';
import RrouterViews from "./router/router"
import {Layout, Menu, Avatar, Dropdown} from 'antd';
import {UserOutlined} from '@ant-design/icons';
import {Link} from "react-router-dom"
import {connect} from "react-redux"
import {changeRouter} from "@/store/action/router"
import {bindActionCreators} from "redux";
import * as Icons from "@ant-design/icons"

const {SubMenu} = Menu;
const {Header, Content, Sider} = Layout;

// icon渲染函数
function iconRender(iconName){
     class Icon extends Component{
        state = {
            Icon:null
        }

        componentDidMount() {
            this.setState({
                Icon: Icons[iconName]
            })
        }
        render() {
            const { Icon } = this.state;
            return (Icon) ? <Icon /> : null;
        }
    }
    return <Icon />
}


function App(props) {

    const menu = (
        <Menu>
            <Menu.Item onClick={() => {
                props.history.push("/login")
            }}>
                退出登录
            </Menu.Item>
        </Menu>
    )

    let [collapsed, setCollapsed] = useState(false);

    let onCollapse = () => {
        setCollapsed(!collapsed)
    }

    useEffect(()=>{
        props.changeRouter()
    },[])

    return (
        <Layout style={{height: '100%'}}>
            <Header className={"layoutHeader"} style={{position: 'fixed', zIndex: 1, width: '100%'}}>
                <div className="logo">LOGO</div>
                <Dropdown overlay={menu} placement="bottomCenter">
                    <div>
                        <Avatar size="large" className={"avatar"} icon={<UserOutlined/>}/>
                        <span style={{marginLeft: 12}}>{props.userName}</span>
                    </div>
                </Dropdown>

            </Header>
            <Layout>
                <Sider width={200} className="site-layout-background" collapsible collapsed={collapsed}
                       onCollapse={onCollapse}>
                    <Menu
                        mode="inline"
                        defaultSelectedKeys={['sub0']}
                        style={{height: '100%', borderRight: 0}}
                    >

                        {
                            props.routerList.map((item, index) => {
                                if(!item.mate.hidden){
                                    if (item.children) {
                                        return (
                                            <SubMenu key={"sub" + index} icon={iconRender(item.mate.icon)} title={item.mate.title}>
                                                {item.children.map((item_item, item_index) => {
                                                    return !item_item.mate.hidden && (<Menu.Item key={item_index}><Link to={item.path + item_item.path}>{item_item.mate.title}</Link></Menu.Item>)
                                                })}
                                            </SubMenu>
                                        )
                                    } else {
                                        return (
                                            <Menu.Item key={"sub" + index} icon={iconRender(item.mate.icon)}>
                                                <Link to={item.path}>{item.mate.title}</Link>
                                            </Menu.Item>
                                        )
                                    }
                                }
                            })
                        }

                    </Menu>
                </Sider>
                <Content className="site-layout" style={{padding: '0 50px', marginTop: 64}}>
                    <div className="site-layout-background"
                         style={{padding: 24, height: 'calc(100% - 30px)', boxSizing: "border-box"}}>
                        <RrouterViews/>
                    </div>
                </Content>
            </Layout>

            {/*<Footer style={{textAlign: 'center'}}>Ant Design ©2018 Created by Ant UED</Footer>*/}
        </Layout>
    );
}
const mapUserAndRouterListToProps = (state) => {
    return {
        userName:state.user.userName,
        routerList: state.router.router
    }
}

const mapRouterListActionToProps = (dispatch) => {
    return {
        changeRouter:bindActionCreators(changeRouter,dispatch)
    }
}

export default connect(mapUserAndRouterListToProps,mapRouterListActionToProps)(App);

Logo

前往低代码交流专区

更多推荐