路由二(jny-router)

上一个版本,我们做到了通过简单的路由匹配来加载一级页面组件。在这个版本中,我们逐步完善了以下功能:

  1. 路由匹配更多模式
  2. 可以无限添加子组件

一,路由匹配更多模式

通过vue-router,我们可以发现路由匹配会遇到很多种情况,比较基本的大概有三种情况:

  • 短路由,比如 /welcom
  • 长路由,比如/welcome/aa/bb
  • 带有参数的路由,比如/welcome/:name/:id

说到这里,其实这个路由匹配我已经做了三个版本了。

第一个版本,思路是把当前的path进行数组分割,比如把 /pro/a分割成 [pro,a] ,然后去路由配置中找组件,最终找到类似[component1, component2]数据,从而加载每个组件。
不过很快就发现,这种思路不行,比如,如果碰到 /pro 这样的path,路由配置的实际情况是 /pro 会有自己的子路由,子路由的path等于"",通过这种方式只能找到 /pro 的组件,并不能找到默认子路由。而/pro的path,需要找到子路由的组件。如果不明白见vue-router文件嵌套路由那章。

第二个版本,为了解决子路由的默认组件情况,我修改了方案,这个方案可以理解为顺藤摸瓜,刚开始也是分割path(比如 :[pro, a]),但是分割后不再各自去路由配置文件中去匹配组件,而是,通过先找到数组中的第一个路径(比如:pro)匹配到根组件,再继续看数组是否有第二个路径(比如:a),如果有则在当前的已经找到的根组件配置中找子组件路由去匹配。如果没有则在当前子路由找有没有path等于“”的默认路由进行加载,如果连默认路由都没有,就加载404.
按照这种方案也有问题,比如遇到了 /welcome/a 这样路径,而在我们的配置文件中,我们会有两个相同名字的路由文件,但是有些区别。比如第一个路由 /welcome 下面有个子路由 path 等于 /b,第二个路由/welcome/:id 下面有个子路由 path 等于 ""。按照顺藤摸瓜的模式,我们匹配到了第一个路由,但是没有找到 /a的,所以,404了,其实,我们预期的结果是能匹配到第二个路由。当然,造成这个问题,是因为我在匹配到第一路由的时候就break掉了。并没有继续循环下一个,因为我也是特意这么做的,如果全部循环可能会找到想要的,但是也可能会出现极端情况,出现三个/welcome路由,中间的那个是我想要的,但是因为没有break,我得继续向下找,找到第三个错误的路由,就错过了第二个正确的。

第三版本,这个是全匹配模式,思路是,刚开始对路由配置文件进行处理,把子路由的path和父路由的path进行组合,形成一个完整路由然后再进行匹配。这个方式目前用的比较好。下面详细介绍下这种方式。

1.1 路由转换


    let routersMap = [];
    
    /**
     * 收集路由配置的所有path
    */
    const createAllRouterPath = (routers, lastPath, lastComponent) => {
        if (!routers || routers.length <= 0) return;

        for(let i = 0, len = routers.length; i < len; i++){
            (function(i, lastPath, lastComponent){

                let {path, children, component} = routers[i];

                if (component == undefined) return;

                // 如果当前路径前面没有 / ,则给他加上
                if (path && path.indexOf('/') !== 0) path = '/' + path;

                if (path.indexOf(':') >= 0) path = path.replace(/\/:[\w]*/g, '/:?[\\w]*')

                lastPath += path;
                lastComponent += ',' + component

                if (children){
                    createAllRouterPath(children, lastPath, lastComponent);
                } else {
                    routersMap.push({path: lastPath, components: lastComponent.split(',').filter(Boolean)});

                }

            })(i, lastPath, lastComponent)
            
        }
    }

上面的代码我们用使用递归去获取组织完整的path和path对于的全部组件,再用闭包解决for循环的时候数据丢失的问题。最终我们会把路由的配置信息转换成新的集合存在 routersMap中,下图就是源配置信息(左图)转最终结果(右图)。


image.png

1.2 路由匹配


    /**
     * 匹配路由,加载组件
     * */ 
    const matchingRouterComponent = async (path, callback) => {
        if (!path) return;

        let _components = [];
        for(let i = 0, len = routersMap.length; i < len; i++){

            const {path: _path, components} = routersMap[i];
            // 添加上开始和结束标记,可精准匹配
            const reg = new RegExp('^' + _path + '$', 'g');

            if (path === _path || (_path.indexOf(':') >= 0 && reg.test(path))){
                _components = components
                break
            }
        }

        for(let i = 0, len = _components.length; i < len; i++){
            await callback(_components[i], i);
        }
    }

通过以上代码,只要提供了当前path和回调函数(加载组件或者销毁组件函数),在这里就通过循环 routersMap 中的path,去做全等于匹配和正则匹配。如果匹配成功就会跳出循环,加载当前path的所有组件。


二,加载无限子组件

加载子路由的组件,需要在父组件中设定一个容器,如果是只加载一个子组件只需要设定

这样的容器,然后把子组件插入到这个容器中即可。但是,在无限子路由中,我们就需要辨别下每个容器之间有什么不同。辨别具有相同class的div标签,我们需要给标签添加一个唯一标识。

    /**
     * 重新加载组件
     * */ 
    const reloadPageComponent = (component, loadPathIndex) => {
        return new Promise(resolve=>{
            const defaultFiles = ['index.html', 'index.js', 'index.css'];

            let html_path = `path1/${component}/${defaultFiles[0]}`;
            let css_path = `path1/${component}/${defaultFiles[2]}`;
            let js_path = `path1/${component}/${defaultFiles[1]}`;

            // https://github.com/seajs/seajs/issues/1135
            // seajs的书写规范,使用require,其参数不能有变量,必须是直接字符串,否则需要使用.async方式加载
            require.async(html_path, (_html)=>{

                const _uuid = _createUUID('data-v');

                // 给路由容器添加唯一标识
                const {res, template} = _compile_routerView_add_uuid(_html, _uuid);

                if (res) temp_uuid.push(_uuid);

                if (loadPathIndex === 0){
                    document.getElementById('app').innerHTML = template;
                    
                    // 当重新开始加载根目录的时候,需要重置临时uuid,再根据状态判断保存
                    temp_uuid = [];
                    if (res) temp_uuid.push(_uuid);

                } else{
                    const uuid = temp_uuid.shift();
                    mounted(template, uuid);
                }

                require.async(js_path);
                require.async(css_path);                 
                
                resolve();
            });

         
        })

    }

加载组件的代码逻辑大概是,我们通过seajs加载html,并且查看html代码中是否有 class='router-view' 的关键词,如果有就生成 uuid并添加到html中,然后返回新的html和匹配结果。
通过 loadPathIndex 区分当前加载的组件是不是根组件,如果是直接插入到 app 的容器中。否则就插入到子组件容器里。并根据 uuid 来区分应该插入到哪个子组件容器,从而形成无限子路由的加载。

三,总结

以上,完成了更多了路由匹配和无限加载子路由,后面我们将要完善路由的history模式和路由的一些基本对外使用接口,比如push()。

你可能感兴趣的:(路由二(jny-router))