简易版 Vue-Router 源码分享

242 阅读3分钟

简易版 Vue-Router 实现思路

1. 规划路由器核心功能

路由注册与初始化

  • grouter/index.js文件中,定义了Router类与createRouter函数来创建和初始化路由实例。此实例包含路由表(routes数组)以及历史管理对象(通过createWebHashHistory()函数生成)。
  • 路由表routes设置了路径和对应的组件,使得打开特定URL时可以加载相应的组件。

路由响应与视图更新

  • RouterView组件在路由变化时,动态地根据当前路由渲染相应组件。这通过计算属性component实现,它读取当前路由和匹配的组件。
  • 路由切换触发时,依赖于current响应式变量的变化,该变量代表当前激活的路由URL。

History管理

  • 实现了基于hash的路由(createWebHashHistory),封装了URL变化的监听和更新逻辑。它通过bindEvents方法绑定浏览器的hashchange事件,使得每次URL改变都会更新Router的当前路由状态。

2. 设计组件接口

RouterLink

  • 实现了路由链接组件,使用户能够通过点击导航到指定的路由。组件渲染为<a>标签,其href属性预设为#加上目标路由的路径。

RouterView

  • 动态渲染当前路由对应的组件。根据Router实例的当前路由状态从路由表中查找并渲染相应的Vue组件。

3. 开发History管理器

  • createWebHashHistory函数封装了浏览器历史API的使用方式,专门针对hash形式的URL。提供了方法来绑定事件监听器,响应URL的hash部分的变化,并通告给路由系统。

4. 实现Router类

  • 此类构造时接收一个包含historyroutes的选项对象。通过bindEvents绑定的方法监听URL的变化,并更新当前路由状态。
  • 提供install方法,使其能够作为Vue插件安装,注册RouterLinkRouterView全局组件,并在Vue实例中提供路由器实例。

5. 集成到Vue中

  • 通过app.use(router)将路由器集成到Vue应用中,这一步是在main.js文件中完成的。安装路由器时,Routerinstall方法被调用,完成Vue组件的全局注册和提供路由实例。

简易版 Vue-Router 源码

router/grouter/index.js

import RouterLink from "./RouterLink.vue"
import RouterView from "./RouterView.vue"
import { ref,inject } from 'vue'

const ROUTER_KEY = '__router__'

// 在任何地方,就可以拿到 router 对象(简洁方便)
const useRouter = () => {
    return inject(ROUTER_KEY)
}
// 封装 VueRouter
const createRouter = (options) => {
    return new Router(options)
}
// 返回一个 hash 路由对象
// url #/about 
// hashChange
const createWebHashHistory = () => {
    function bindEvents(fn) {
        window.addEventListener('hashchange', fn)
    }
    return {
        url: window.location.hash.slice(1) || '/',
        bindEvents
    }
}
class Router {
    constructor(options) {
        this.history = options.history
        this.routes = options.routes
        console.log(options, '////');
        // 当前的 url 状态,是 router-view component 计算属性的依赖
        this.current = ref(this.history.url)
        this.history.bindEvents(() => {
            // this 指向 Router
            this.current.value = window.location.hash.slice(1)
            console.log(this.current.value);
        })
    }
    install(app) {
        console.log(app);
        // console.log('vue 要对接 vue-router');
        // 全局组件的声明
        // 全局提供 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="'#' + props.to">
        <slot></slot>
    </a>
</template>


<script setup>
import { defineProps } from 'vue';
let props = defineProps({
    to: {
        type: String,
        required: true      // 这个参数是必须要传递的
    }
})
</script>

RouterView.vue 页面

<template>
    <component :is="component"></component>
</template>


<script setup>
import { computed, } from 'vue';
import { useRouter } from './index';
// 怎么拿到的 component?
// router -> routes / -> component:About
let router = useRouter()
console.log(router,'?????????');

// 这里的 component 是动态的,响应式的,用计算属性实现的
const component = computed(() => {
    const route = router.routes.find(
        (route) => route.path === router.current.value
    )
    return route ? route.component : null
})
</script>

grouter/index.js 页面

import { createRouter,createWebHashHistory } from "./grouter/index";
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(),
        routes
    })

export default router

main.js 页面

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'

// vue 是一个非常优秀的框架,只做自己 mvvm 等一些功能
// 生态 vue-router 规定install方法
const app = createApp(App)

app
    .use(router)
    .mount('#app')

App.vue 页面

<script setup>
</script>

<template>
  <router-link to="/">首页</router-link>
  <router-link to="/about">About</router-link>
  <router-view />
</template>

<style scoped></style>

Home.vue 页面

<template>
    <div>
        home
    </div>
</template>

<script setup>

</script>

<style lang="scss" scoped>

</style>

About.vue 页面

<template>
    <div>
        about
    </div>
</template>

<script setup>

</script>

<style lang="scss" scoped>

</style>