引言
众所周知路由
Router在Vue中是特别重要的,是我们构建单页应用(SPA)必不可少的。我们经常使用但可能对其底层是如何实现的所知甚少。在本篇文章,我们将去解读Router的底层原理,手动去实现Router-link和Router-view组件。
初始化Vue应用
首先,我们先在 main.js 中引入和使用我们要实现的 router
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router/index'
const app = createApp(App)
// use 方法挂载了路由
// vue 只负责 组件思想, mvvm , 响应式 等核心
// 其他的交给生态系统 一起开源 vue-router 是vue 生态中的路由模块
// vue 和它生态的对接呢? 就是这个use 方法
app.use(router)
.mount('#app')
app.use() 调用是 Vue 插件系统的核心,它的作用是将实例挂载到整个 Vue 应用上。这里实现了路由的注册和全局组件的使用。也正是因为use接口功能强大,极大地丰富了Vue的生态。
然后我们在src目录下新建一个pages文件夹用于存放视图组件。
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>
Router路由
Router 初始化
我们在src目录下新建一个router文件夹,在该目录下新建一个index.js用于配置路由表
import { createRouter, createWebHashHistory } from './grouter/index'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
我们定义了路由配置并创建了路由实例。
-
routes:定义了一个包含两个路由对象的数组,每个对象代表一个路由配置。path表示路径,name表示路由名称,component表示该路径对应的组件。 -
createRouter:使用createRouter函数创建路由实例,并传入history对象和路由配置。
Router 构建
然后在该目录下再新建一个 grouter 文件夹,新建两个vue文件。
RouterLink.vue
RouterView.vue
router-link 实现
<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="scss" scoped>
</style>
-
<a :href="'#' + props.to">:生成一个a标签,href属性的值是props.to前面加上#。这意味着该链接会导航到指定的哈希路径。 -
<slot />:插槽允许在使用router-link组件时,自定义链接的内容。就是渲染双标签中间的文本内容。
router-view 实现
再新建一个index.js文件。
首先我们定义一个Router 对象
class Router {
constructor(options) {
this.history = options.history
this.routes = options.routes
this.current = ref(this.history.url)
this.history.bindEvents(() => {
// console.log('//////////')
this.current.value = window.location.hash.slice(1)
})
}
// use 调用 插件install
install(app) {
// 全局声明有一个router 全局使用的对象
app.provide(ROUTER_KEY, this)
console.log('准备与vue 对接', app)
app.component('router-link', RouterLink)
app.component('router-view', RouterView)
}
}
constructor(options):构造函数接收一个 options 参数,用于初始化路由实例。this.history 保存了历史记录对象,this.routes 保存了路由配置,this.current 是一个响应式引用,保存当前的 URL。然后绑定了 hashchange 事件,当 URL 的 hash 部分发生变化时,更新 this.current 的值。
这里的 install(app) 是 Vue 插件的标准方法。当我们使用 app.use(router) 时,这个方法会被调用。
-
app.provide(ROUTER_KEY, this):通过provide方法,我们可以将Router实例注入到整个应用中,这样任何组件都可以通过inject方法访问到路由实例。 -
app.component('router-link', RouterLink)和app.component('router-view', RouterView):注册全局组件router-link和router-view,使它们在整个应用中都可以使用。
// 标记一下 router 要向全世界暴露 常量所以名称要大写
const ROUTER_KEY = '__router__'
// use 开头的是一派 hooks 函数式编程
export const useRouter = () => {
return inject(ROUTER_KEY)
}
在 Vue.js 应用中提供一个便捷的方式来访问 Router 实例。通过调用这个函数,用户可以在任何组件中获取到 Router 实例,并进而访问实例上的属性和方法。
// 单例的责任
export const createRouter = (options) => {
return new Router(options)
}
// 提供一种灵活的方式来注册和响应浏览器的 hashchange 事件
export const createWebHashHistory = () => {
function bindEvents(fn) {
window.addEventListener('hashchange', fn)
}
// history 对象
return {
url: window.location.hash.slice(1) || '/',
bindEvents
}
}
-
createRouter函数:该函数用于创建一个Router实例,接收一个options参数,该参数包含了路由的配置。 -
createWebHashHistory函数:该函数用于创建一个带有hash路由模式的历史记录对象。bindEvents方法用于绑定hashchange事件,这样当 URL 的hash部分发生变化时,可以触发相应的回调函数。
然后我们再回到 RouterView.vue
<template>
<!-- 动态组件 -->
<component :is="component" />
</template>
<script setup>
import {computed} from 'vue'
// ref 是私有的 props 是父组件向子组件传递数据 computed 根据其他数据派生新数据,有个计算过程,且这个属性会根据其他数据变化而变化
import Home from '../../pages/Home.vue'
import About from '../../pages/About.vue'
import { useRouter } from './index.js';
const component = computed(() =>{
const route = router.routes.find(
(route) => route.path == router.current.value
)
console.log(route,'////');
return route? route.component: null
// 错误的route则不渲染
})
// router-view 动态组件 展示 依赖于 url 的变化
// 响应式 router.current 设置为ref
const router = useRouter();
console.log(router);
</script>
<style lang="scss" scoped>
</style>
这里使用 Vue 的动态组件<component :is="component" />功能,根据 component 变量的值来渲染不同的组件。
-
useRouter:通过useRouter函数获取路由实例。 -
computed:定义了一个计算属性component,根据当前的路径 (router.current.value),查找匹配的路由对象,并返回相应的组件。如果找不到匹配的路由,则返回null。
完整代码
grouter中的 index.js
import RouterLink from './RouterLink.vue'
import RouterView from './RouterView.vue'
import {ref, inject} from 'vue'
// 单例的责任
export const createRouter = (options) => {
return new Router(options)
}
// 提供一种灵活的方式来注册和响应浏览器的 hashchange 事件
export const createWebHashHistory = () => {
function bindEvents(fn) {
window.addEventListener('hashchange', fn)
}
// history 对象
return {
url: window.location.hash.slice(1) || '/',
bindEvents
}
}
// export const createWebHistory = () => {
// return {
// url: window.location.pathname,
// bindEvents(fn) {
// window.addEventListener('popstate', fn)
// }
// }
// }
// 标记一下 router 要向全世界暴露 常量所以名称要大写
const ROUTER_KEY = '__router__'
// use 开头的是一派 hooks 函数式编程
// 在 Vue.js 应用中提供一个便捷的方式来访问 Router 实例。通过调用这个函数,用户可以在任何组件中获取到 Router 实例,并进而访问实例上的属性和方法。
export const useRouter = () => {
return inject(ROUTER_KEY)
}
class Router {
constructor(options) {
this.history = options.history
this.routes = options.routes
this.current = ref(this.history.url)
this.history.bindEvents(() => {
// console.log('//////////')
this.current.value = window.location.hash.slice(1)
})
}
// use 调用 插件install
install(app) {
// 全局声明有一个router 全局使用的对象
app.provide(ROUTER_KEY, this)
console.log('准备与vue 对接', app)
app.component('router-link', RouterLink)
app.component('router-view', RouterView)
}
}
效果展示
最后我们在 App.vue 中使用我们注册到全局的 <router-link> 和 <router-view>组件
<script setup>
</script>
<template>
<header>
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">About</router-link>
<!-- 其中首页和About就是插槽对应的 -->
</nav>
</header>
<main>
<router-view />
</main>
<footer>
</footer>
</template>
<style scoped>
</style>
总结
我们通过讲解逐步实现一个简单的 Vue 路由系统,展示了 router-link 和 router-view 两个核心组件的实现过程。我们从 main.js 开始,讲解了 app.use(router) 的作用,并深入分析了 router/grouter/index.js 中 Router 类的实现细节。通过自定义的 Router 类,我们实现了 URL 的响应式管理和路由组件的动态渲染。最终,我们结合具体代码示例,详细说明了 router-link 和 router-view 组件的实现方式。