vue中router路由的原理?两种路由模式如何实现?(vue2) -(下)

news2024/12/25 14:31:21

上一期我们说到了如果想要实现一个路由嵌套,那么就需要判断传递实例化路由时的那个路由信息是否存在children属性,如果有children说明它是二级路由,我们还需要去递归判断,因为它不一定只有一个子路由,接下来实现一下路由嵌套。

1、首先我们在路由文件中配置了路由信息,并且我们嵌套了两个二级路由。

我们可以看到,一级路由是绝对路径,我们二级路由是相对路径,因此找到切入点,我们可以判断是否是子路由。

const router = new VueRouter({
    mode: 'history',
    routes: [
        {
            path: '/home',
            component: () => import('../components/Home/Home.vue')
        },
        {
            path: '/list',
            component: () => import('../components/List/List.vue'),
            children: [
                {
                    path: 'list1',
                    component: () => import('../components/List/List1/List1.vue')
                },
                {
                    path: 'list2',
                    component: () => import('../components/List/List2/List2.vue')
                }
            ]
        },
    ]
})

2、自定义router插件定义Router类并且定义一个install方法

和上期一样,我们都需要定义一个router类,并且定义了一个install方法,这都是在第一次实习的基础上做出了小的更改。主要做出了以下几个更改:

(1)、constructor中我们调用了一个方法,我们将监听的方法封装为一个方法,在constructor进行调用(我们当然也可以不封装,封装起来更加直观)

我们可以看到这里跟上一次的逻辑是没有什么大概的变化的,只是把我们监听hash模式以及history的方法封装为一个函数。但是我们封装为函数主要还有一个目的,就是为了我们使用递归匹配所有route的时候方便些。因为我们不管什么模式都需要进行递归我们的路由信息判断它是否存在子级路由信息。

let Vue;
class VueRouter {
    constructor(options) {
        //保存选项
        this.options = options;
        // 定义一个current,保存当前的hash值
        this.current = location.hash.slice(1,) || '/';// /home. /list/list1
        // defineReactive定义响应式的对象
        Vue.util.defineReactive(this, 'matched', [])
        this.init();
    }
      init() {
        //判断当前的路由模式
        if (this.options.mode === 'hash') {
            // hashchange事件来监听hash值的变化, 注意this指向问题
            addEventListener('hashchange', this.changeHash.bind(this))
        } else if (this.options.mode === 'history') {
            // history模式监听的事件叫做:popstate, 监听的是浏览器左上角的两个小箭头的变化
            addEventListener('popstate', this.changeHistory.bind(this))
        }

        this.match();
    }
     //监听hash值变化的函数
    changeHash() {
        //通过location.hash来获取到hash值
        this.current = location.hash.slice(1,)
        console.log(this.routesMap, '555');
        // console.log(this.current);//当前path路径
        // 上一次匹配到的matched清空
        this.matched = [];
        this.match();
    }
    // history
    changeHistory() {
        this.current = location.pathname;
        // 上一次匹配到的matched清空
        this.matched = [];
        this.match();
    }
}

(2)、定义match方法为我们匹配路由的函数,作用是为了把我们一级路由一级多级子路由添加到我们定义的数组中,从而我们进行业务逻辑的操作。

这里我们通过递归的方式,将我们的一级路由信息一级子级路由信息全部添加到我们定义的matched数组中,并且我们每次路由发生变化以后会将我们匹配到的路由信息清空,重新再次调用我们的匹配递归方法。这样依次递归,就不会导致我们下一次路由匹配错误。

let Vue;
class VueRouter {
    constructor(options) {
        //保存选项
        this.options = options;
        // 定义一个current,保存当前的hash值
        this.current = location.hash.slice(1,) || '/';// /home. /list/list1
        // defineReactive定义响应式的对象
        Vue.util.defineReactive(this, 'matched', [])
        this.init();
    }
    init() {
        //判断当前的路由模式
        if (this.options.mode === 'hash') {
            // hashchange事件来监听hash值的变化, 注意this指向问题
            addEventListener('hashchange', this.changeHash.bind(this))
        } else if (this.options.mode === 'history') {
            // history模式监听的事件叫做:popstate, 监听的是浏览器左上角的两个小箭头的变化
            addEventListener('popstate', this.changeHistory.bind(this))
        }

        this.match();
    }
    //监听hash值变化的函数
    changeHash() {
        //通过location.hash来获取到hash值
        this.current = location.hash.slice(1,)
        console.log(this.routesMap, '555');
        // console.log(this.current);//当前path路径
        // 上一次匹配到的matched清空
        this.matched = [];
        this.match();
    }
    // history
    changeHistory() {
        this.current = location.pathname;
        // 上一次匹配到的matched清空
        this.matched = [];
        this.match();
    }
    match(routes) {//递归匹配所有的route
        routes = routes || this.options.routes;
        //循环routes
        routes.forEach(route => {
            if (route.path === '/' && this.current.includes(route.path)) {
                this.current.push(route)
            }
            if (route.path !== '/' && this.current.includes(route.path)) {
                this.matched.push(route);
                if (route.children) {// 递归查找所有route
                    // this.matched.push(route)
                    this.match(route.children)
                }
            }
        })
        console.log(routes, 'routes');
    }
}

(3)、为了逻辑更加直观,将定义router-link全局组件以及router-view全局组件封装起来,以使我们清晰的知道哪个js文件是对哪一块业务逻辑的操作。

import RouterLink from './model/RouterLink'
import RouterView from './model/RouterView'
let Vue;
VueRouter.install = function (_Vue) {
    //1、保存Vue
    Vue = _Vue
    //2、将以下代码延迟到vue实例初始化完毕执行
    Vue.mixin({
        beforeCreate() {
            //判断如果有router属性就执行下方代码
            if (this.$options.router) {
                Vue.prototype.$router = this.$options.router;
            }
        }
    })
    //创建全局组件router-link和router-view
    //创建router-link组件
    Vue.component('router-link', RouterLink)
    //创建router-view组件
    Vue.component('router-view', RouterView)
}

以上行为基本上都是对业务逻辑的一个封装,但对于我们路由嵌套还没有根本上的实现。

我们主要是在router-view组件渲染的时候进行业务逻辑的处理,因为是否渲染子组件完全取决于是否存在routerview全局组件。

3、RouterView全局组件的逻辑处理。

由于我们通过每次路由监听都会进行一次路由的匹配,判断是否存在children属性,从而将我们的路由信息传递到我们定义的matched数组当中。

大概思路如下:

(1)、我们给当前的虚拟dom节点标记当前组件时routerView组件(定义布尔值为true)

这样我们每个通过routerView渲染的组件都存在一个routerView的属性,并且值为true

(2)、我们定义当前路由的层级,默认为0。通过获取父节点,循环父节点,判断它的父节点上是否存在我们定义的routerView标记,如果存在,我们将它层级+1这时,我们是二级路由,我们判断父节点也就是我们的一级路由存在routerView标记,那么它就是二级路由,我们层级为二级。

我们会继续循环判断,直到它查找父节点并且没有父节点以后会退出循环。

(3)、这时我们通过router类中定义的matched数组,可以获取到我们当前路由下的一级路由一级子路由,全部添加到这个数组中,我们根据下标可以获取到当前是鸡鸡路由。从而渲染我们的组件。

下面使用图片展示,大家会更加清晰。

 

 match方法通过递归的方式将我们的数据添加到matched数组中,我们可以看到点击一级路由它会获取到一个数组里面是我们一级路由的两个二级路由,当我们点击二级路由时,我们会在数组基础上添加我们当前的二级子路由,如图三所示。这样我们每次存在子路由都可以通过添加到matched数组当中。

这时就与我们定义的当前层级数产生了联系,如果是一级,那么它的层级depath为0,如果是二级路由那么它的depath为1,我们就可以通过下标找到它的组件以及path路径,不理解可以对照图三看看是不是这样。

并且我们通过while循环判断我们当前的routerView的父节点,判断它是否存在生命的routerView标记,如果存在说明我们当前是子路由,依次类推,如果不存在说明我们是一级路由,因此不需要渲染children的子路由。

 我们的每一个路由都存在标记的routerView。我们就是通过判断当前路由的父节点是否存在标记routerView来判断是几级路由的。

最后通过下标可以获取到我们所对应的路由组件,通过render()return渲染我们的组件。

代码如下:

export default {
    render(h) {
        // 标记一下当前的组件是routerView组件
        this.$vnode.data.routerView = true;
        // 保存当前路由的层级c
        let depath = 0;
        // 当前routerView组件的父节点
        let parent = this.$parent;
        console.log(this.$vnode, '$vnode');
        while (parent) {
            const vnodeData = parent.$vnode && parent.$vnode.data;
            // 证明我当前找到的这个节点就是routerView组件
            if (vnodeData && vnodeData.routerView) {
                // 如果是routerView组件,就让层级+1
                depath++;
            }
            // 不断的向上 查找父节点。直到没有父节点了,退出循环
            parent = parent.$parent;
        }
        // console.log(depath);   
        const router = this.$router
        let component = null;
        // 通过depath找到对应的route
        let route = router.matched[depath];
        if (route) {
            component = route.component
        }
        return h(component)
    }
}

 4、routerLink全局组件

 我们的router-link组件并未做出一些改变,只需要每次history路由模式点击以后,手动调用监听的历史路由模式的方法,这样可以清空我们递归匹配的所有路由信息。

代码如下:

export default {
    props: {
        to: {
            type: String | Object,
            required: true
        }
    },
    render(h) {// h 有三个参数,第一个参数创建的元素,第二个参数元素具有的属性, 第三个参数元素的内容
        const router = this.$router;
        // console.log(router, 'router');
        if (router.options.mode === 'hash') {
            return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
        } else if (router.options.mode === 'history') {
            return h('a', {
                attrs: { href: this.to },
                on: {
                    'click': ev => {
                        // 1. 阻止a链接的默认跳转行为
                        ev.preventDefault();
                        // 2. 调用pushState方法来实现跳转: pushState(obj, title, url)
                        history.pushState({}, '', this.to)
                        // 3. 设置current的值
                        router.current = this.to;
                        // 4. 点击的时候再次调用changeHistory方法
                        this.$router.changeHistory();
                    }
                }
            }, this.$slots.default)
        }

    }
};

总结:

总体并未做出很大改变,首先将我们的源码进行了封装,其次由于我们使用到了路由嵌套因此定义了一个递归函数来匹配路由。最后做出极大变化的是router-view组件,我们需要定义标记,并且通过循环判断父节点是否存在我们定义的标记从而判断是第几层级,最后根据层级下标来获取当前子路由的路由信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/882431.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【第三阶段】kotlin语言的substring

const val INFO"kotlin java" fun main() {val indexOfINFO.indexOf(j)//左包右不包//0,indexOf等价于0 until indexOf ktolin常用0 until indexOf 方式println(INFO.substring(0,indexOf))println(INFO.substring(0 until indexOf))}执行结果

Android Framework 动态更新插拔设备节点执行权限

TF卡设备节点是插上之后动态添加,所以不能通过初始化设备节点权限来解决,需要监听TF插入事件,在init.rc 监听插入后动态更新设备节点执行权限 添加插拔TF卡监听 frameworks/base/services/core/java/com/android/server/StorageManagerServic…

YOLOv5基础知识入门(6)— 激活函数(Mish、Sigmoid、Tanh、ReLU、Softmax、SiLU等)

前言:Hello大家好,我是小哥谈。激活函数(Activation functions)对于人工神经网络模型去学习、理解非常复杂和非线性的函数具有十分重要的作用。YOLOv5模型训练过程中即使用了激活函数,可以改善模型的训练速度和准确性。…

SpringCloud实用篇7——深入elasticsearch

目录 1 数据聚合1.1 聚合的种类1.2 DSL实现聚合1.2.1 Bucket聚合语法1.2.2 聚合结果排序1.2.3 限定聚合范围1.2.4 Metric聚合语法1.2.5.小结 1.3 RestAPI实现聚合1.3.1 API语法1.3.2 业务需求1.3.3 业务实现 2 自动补全2.1 拼音分词器2.2 自定义分词器2.3 自动补全查询2.4 实现…

vue3-router

一、路由 (1)通过 URL 区分路由的机制上,有两种实现方式: hash 模式:通过 URL 中 # 后面的内容做区分,我们称之为 hash-router; history 模式:在这种方式下,路由看起来和…

C++_模板进阶_非类型模板参数_模板特化_分离编译

一、非类型模板参数 模板参数,分为类型形参和非类型形参。 类型形参就是在模板中跟在typename和class之后的参数类型名称,非类型形参就是用一个常量作为类模板或者函数模板的一个参数,在类模板和函数模板中,可以将该参数当作常量…

第三方软件安全测评如何收费,安全测试包括哪些测试项?

近年来,随着全球范围内网络安全事件的频发,第三方软件安全测评的需求也日益增长。软件安全对于企业的重要性不言而喻,那么如何收费和可做测试项就成了企业最为关注的问题,小编将就以上问题作出以下简析。 一、第三方软件安全测评…

Smartbi 修改用户密码漏洞

漏洞简介 通过查看 Smartbi 的补丁包信息,发现存在漏洞在某种特定情况下修改用户的密码,进行简单的复现和分析 ​ 漏洞复现 在页面上修改密码时,需要知道原本的用户对应的密码 ​ ​ 直接构造这样的数据包,就不需要知道原本…

Excelize Go语言操作 Office Excel文档基础库

Excelize 是 Go 语言编写的用于操作 Office Excel 文档基础库,基于 ECMA-376,ISO/IEC 29500 国际标准。可以使用它来读取、写入由 Microsoft Excel™ 2007 及以上版本创建的电子表格文档。支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式&#xf…

硬件-10-浏览器发展史和开源操作系统FydeOS

吊打 IE、Firefox,谷歌 Chrome 十年发展史 fydeos官方网站 全面了解浏览器内核发展史 1 浏览器内核 浏览器内核英文叫做:Rendering Engine,中文翻译很多,排版引擎、解释引擎、渲染引擎,现在流行称为浏览器内核。内核…

42、可靠传输——连续ARQ

上回内容我们学习到,一种保证可靠传输的机制——ARQ协议,ARQ协议通过设置超时定时器,当在规定时间内没有收到来自对方发来的“确认”报文,就自动报文重传。这其中,ARQ协议和停止等待机制的结合,我们把它称作…

USB PD快充保护方案有哪些?用集成式TVS还是分立式TVS?

USB Power Delivery ,简称USB PD,是由USB-IF组织制定的一种快速充电规范,目前主流的快充协议之一。该规范可实现更高的电压和电流,输送的功率最高可达100瓦,并可以自由的改变电力的输送方向。在日常使用USB PD快充充电…

CNN卷积详解(三)

一、卷积层的计算 4 ∗ * ∗ 4的输入矩阵 I I I 和 3 ∗ * ∗ 3 的卷积核 K K K: 在步长(stride)为 1 时,输出的大小为 ( 4 − 3 1 ) ( 4 − 3 1) 计算公式: ● 输入图片矩阵 I I I 大小: w w w w ww ●…

【八大排序】-- 基数排序(动图演示)

基数排序(桶排序)介绍 (1)基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是通…

【环境配置】Windows10-YOLOv8运行报错

问题如下: Windows10 运行 YOLOv8 出现如下错误: Traceback (most recent call last): File “”, line 1, in File “D:\anaconda3\envs\yolov8\Lib\multiprocessing\spawn.py”, line 116, in spawn_main exitcode _main(fd, parent_sentinel) ^^^^^…

【ROS】话题通信--从理论介绍到模型实现

1.简单介绍 话题通信是ROS中使用频率最高的一种通信模式,话题通信是基于发布订阅模式的,也即:一个节点发布消息,另一个节点订阅该消息。像雷达、摄像头、GPS… 等等一些传感器数据的采集,也都是使用了话题通信,换言之…

帆软大屏2.0企业制作

 数字化观点中心 / 当前页 如何从0-1制作数据大屏,我用大白话给你解释清楚了 文 | 商业智能BI相关文章 阅读次数:18,192 次浏览 2023-06-08 11:51:49 好莱坞大片《摩天营救》中有这么一个场景:  你可以看见反派大b…

设备数字化平台的优势和应用价值

在现代工业领域,设备的高效管理和维护对于企业的运营和竞争力至关重要。而设备管理系统作为一个强大的工具,可以极大地提升设备管理和维护的效率,从而实现生产效益的最大化。本文将探讨设备数字化平台的优势和应用价值。 设备数字化平台是一款…

蓝桥杯嵌入式省一教程:(一)点亮LED

如同所有编程入门的第一个教程——打印"Hello world"一样,点亮LED可以算得上是嵌入式开发中的"Hello world",所有的单片机开发入门都从这里开始。 在点亮LED前,我们需要先了解一个重要概念——GPIO(General Purpose Inp…

【C++】queue容器

1.queue容器基本概念 2.queue常用接口 #include <iostream> using namespace std;//队列queue #include<queue>//创建Person类 class Person { public:Person(string name, int age){this->m_Name name;this->m_Age age;}string m_Name; //姓名int m_Age; …