引言
众所周知路由
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
组件的实现方式。