前言
今天来手写实现vue-router
简化版,主要以hash模式来实现网址的跳转改变。
涉及到router-link,router-view,路由配置
主要目的是为了深刻的理解vue-router在路由加载背后到底做了些什么?话不多说开始进入今天的主题。
这是App.vue文件,感兴趣的小伙伴可以一起来实现
App.vue
<template>
<div>
<header>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">About</router-link>
</nav>
</header>
<main>
<router-view></router-view>
</main>
<footer></footer>
</div>
</template>
<script setup>
</script>
<style lang="css" scoped>
</style>
router/index.js完整代码
import { createRouter, createWebHashHistory } from './grouter/index.js';
import Home from '../pages/Home.vue';
import About from '../pages/About.vue';
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
name: 'Home',
component: Home
}, {
path: '/about',
name: 'About',
component: About
}
]
})
export default router;
理解app.use
一般我们在使用路由插件时是先进行npm i vue-router
下载路由,然后在main.js
中使用app.use
导入router
插件。想要手写vue-router
,必须先搞懂app.use()
干了一件什么事情。我们去官方文档下面看看。
看完我们知道插件中自带一个
install()
方法,在app.use方法内会自动执行。所以我们只需要关注插件的实现即可。app.use()
传送门 应用实例 API | Vue.js (vuejs.org)
创建Router对象来管理路由
在useRouter
方法和createRouter
中我们可以明白,在vue-router
中是有一个Router
对象对着全局的路由进行管理。所以首先我们需要进行Router
对象的创建,而对象的功能具备install
方法,将Router
对象挂载在app上。然后constructor
(构造方法)需要进行history和routes
的加载配置。让我们来逐一实现。
- 首先是
install
方法内,我们需要将整个路由对象暴露出去,然后使用createRouter
方法来接受暴露的Router
对象在默认导出,这里我们使用provide
和inject
方法来对Router对象的暴露与接受。 - app将组件变为全局组件的方法,
app.component
来注册router-link和router-view
完整Router和useRouter代码,路径:router/grouter/index.js
// 标记一下 router 要向所有人暴露
const ROUTER_KEY = "__router__";
export const useRouter = () => {
//通过关键字获取暴露的Router对象
return inject(ROUTER_KEY);
}
class Router {
constructor(options) {
//history对象就是createHashWebHistroy返回的history对象
this.history = options.history;
// 路由对象数组,path,component,name等配置对象
this.routes = options.routes;
//获取当前路由地址
this.current = ref(this.history.url);
// 这里的方法是监听hashchange事件来更新current,切换路由
//,可以先不看这一段等会会讲
this.history.bindEvent(() => {
this.current.value = window.location.hash.slice(1);
})
}
//use 会调用 插件的install方法
install(app) {
// 全局声明 有一个router 全局使用的对象
app.provide(ROUTER_KEY, this);
console.log("准备与vue对接");
//注册全局组件
app.component('router-link', RouterLink);
app.component('router-view', RouterView);
}
}
router-link和router-view的实现
首先创建两个对应的vue文件,然后让我们来分别实现其中的功能。
router-link
<router-link to="/">首页</router-link>
router-link
本质是使用了a标签,所以我们在进行router-link
组件封装时, 直接使用<a></a>
标签进行地址栏的跳转,to
可以用defineProps
来接受,但是router-link
中的文字如何添加进入a标签中呢?这里我们使用到插槽Slots
语法
通过图片我们可以看到,只需要在
<a></a>
标签内添加插槽即可实现文字的接收,这里是Slots传送门,感兴趣的小伙伴可以自行查看:插槽 Slots | Vue.js (vuejs.org)
完成之后我们只需要注意我们使用的是hash模式,所以地址栏需要出现#
router-link完整代码 路径:router/grouter/RouterLink.vue
<template>
<a :href="'#' + props.to">
<slot></slot>
</a>
</template>
<script setup>
const props = defineProps({
to: {
type: String,
required: true,
},
});
</script>
<style lang="css" scoped>
</style>
测试了一下发现没啥问题,地址可以改变。那么接下来就是router-view该如何实现
router-view
router-view需要的就相对繁琐一些,因为需要动态的根据地址hash的改变来进行组件的渲染,所以这里我们主要需要两个功能的实现。
- 组件的动态渲染
- hash地址的寻找正确的组件
首先思考功能1,v-if
虽然可以动态渲染组件但是太多组件的话这要多少个v-else-if
,所以排除,那有没有一种
可以让我们只需要写一个组件然后让组件自动渲染不同的组件呢?你别说哦~~还真有。
传送门:内置特殊元素 | Vue.js (vuejs.org)
这个component组件还真可以满足我们的需求进行组件的动态渲染。那么我们接下来只需要考虑组件的寻找了。
我们可以创建一个mapRouter
的map
对象,然后在组件被加载时,进行当前router-view
对象中的组件信息进行注册(set(path,component)
),然后更改地址,只需要进行get方法就可以拿到对应的组件,但是注意有可能用户会输入错误代码所以需要进行校验
router-view完整代码 路径:router/grouter/RouterView.vue
<template>
<!-- 动态组件 -->
<component :is="component"></component>
</template>
<script setup>
import { useRouter } from "./index.js";
import { computed } from "vue";
const router = useRouter();
// router-view 动态组件 展示 依赖于url的变化
const mapRouter = new Map();
const reflectMapRouter = (router) => {
router.routes.forEach((route) => {
// 更具路径进行组件查询,保证O(1)时间
mapRouter.set(route.path, route.component);
});
};
//方法执行进行组件注册
reflectMapRouter(router);
// 响应式 router.current 设置为ref
//设置为计算属性响应式更新
const component = computed(() => {
const nowRoute = mapRouter.get(router.current.value);
//这里注意,需要进行校验,因为用户可能输入错误网址
return nowRoute ? nowRoute : null;
});
</script>
<style lang="css" scoped>
</style>
createWebHashHistory&createRouter
createWebHashHistory
两个事,1-监听网址的改变,2-返回地址中的hash值
createRouter
就更简单了返回new Router(),
代码 路径 router/grouter/index.js
export const createRouter = (options) => {
return new Router(options);
}
export const createWebHashHistory = () => {
function bindEvent(fn) {
window.addEventListener('hashchange', fn);
}
// history 对象
return {
url: window.location.hash.slice(1) || '/',
bindEvent
}
}
结语
本篇文章到此就基本全部完成了,文章全部为手写,所以可能会有一些打错字的情况,欢迎在评论区指正。又或者有小伙伴和大佬对本篇文章有别的看法和指导也欢迎在评论区指出,本人非常欢迎大佬和伙伴的建议和指正。- ̗̀(๑ᵔ⌔ᵔ๑)
好了本篇文章就到此为止了,喜欢的话就点个赞和关注吧!