手写Router,解析Router在Vue底层到底干了什么?
今天我们就来彻底地剖析Router的底层究竟是如何封装实现的,同时我们也手写完成一个自己的handRouter,能够完成Router的基本功能。Router中的主要四个功能有:
- 全局注册
RouterView
和RouterLink
组件。- 添加全局
$router
和$route
属性。- 启用
useRouter()
和useRoute()
组合式函数。- 触发路由器解析初始路由。
手写
新建目录结构如下 在index.js文件中实现核心业务index.js
这里我们实现的history模式是hashHistory 实现思路: ①创建Router实例(包括属性history,routes,current),并在创建实例的方法上绑定事件监听,监听url的hash值改变,当hash值改变时将current的值改为此时的hash值。 ②将RouterLink和RouterView注册为全局组件 ③利用privide,inject将实例对象暴露给RouterView 源码如下:
import RouterLink from './RouterLink.vue'
import RouterView from './RouterView.vue'
import { ref,inject,provide } from 'vue'
export const createRouter = (options) => {
return new Router(options)
}
//单例的职责
export const createHashRouter = () => {
// history 对象
function bindEvents(fn){
window.addEventListener('hashchange', fn)
}
return {
url: window.location.hash.slice(1) || '/',
bindEvents
}
}
// 标记一下 router 要向全世界暴露
const ROUTER_KEY = '__router__'
export const useRouter = () => {
return inject(ROUTER_KEY)
}
class Router {
// use(Router) 会调用插件的install方法
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)
})
}
install(app) {
// 全局声明由一个router 全局使用的对象
app.provide(ROUTER_KEY, this)
console.log('准备与vue 对接', this);
app.component('router-link', RouterLink)
app.component('router-view', RouterView)
}
}
Router-Link
实现思路:
①Router-Link组件主要实现原理是a标签跳转路由,利用a标签的锚点链接**href="#"
**再加上to这个参数传递给a标签
②a标签的内容由插槽slot接收
源码如下
<template>
<a :href="'#' + props.to">
<slot></slot>
</a>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
to:{
type: String,
required: true
}
})
</script>
<style lang="less" scoped>
</style>
Router-view
实现思路: ①利用vue中内置组件动态地渲染组件,它允许你在运行时根据表达式的值来渲染哪个组件,基于组件的name或者component对象动态地切换所显示的组件,component标签通常v-bind:is结合使用,v-bind:is绑定一个动态组件名或者组件对象。 ②通过index.js中useRouter()方法暴露出来的实例Router对象,拿到current当前路由的值,与routes数组中的各个数据进行比较,若有相同则记录下当前路由route并返回给component组件绑定的is。 源码如下
<template>
<component :is="component"></component>
</template>
<script setup>
import { computed } from 'vue';
import Home from '../../pages/Home.vue'
import About from '../../pages/About.vue'
import { useRouter } from './index.js';
const router = useRouter()
console.log(router);
// computed是基于一些基础属性的变化来计算,也就是根据ref和props或者computed类型的值的改变而变化
// 响应式 router.current 设置为ref
// ref是私有响应式变量, 只能依靠自身函数进行修改。无法通过变量的变化实时监视并修改。
const component = computed(()=>{
const route = router.routes.find(
(route)=>route.path == router.current.value
)
console.log(route);
return route?route.component:null;
})
</script>
<style lang="scss" scoped>
</style>
配置Router
这样完成了这三个模块的编写,router就可以直接到路由配置文件中使用了,别忘记了引入你的三个文件。
import {createRouter,createHashRouter} from './grouter/index'
import Home from '../pages/Home.vue'
import About from '../pages/About.vue'
import { ref } from 'vue'
const routes = [
{
path:'/',
name:'Home',
component:Home
},
{
path:'/About',
name:'About',
component:About
}
]
const router = createRouter({
history: createHashRouter(),
routes
})
export default router
实测App根组件,能够正常跳转路由
<template>
<header>
<nav>
<router-link to="/">主页</router-link>
<router-link to="/About">关于</router-link>
</nav>
</header>
<main>
<router-view></router-view>
</main>
</template>
<script setup>
</script>
<style lang="less" scoped></style>
手写完工!
转载自:https://juejin.cn/post/7393740927154864164