动态路由匹配

532 阅读4分钟

动态路由匹配

虽然这里说是动态路由匹配,其实只是因为自己太懒了,实在不想每一次写一次page_list.json文件中的内容之后,还要再去router下面的index.js里面添加一条route记录,实在太麻烦了。所以为了一劳永逸,直接使用vue-router中提供的addRoute方法,结合RouteConfig结构,直接生成路由。然后在main.js添加进去,避免了总是需要自己去添加的麻烦。

vue-router中使用到的方法及类型说明

  1. RouteConfig路由数据

通过查阅vue-router的api,可以很简单的找到RouteConfig这种路由数据的结构

官网给出的结构:

interface RouteConfig = {
  	path: string,
  	component?: Component,
  	name?: string, // 命名路由
  	components?: { [name: string]: Component }, // 命名视图组件
  	redirect?: string | Location | Function,
  	props?: boolean | Object | Function,
  	alias?: string | Array[string],
  	children?: Array[RouteConfig], // 嵌套路由
  	beforeEnter?: (to: Route, from: Route, next: Function) => void,
  	meta?: any,
	
  	// 2.6.0+
  	caseSensitive?: boolean, // 匹配规则是否大小写敏感?(默认值:false)
  	pathToRegexpOptions?: Object // 编译正则的选项
}

在本次的动态路由匹配中,实际上主要使用的是以下几个参数: path,component,children,caseSesitive。因为我们的代码是不需要进行权限设置的,所以meta还有beforeEnter方法都不需要写,而且在路由里面已经进行了beforeEach的处理。

  1. addRoute方法

addRoute方法的使用非常简单,只需要传入RouteConfig的类型数据(可以嵌套的哦),也可以传入Parent Name, RouteConfig的形式。

addRoute(parentName: string, route: RouteConfig): () => void
addRoute(route: RouteConfig): () => void
//注意使用addRoute时,添加一条新的路由规则记录作为现有路由的子路由。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。

具体实现思路

  1. 借助数据结构中的树形结构,来存放数据,这里选用了Js中Map类,因为用来存放数据非常简单,然后再利用了Js中的引用,来修改最初提供的数据。

  2. 处理很多特殊情况

    (1) 比如在一开始的时候定义了一个路径为/vue/router/dynamic的路由路径,那么这种情况下生成的RouteConfig的结构该是什么样子的呢,如下所示

{
    path: "/vue",
    caseSensitive: true,
    redirect: '/vue/router/dynamic',
    children: [
        {
            path: "router",
        	caseSensitive: true,
            redirect: '/vue/router/dynamic'
            children: [
                {
                    path: "dynamic",
                    caseSensitive: true,
                    children: [],
                    component: Page
                }
            ]
        }
    ]
}

为什么会是上述的结构呢,因为在最外层的vue,和router中,都没有相关的配置文件,所以此时他们的component一定为空的

==其实这里忘记处理了一个很重要的东西,这里应该加上redirect,否则会出现问题==

(2)如果是之前的路由继续设置了内容,那么我们应该去更新路由的内容,同时删除掉之前给定redirect,不过这里的redirect还没加上去,之后会进行处理,那么处理之后的代码格式如下:

{
    path: "/vue",
    caseSensitive: true,
    redirect: '/vue/router/dynamic',
    children: [
        {
            path: "router",
        	caseSensitive: true,
            component: Page,
            children: [
                {
                    path: "dynamic",
                    caseSensitive: true,
                    children: [],
                    component: Page
                }
            ]
        }
    ]
}

(3) 如果之前的节点都存在的话,就只要添加在其根节点的children中即可

代码

  1. 添加子节点,递归添加,类似添加子树

    DynamicRoute.prototype.addChildren = function (data, root, type = 'normal') {
        let path = data.splice(0, 1)[0];
        if (type == 'no-root') {
            if (data.length == 0) {
                root.set(path, {
                    path,
                    children: new Map(),
                    component: Page,
                    caseSensitive: true
                })
    
                return root;
            } else {
                root.set(path, {
                    path,
                    children: new Map(),
                    caseSensitive: true
                })
                if (data.length >= 1) {
                    return this.addChildren(data, root.get(path).children, 'no-root');
                }
            }
        } else {
            root.set(path, {
                path,
                children: new Map(),
                component: Page,
                caseSensitive: true
            })
            if (data.length > 1) {
                return this.addChildren(data, root.get(path).children, 'normal');
            } else {
                return root;
            }
        }
    }
    
  2. 查找父节点,如果没有则自己创建使用addChildren方法, 有的话直接加入父节点的children中

DynamicRoute.prototype.findRoot = function (data, routeMap) {
    let findMap = routeMap;
    let findCount = 0;
    let length = data.length;
    for (let value of data) {
        if (findMap.has(value) && findCount < length - 1) {
            findMap = findMap.get(value).children;
            findCount++;
        } else if (!findMap.has(value) && findCount < length) {
            findMap = this.addChildren(data.splice(findCount, data.length - findCount), findMap, 'no-root');
            break;
        } else if (!findMap.has(value) && findCount == length) {
            findMap.set({
                path: value,
                children: new Map(),
                component: Page,
                caseSensitive: true
            })
            break;
        } else if (findMap.has(value) && findCount == length - 1) {
            if (typeof (findMap.get(value).component) == 'undefined') {
                findMap.get(value)['component'] = Page;
            }
            break;
        }
    }
}
  1. 处理map数据使其成为RouteConfig数据,并添加入Router中
DynamicRoute.prototype.generateRoute = function () {
    this.handleAndCreate(this.routeInfo);
    //处理数据,生成最终路由表,如果
    if (this.routeMap.size != 0) {
            this.routeData = Array.from(this.routeMap).flat().filter(value => {
                if (typeof (value) != 'string') {
                    return value
                }
            })
            this.handleRouteDataForNormal(this.routeData);

            for(let value of this.routeData){
                this.router.addRoute("Page", value);
            }
    }
}

有待完善

  1. 首页就是上面提到的那一点,redirect的处理,这里在2021年10月28日 会进行修改。

完善redirect的功能

  1. 新增一个filterRouteData的方法

    代码实现如下:

/**
 * @method filterRouteData 过滤路由数据,为没有组件的前置路由添加redirect
 * @param {*} data
 */
DynamicRoute.prototype.filterRouteData = function(data){
    let findRedirectPath = function(data, path){
        let completePath = path;
        if(data.length > 0){
            for(let value of data){
                if(typeof(value.component) != 'undefined'){
                    return `${completePath}/${value.path}`;
                }else if(value.children.length > 0){
                    return findRedirectPath(value.children, `${completePath}/${value.path}`);
                }
            }
        }
    }
    let setRedirectPath = function(data, path){
        for(let value of data){
            if(typeof(value.component) == 'undefined'){
                value['redirect'] = path;
            }else {
                delete value.redirect;
            }
            if(value.children.length != 0){
                setRedirectPath(value.children, path)
            }
        }
    }

    for(let value of data){
        if(value.children.length != 0 && typeof(value.component) == 'undefined'){
            let redirectPath = findRedirectPath(value.children, `/page/${value.path}`);
            value['redirect'] = redirectPath;
            setRedirectPath(value.children, redirectPath)
        }else{
            continue;
        }
    }
}

代码说明:

  1. 实际上在生成一个redirect的时候,我们就回去找当前所要处理的层级下,离他最近一个子路由的component是否为空。
  2. 如果不为空,那么我们就知道这个子路由下挂载的是包含着内容,那么这个时候就为其添加redirect。
  3. 然后一层一层去处理,直到处理完全部的内容后,就返回。
  4. 这里的一些判断依据就是component和children,通过这两个元素来判断是否路由包含内容或者是否存在子路由。
  5. 除了上述构建路由的redirect之外,还需要如果在之后读进来的数据是挂载了内容的,但是此时它的redirect是存在的,那么就需要处理这个redirect,对其进行删除,并且更新该节点上的父节点的redirect,让其父节点的redirect指向这个节点。