在单页应用流行的当下,路由的使用是非常优雅非常的爽。在这段时间的学习里,对它的工作机制有了一定的认识,所以我准备结合之前介绍的哈希路由手搓一个router。
话不多说,开干。
目录结构
首先,先设置配置两个路由页面,并在单页上可以点击跳转。
App.vue
<template>
<header>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">About</router-link>
</nav>
</header>
<router-view/>
</template>
<script setup>
// import { RouterView } from 'vue-router';
</script>
<style lang="css" scoped>
</style>
router/index.js
import { createRouter,createHashHistory } from './grouter/index'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
const router = createRouter(
{
history: createHashHistory(),
routes: [
{
path:'/',
name:'Home',
component:Home
},
{
path:'/about',
name:'About',
component:About
}
]
}
)
export default router
理解app.use(router)
以上的准备工作做完之后,就该是引入正题了,为让路由全局实现,我们应在main.js中引入路由插件。那么为了实现手写router我们就应该了解到,app.use(router)干了什么,我们来到router官网和vue官网上查看。
创建路由管理器
理解app.use(router)的作用后,再结合我们平时使用的useRouter和createRouter,可以发现只有一个router对象对全局路由进行管理(即单例模式),并且具有install()方法和构造函数初始化路由。除了这些外,路由管理文件中应该还需要抛出createRouter、createHashHistory、useRouter。
-
install():这个方法是
Router类的一个实例方法,用于将Router实例安装到vue应用中。它使用provide将Router实例提供给子组件,以及利用component注册RouterLink和RouterView组件到vue应用中,使它们成为全局可用的组件。 -
createRouter:这个函数用于创建一个新的
Router实例。它接收一个包含路由配置的Options对象作为参数,并返回一个新创建的Router实例。 -
createHashHistory:这个函数用于创建一个基于浏览器哈希历史的路由历史对象。它绑定事件监听器以监听
hashchange事件,当URL的哈希部分发生改变时,事件处理器会被触发,更新当前路由的URL。 -
useRouter:这个函数用于从
vue应用中获取Router实例。它使用inject函数来获取在应用中被提供的Router实例(实例将在router对象中的install方法中利用provide将实例曝光在全局中)。
grouter/index.vue完整代码
// 导入 RouterLink 组件
import RouterLink from "./RouterLink.vue";
// 导入 routerView 组件
import routerView from "./RouterView.vue";
// 导入 inject 和 ref 函数
import { inject, ref } from "vue";
// 单例的责任
// 创建一个 Router 实例的工厂函数
export const createRouter = (Options) => {
return new Router(Options);
}
// 创建一个处理 URL 哈希变化的 history 对象
export const createHashHistory = () => {
// 绑定事件处理函数的辅助函数
function bindEvents(fn) {
window.addEventListener('hashchange', fn);
}
// history 对象
return {
url: window.location.hash.slice(1) || '/',
bindEvents
}
}
// 标记一下 router 要向全世界暴露
const ROUTER_KEY = "__router__";
// use 开头的是一派 hooks 函数式编程
// 获取全局注入的 Router 实例的 hooks 函数
export const useRouter = () => {
return inject(ROUTER_KEY);
}
// Router 类
class Router {
constructor(Options) {
// 初始化 history 对象
this.history = Options.history;
// 初始化路由表
this.routes = Options.routes;
// 使用 ref 来创建一个响应式的数据 current
this.current = ref(this.history.url);
// 绑定 hashchange 事件,更新 current
this.history.bindEvents(() => {
// console.log('///');
this.current.value = window.location.hash.slice(1) || '/';
})
}
// 用于在 Vue 应用中安装 Router 实例的方法
install(app) {
// 提供一个全局的 Router 实例,供所有组件注入和使用
app.provide(ROUTER_KEY, this);
console.log('准备与路由vue 对接', app);
// 在应用中注册 router-link 组件
app.component('router-link', RouterLink);
// 在应用中注册 router-view 组件
app.component('router-view', routerView);
}
}
RouterLink组件实现
router-link组件能够处理客户端路由,当用户点击链接时,它会更新浏览器的URL并重新渲染对应的组件,而不需要重新加载整个页面。所以我们需要获取到需要跳转的链接,使用defineProps接收父组件传来的链接地址,再利用<slot />显示父组件传递给这个链接组件的内容。
grouter/RouterLink.vue完整代码
<template>
<a :href="'#' + props.to">
<slot />
</a>
</template>
<script setup>
import {defineProps} from 'vue'
const props = defineProps({
to: {
type: String,
required: true
}
})
</script>
<style lang="css" scoped>
</style>
RouterView组件实现
router-view是实现路由跳转后的页面渲染。所以我们使用Vue中的动态组件语法,用:is绑定告诉Vue要渲染的组件是由component计算属性决定的。当component的值改变时,这个组件会重新渲染,显示与当前路由相匹配的组件。
<template>
<!-- 使用 :is 指令动态渲染组件 -->
<component :is="component"></component>
</template>
<script setup>
import { useRouter } from './index.js';
import { computed } from 'vue';
const router = useRouter();
// router-view 动态组件 展示 依赖于url的变化
// 响应式 router.current 设置为 ref
const component = computed(() => {
// 在路由表中查找匹配当前 URL 的路由记录
const routes = router.routes.find(
// 如果有路由的路径与当前的 URL 匹配,则返回该条记录
(route) => route.path == router.current.value
)
console.log(routes);
// 如果找到匹配的路由记录,则返回该记录的组件,否则返回 null
return routes? routes.component : null;
})
</script>
<style lang="css" scoped></style>
总结
虽然上述代码只是一个简化的示例,但它展示了构建一个基本的路由系统的步骤。在实际项目中,vue-router提供了更丰富的功能,如嵌套路由、命名视图、历史模式等,这些都是手写版本所不具备的。然而,通过手写实现,我们可以更好地理解路由背后的机制,这对于深入学习框架的高级特性和优化应用性能都大有裨益。