likes
comments
collection
share

刨析Vue路由原理,解读路由Hash模式

作者站长头像
站长
· 阅读数 3

今天,我来带大家刨析Vue路由原理!

我们知道Vue路由有两种模式:history模式和hash模式

在history模式下,我们的页面路径为:刨析Vue路由原理,解读路由Hash模式

在hash模式下,我们的页面路径为:刨析Vue路由原理,解读路由Hash模式

这个就是两个模式的区别,其实hash模式实现路由的手段更加的方便,但是hash在浏览器路径上会多出一个#的符号,不是那么美观。

那么,我们要自己实现一个路由该怎么实现呢?

vue-router项目准备

首先我们可以安装一个vue项目,这里我选择的是使用vite脚手架开始一个新项目!再进行配置文件的安装,这里我们手动安装一下vue-router

    npm install vue-router@4
    yarn add vue-router@4
    pnpm add vue-router@4

三种安装方式:npm,yarn,pnpm

安装完成之后,我们来一个最简单的效果:Home-Page

刨析Vue路由原理,解读路由Hash模式

我们要新建两个页面:Home.vue和About.vue

在src目录下,新建一个views文件夹,在文件夹内新建页面

刨析Vue路由原理,解读路由Hash模式

配置好vue-router的配置文件

在src目录下,新建一个router文件夹,在文件夹内新建一个index。js文件,并配置

import {createRouter,createWebHashHistory} from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
    {
        path:'/',
        name:'home',
        component:Home
    },
    {
        path:'/about',
        name:'about',
        component:About
    }
]

const router = createRouter({
    history:createWebHashHistory(),//hash模式
    routes
})

export default router

我们可以发现,这个配置文件是通过引入vue-router中的两个函数createRoutercreateWebHashHistory

createRouter:用于创建路由的函数

createWebHashHistory:创建哈希模式的函数

整个配置文件可以让我们通过以数组形式的routes将路径和组件进行一一映射!

实现Vue-router的Hash模式

打造createRouter,createWebHashHistory

通过观察vue-router的配置文件,我们可以发现,其主要功能依赖于引入的两个函数createRoutercreateWebHashHistory

所以,我们要自己实现一个路由Hash源码,也就是对这两个函数功能的实现!

我们可以在原有项目的基础上,在router文件夹中新建一个文件夹myRouter用于我们自己的路由源码,在myRouter文件中再新建一个index.js文件

我们再修改路由的配置文件

将:import {createRouter,createWebHashHistory} from 'vue-router'

替换为:import {createRouter,createWebHashHistory} from './myRouter'

接下来,我们进行配置,这里我们引入了ref动态得绑定数据,实现同步更新

import {ref} from 'vue'
function createRouter(options) {
    return new Router(options)//调用就返回一个实例对象
}

function createWebHashHistory(){

}

export {
        createRouter,
        createWebHashHistory
}

基本的结构我们就配置好了,两个函数createRoutercreateWebHashHistory,并且将这两个函数抛出!

通过观察路由配置文件

刨析Vue路由原理,解读路由Hash模式

我们可以发现createRouter接收的是一个对象,并且返回的是一个实例对象,在这个构造函数当中,我们在对象里面接收两个key值:historyroutes

所以我们可以通过使用ES6中的class写一个构造函数Router

// es6 构造方法
class Router{
    constructor(options){
        // 拿到两个参数
        this.history = options.history
        this.routes = options.routes
    }
}

我们接着可以发现historycreateWebHashHistory的执行结果,这个方法的调用就是启用哈希模式。

于是接下来,我们可以这样编写createWebHashHistory

function createWebHashHistory(){
    // 绑定事件,绑定一个fn回调函数
    function bindEvents(fn){
        window.addEventListener('hashchange', fn)//hash改变,fn回调函数触发
    }
    return {
        bindEvents,//函数
        //url是当前的路径
        url:window.location.hash.slice(1) || '/' 
    }
}

createWebHashHistory函数执行返回一个对象,返回的对象当中包含一个函数bindEvents和一个属性url当前地址

bindEvents函数绑定一个事件,负责监听hash值的变化,当hash地址发生变化时,fn回调函数就会被触发!这里绑定的事件是js自带的'hashchange'能够捕获到hash值的变更。

此时我们编写一下我们的构造方法Router

// es6 构造方法
class Router{
    constructor(options){
        this.history = options.history
        this.routes = options.routes
        // 路由要能读到当前的路径
        this.current = ref(this.history.url)

        // 在函数初始化的时候直接调用掉history的bindEvents函数
        this.history.bindEvents(() => {
            // 这个箭头函数就是fn回调函数
           this.current.value = window.location.hash.slice(1)
        })
    }
}

这样我们的构造函数就要拿到三个参数了,新增加了一个this.current读到当前的路径,同时我们也配置了bindEvents的回调函数fn,一旦fn触发,就会将当前新的路径赋值给this.current

实现能够被vue给use成功

实现到这里,我们还需要一个条件,也就是我们写出来的路由,要能够被我们的vue项目use掉!

所以,我们来到构造方法Router

// es6 构造方法
class Router{
    constructor(options){
        // 拿到两个参数
        this.history = options.history
        this.routes = options.routes
        // 路由要能读到当前的路径
        this.current = this.history.url

        // 在函数初始化的时候直接调用掉history的bindEvents函数
        this.history.bindEvents(() => {
            // 这个箭头函数就是fn回调函数
           this.current.value= window.location.hash.slice(1)
        })
    }
    // vue内定的官方方法,第三方插件一定要具备install方法
    install(app){
        console.log(app);
    }
}

我们在构造方法中,新添加了一个install方法,这是vue官方内定的一个方法,第三方的插件想要被vue能够use掉,就一定要具备这个install方法,我们来看看app的打印结果!

刨析Vue路由原理,解读路由Hash模式

这个其实就是vue的实例对象,其中包含了vue的各种方法

我们拿到其中的provide方法和component

provide:提供一个路由的实例对象,负责与inject进行配合,在进行组件RouterView的配置当中,我们需要拿到Router中配置的数组,跳转对应path的组件中,使用一个标记ROUTER_KEY(自己声明的),能够通过inject将这个标记对应的this,将Router注入到子组件当中,而这个this指向的就是所在类本身也就是Router

component:负责注册全局组件。就比如app.component('router-link',RouterLink)也就意味着,我们注册一个router-link的全局组件

拿到vue的实例对象后,接下来就可以开始我们的正式工作了!

实现router-link全局组件

要实现这样一个组件,我们就要有这样一个组件,我们知道官方的的标签是这样的

<router-link to="/">Home</router-link>|
<router-link to="/about">About</router-link>

可以发现,我们要接收一个to参数,并且组件的标签之间可以放东西,所以,我们打造这个组件需要插槽,并且要接收一个参数to,将标签转换为a标签,实现url的跳转。

所以,我们可以声明一个组件RouterLink.vue

<template>
    <a :href="'#'+to">
        <!-- 插槽 -->
        <slot />
    </a>
</template>

<script setup>
defineProps({
    to:{
        type:String,
        required:true,//必须传

    }
})
</script>

在这个组件当中,我们会拿到父组件传过来的to地址,拼接到a标签对应的路径当中。

回到我们自己的定义的配置文件index.js中,我们引入这个组件

import RouterLink from './RouterLink.vue'
    ...
    install(app){
        // 注册全局组件
        app.component('router-link',RouterLink)

    }

现在,看看我们这个组件的效果!

注:我们需要修改路由的配置文件

将: import {createRouter,createWebHistory,createWebHashHistory} from 'vue-router'

修改为:import {createRouter,createWebHashHistory} from './myRouter'

使用我们自己配置的路由!

刨析Vue路由原理,解读路由Hash模式

实现router-view全局组件

要实现router-view这样一个效果,我们需要拿到vue-routerRouter的实例对象,并且根据当前路径,与配置文件routes中的path进行比对,找到对应的路径,然后展示相应的组件,也就是页面!

重要!!!我们的页面需要是响应式的,所以我们要引入ref进行响应式设计!

来到我们的配置文件

//引入组件
import RouterView from './RouterView.vue'
// 注入,需要provide
import {inject} from 'vue'
//声明一个标记
const ROUTER_KEY = '_router_'//这是一个标记
...
function useRouter(){
    return inject(ROUTER_KEY)
}
...
 install(app){
        console.log(app);
        // ROUTER_KEY是我们声明的标记,this指向的是Router类
        app.provide(ROUTER_KEY,this)

        // 注册全局组件
        app.component('router-link',RouterLink)
        app.component('router-view',RouterView)

    }
}
//抛出
export {
        createRouter,
        createWebHashHistory,
        useRouter
}
  1. 在我们的配置文件引入了inject和组件RouterView,同时定义了一个标记ROUTER_KEY
  2. 我们声明了一个useRouter函数,返回的是inject注入,标识ROUTER_KEY,子组件可以通过调用这个函数来注入router
  3. app.provide提供一个路由的实例对象,通过POUTER_KEY这个标识,返回一个this指向Router的实例对象!
  4. 通过app.component('router-view',RouterView)声明全局组件

RouterView组件设计

<template>
    <!-- vue自带的组件 -->
    <component :is="component"></component>
</template>

<script setup>
import {computed} from 'vue'
import {useRouter} from '../myRouter/index.js'

const router = useRouter() //在当前组件注入了router
console.log(router)
    const component = computed(()=>{
        // 找到对应路径的path返回相应的组件 映射关系
        const route = router.routes.find((route)=>{
            return route.path === router.current.value

        })
        return route ? route.component :null
    })
</script>

在组件设计当中,我们使用到了vue自带的组件component标签,可以通过is属性来设置要展示的组件!

js部分:

  1. 引入了计算属性computed和配置文件中的useRouter方法
  2. 定义一个变量router等于useRouter的执行结果,我们就实现了router的注入!我们可以看到router的打印结果刨析Vue路由原理,解读路由Hash模式
  3. 定义一个变量component进行组件映射
    1. 通过计算属性执行一个回调函数,定义一个变量routerouter.routes.find返回的结果,
    2. find是官方自带的一个方法,可以找到符合逻辑 return route.path === router.current.value的结果,通过return返回出来!
    3. return route ? route.component :null通过三元表达式判断route是否存在,存在则返回route.component返回对应的组件,否则返回空,并且赋值给component
  4. 通过对component标签使用v-bind绑定is属性,并且赋值为变量component,就实现了一个简单的vue-router路由Hash模式的源码!

看看效果!

刨析Vue路由原理,解读路由Hash模式

全部代码

刨析Vue路由原理,解读路由Hash模式

router配置文件index.js

import {createRouter,createWebHashHistory} from './myRouter'
import Home from '../views/Home.vue'
import About from '../views/About.vue'


const routes = [
    {
        path:'/',
        name:'home',
        component:Home
    },
    {
        path:'/about',
        name:'about',
        component:About
    }
]

const router = createRouter({
    history:createWebHashHistory(),//hash模式
    routes
})

export default router

myRouter配置文件index.js

import {ref} from 'vue'
import RouterLink from './RouterLink.vue'
import RouterView from './RouterView.vue'

// 注入,需要provide
import {inject} from 'vue'


const ROUTER_KEY = '_router_'//这是一个标记

function useRouter(){
    return inject(ROUTER_KEY)
}

function createRouter(options) {
    return new Router(options)//调用就返回一个实例对象
}

function createWebHashHistory(){
    // 绑定事件,绑定一个fn回调函数
    function bindEvents(fn){
    //监听Hash变化
        window.addEventListener('hashchange', fn)
    }
    // 返回了一个对象,对象里面有一个函数
    return {
        // 这是一个函数
        bindEvents,
        // window.location.hash.slice(1) || '/' 代表当前的路径,如果当前的路径为空,就默认为'/'
        url:window.location.hash.slice(1) || '/'
    }
}

// es6 构造方法
class Router{
    constructor(options){
        // 拿到两个参数
        this.history = options.history
        this.routes = options.routes
        // 路由要能读到当前的路径
        this.current = ref(this.history.url)

        // 在函数初始化的时候直接调用掉history的bindEvents函数
        this.history.bindEvents(() => {
            // 这个箭头函数就是fn回调函数
           this.current.value= window.location.hash.slice(1)
        })
    }
    // vue内定的官方方法,第三方插件一定要具备install方法
    install(app){
        console.log(app);
        // ROUTER_KEY是我们声明的标记,this指向的是Router类
        app.provide(ROUTER_KEY,this)

        // 注册全局组件
        app.component('router-link',RouterLink)
        app.component('router-view',RouterView)

    }
}
//抛出
export {
        createRouter,
        createWebHashHistory,
        useRouter
}

RouterLink.vue

<template>
    <a :href="'#'+to">
        <!-- 插槽 -->
        <slot />
    </a>
</template>

<script setup>
defineProps({
    to:{
        type:String,
        required:true,//必须传

    }
})
</script>

RouterView.vue

<template>
    <!-- vue自带的组件 -->
    <component :is="component"></component>
</template>

<script setup>
import {computed} from 'vue'
import {useRouter} from '../myRouter/index.js'

const router = useRouter() //在当前组件注入了router
console.log(router)
    const component = computed(()=>{
        // 找到对应路径的path返回相应的组件 组件映射关系
        // 如果当前url是'/'就返回Home.vue
        const route = router.routes.find((route)=>{
            return route.path === router.current.value

        })
        return route ? route.component :null
    })
</script>

最后

到这里我们已经完成了vue源码系列的路由之Hash模式精简版代码的实现了。

这个其实算是vue源码中逻辑算是比较简单的一部分了

总体来看,我们通过手动打造了createRouter,createWebHashHistory,useRouter等一系列方法,定义了构造函数,通过install让我们的配置文件能够被vue给use掉,再定义全局组件,实现父子组件传值等操作实现了这样一个路由!

假如你也和我一样,在准备冲刺春招,欢迎大家加微信shunwuyun,我们这里有好多小伙伴和各种大佬可以相互鼓励,分析信息,模拟面试,共读源码,齐刷算法,手撕面经。加油!友友们!

如果,你觉得这篇文章有帮助的话,可以帮博主点赞+评论+收藏,三连一波!感谢!

往后,我还会持续输出vue相关的文章,如vue路由原理,node相关的内置模块,koa的使用等等文章,感兴趣的小伙伴可以关注一波!

代码已经上传至个人Github:一个修远君的库之vue路由源码Hash模式

转载自:https://juejin.cn/post/7338250632938618889
评论
请登录