使用过vue的小伙伴们都知道,路由使用起来非常简单,只用引入,定义,配置,输出还能需要的地方引入使用,实现单页应用的页面跳转,今天我们就来说一说,我不import,也能实现
<router-link>、<router-view>
的路由效果。来到了我们最熟悉的手写环节。
一、知识储备
(1)、全局组件
- 在vue中,路由是在很多地方都要使用的组件,所以在手写的实现过程,我们将它定义成全局组件,一旦被注册,在任何需要使用的模板组件中,都不需要引入,直接可以使用。
- 使用app.component()方法注册一个全局组件,这里的app实际上是Vue实例。接受两个参数:name:组件名、options:组件定义对象,既可以是Vue组件,也可以是自定义组件。
(2)、provide和inject
- 用于祖先-后代关系通信的特性,这就意味着允许所有的后代可以访问到祖先组件注入的数据或者服务,不需要通过组件逐层的传递props。在跨多个组件层级传递数据或服务时多使用。
(3)、<slot>
元素,也叫插槽
- 它允许父组件向子组件的特定位置插入内容,这个机制叫做内容分发。它让组件的设计更为灵活,允许组件的使用者决定某些部分的具体内容,不再仅仅是组件的设计者。
使用示例:
父组件
xml
<template>
<div>
<child-component>
<p>子组件中显示的内容</p>
</child-component>
</div>
</template>
子组件
xml
<template>
<div>
<h1>我是子组件</h1>
<slot></slot> <!-- 显示的位置 -->
</div>
</template>
(4)<component>
元素,动态组件标签
- 根据一个表达式来动态的渲染不同的组件,需要一个is特性,绑定到一个表示组件名称的字符串,或者是一个组件选项对象。这个值通常是响应式的数据属性,决定了渲染哪个组件。
二、实现过程
1、router-link的实现
- 在使用vue的原生route-link时,会有一个to参数,值为跳转的目标路由。这里我们定义一个router-link组件,接收父组件,也就是使用router-link的组件传来的参数,并使用slot显示跳转信息。
xml
<template>
<div>
<a :href="'#' + props.to">
<slot></slot>
</a>
</div>
</template>
<script setup>
const props = defineProps({
to:{
type:String,
required:true
}
})
</script>
<style lang="css" scoped>
</style>
- 以哈希路由为例,'#'加上父组件传来的路径,实现路由的切换。更详细的hash路由的手写:HashRouter的实现原理,带你一起手写。 - 掘金 (juejin.cn)
- 这样在使用了router-link的父组件中,你可以像Vue中的router一样的方法使用它。
ini
<router-link to="/">首页</router-link>
<router-link to="/about">About</router-link>
2、定义Router并单例输出
javascript
import RouterLink from './RouterLink.vue'
import RouterView from './RouterView.vue'
import {ref,inject} from 'vue'
// 单例的责任
export const createRouter = (options) => {
return new Router(options)
}
export const createWebHashHistory = () =>{
//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{
constructor(options){
this.history = options.history
this.routes = options.routes
this.current = ref(this.history.url)
this.history.bindEvents(() => {
this.current.value = window.location.hash.slice(1)
})
}
// use方法会调用 插件的install方法
install(app){
//全局声明有一个router 全局使用的对象
app.provide(ROUTER_KEY,this)
app.component('router-link',RouterLink)
app.component('router-view',RouterView)
}
}
- 输出单例路由示例,接收配置对象作为参数,例如Vue中router的routes和history
- 定义监听hashchange事件的函数,将当前路径和这个函数返回,接着Router类中跟踪和响应URL hash的变化。
- 将ROUTER_KEY常量作为Vue中provide和inject API中的标识符,用于在组件树中注入和获取路由实例,用于祖先-后代关系通信的特性。
- 使用inject方法,从组件树中获取路由实例,让任何组件都可以通过调用这个方法来访问路由对象,从而实现对路由状态的查询和修改。
- Router类,构造函数接收option对象(配置信息routes和history),将当前路径设置为响应式,存储当前的路由状态,初始值为createWebHashHistory中返回的url。并且在路径发生改变时,自动响应更新对应的值,这是响应式在路由中的高级用法。
- install(app),将当前Router注入到Vue应用(app)中,让整个组件树中的后代组件都能通过inject方法来访问这个路由实例。对RouterLink、RouterView组件的全局注册。
3、router-view的实现
xml
<template>
<component :is="component">
</component>
</template>
<script setup>
import { useRouter } from './index'
import { computed } from 'vue';
const router = useRouter()
console.log(router);
//router-view 动态组件展示,依赖于 url 的变化
//响应式 router.current 设置为ref
const component = computed(() => {
const route = router.routes.find((route)=>route.path == router.current.value)
return route ? route.component:null
})
</script>
<style lang="css" scoped>
</style>
- 使用计算属性computed,在当前路由变化时,计算component的值,并显示配置信息中的对应组件,
route ? route.component:null
并且做了防止用户输入错误路由。
使用Router
javascript
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
将from 'vue' 改为我们自己手写的,我们来看一下实现效果。在这Home和About中只写了相应的名称来代表页面,模拟实现效果。