前言
使用vue时总会使用到路由,路由的跳转..等等,对于对代码充满好奇的我们,自然不会放过每一个手撕源码的机会,因此今天我们一起来看看router-link、router-view的底层到底是怎么实现的
vue-router中的router-link的使用
首先我们来玩一下vue封装好的vue-router
- npm i vue-router 安装vue-router
- 在router文件夹下创建index.js
index.js
js
import { createRouter, createWebHashHistory } from 'vue-router';
const routes = [
{
path: '/',
name: 'home',
component: () => import('../views/Home.vue')
},
{
path: '/about',
name: 'about',
component: () => import('../views/About.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router;
这里我们就创建
了一个router,抛出
之后,在全局引入(main.js),并使用use。
注意: 这里我们使用的是带Hash的createWebHashHistory,它与不带Hash的路由创建模式存在一些区别:
URL 形式 :使用 createWebHashHistory
创建的路由模式,其 URL 中会带有 #
号,例如 /#/home
、/#/about
等;而 createWebHistory
创建的路由模式,其 URL 更加直观,不会在 URL 中添加 #
号,例如 /home
、/about
等。
服务器配置要求 :createWebHashHistory
不需要在服务器端进行特殊配置,因为 #
后面的部分不会被发送到服务器。而使用 createWebHistory
时,由于直接使用了 HTML5 中的 history.pushState
和 history.replaceState
方法来实现路由跳转,如果直接在浏览器中刷新或直接访问某个路由,服务器将无法识别该路由,并返回 404 错误。因此,需要在服务器端进行配置,将所有的路由请求都返回首页,再由前端代码进行路由的匹配和处理。
浏览器兼容性 :createWebHashHistory
支持所有浏览器,包括老版本的浏览器。而 createWebHistory
只支持 HTML5 标准浏览器。
SEO 优化 :createWebHashHistory
因为 URL 中带有 #
号,在搜索引擎的 SEO 优化方面存在一些问题。而 createWebHistory
的 URL 形式更有利于 SEO。
开发环境设置 :在开发环境下,使用 createWebHistory
需要将 webpack 的 historyApiFallback
属性设置为 true
,以便在开发环境下正常使用路由。
main.js
js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
.mount('#app')
引入之后我们就可以在App.js中加入路由入口 <router-view />
,启动项目,这样一个基本的路由就建立起来了。
App.vue
js
<template>
<router-link to="/">首页</router-link>
<router-link to="/about">about</router-link>
<router-view />
</template>
<script setup>
</script>
<style lang="scss" scoped></style>
- router-link点击可以跳转。
- 此时启动项目,就能直接访问到/路径下的Home.vue了,更改路径为/about就能访问到About.vue。
走到这一步,我们就自然而然会思考:这个routerlink是如何进入到组件树,它的底层又是如何担起跳转页面的功能的?
组件都是开发出来的,这个routerlink也一样,它来自于我们安装的vue-router,底层源码到底做了什么事情?
手撕router-link
- routerlink能实现页面跳转的功能,那么必然他的底层一定存在a标签
- 标签中的to,具体到哪个路径需要做绑定,用到defineProps,并且这是一个必填required属性,就像href一样。
- 我们要使用自己写的routerlink,那必然在main.js中不要再use已经写好的router
- 我们学习html时标签中间的文字部分(显示不同的超链接,如:about超链接,home超链接),每一个超链接标签中间的文字都是不一样的,此时我们把自己写好的RouterLink组件引入到App.js中使用时,如何把组件中间写的文字搬运到a标签中?---->
<slot></slot>插槽占位符
提升组件的定制性,可读性
更好
RouterLink.vue
js
<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="scss" scoped></style>
App.vue
js
<template>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/about">about</RouterLink>
</template>
<script setup>
import RouterLink from './router/grouter/RouterLink.vue'
</script>
<style lang="scss" scoped></style>
但是问题来了:我们使用vue-router中的routerlink组件的时候,想让其生效,其实就是在main.js中用app给use了一下我们在路由中创建的router,它压根就不需要像上方我们的做法一样(引入这个组件import RouterLink ......),那么这个use方法到底做了什么事情呢?
- vue-router中的routerlink组件不需要声明就能使用,这是:全局组件,想让一个组件成为
全局组件:app.component('组件名',类在哪里)
,如routerlink组件声明:app.component('router-link',RouterLink) - main.js中我们原来是在router文件夹中的index.js中引入vue-router 然后去createrouter..createWebHashHistory,然后定义一个routes,通过createrouter方法创造一个router最终抛出这个router,现在我们手撕一下这个index.js
index.js
js
import { createRouter } from './grouter/index'
const router = createRouter()
export default router
我们还封装了一层index.js在grouter文件夹下:
index.js
js
// VueRouter封装在这里
import RouterLink from "./RouterLink.vue"
const createRouter = () => {
return new Router()
}
class Router {
constructor() {
}
install(app) {
// console.log(app);
// console.log('vue-router install');
// 完成全局组件的声明
app.component('RouterLink', RouterLink);
}
}
export {
createRouter
}
那么至此,在App.vue中我们就再也不需要引入RouterLink组件才能去使用RouterLink了
App.vue
js
<template>
<RouterLink to="/">首页</RouterLink>
<RouterLink to="/about">about</RouterLink>
</template>
<script setup>
// import RouterLink from './router/grouter/RouterLink.vue'
</script>
<style lang="scss" scoped></style>
效果:
到这里,可以看到,vue分为vue的本体以及vue的生态,不得不说尤雨溪是真的厉害。在做vue框架时做好边界(mvvm,组件,VDOM...)的同时还有自己的生态(install)app只需要use一下拿到这个实例,把app传过去就能调用install方法,
手撕router-view
既然已经写到这里了,不如把router-view也给写了
- 还是一样,在刚才封装的index.js中再将RouterView也给声明成全局组件,虽然这个组件还没写
- 动态组件
<component :is="component"> </component>
这个component是响应式的我们需要拿到url才知道需要去哪里,这里就需要对index.js中写的路由routes数组里的path与当前url做对比,比对成功就跳转
RouterView.vue
js
<template>
<component :is="component"> </component>
</template>
<script setup>
import { computed, ref } from 'vue'
let router = useRouter()
const component = computed(() => {
const route = router.routes.find(
(route) => route.path === router.url
)
return route ? route.component : null
})
</script>
<style lang="scss" scoped></style>
grouter文件夹下的index.js
js
// VueRouter封装在这里
import { ref } from "vue";
import RouterLink from "./RouterLink.vue"
import RouterView from "./RouterView.vue"
const createRouter = (options) => {
return new Router(options)
}
const createWebHashHistory = () => {
return {
url: window.location.hash.slice(1) || '/'
}
}
class Router {
constructor(options) {
console.log(options, '/////////');
this.history = options.history
this.routes = options.routes;
// 当前的url 状态 它是router-view computed计算属性的依赖
this.current = ref(this.history.url)
}
install(app) {
// console.log(app);
// console.log('vue-router install');
// 完成全局组件的声明
app.component('RouterLink', RouterLink);
app.component('RouterView', RouterView);
}
}
export {
createRouter,
createWebHashHistory
}
- url: window.location.hash.slice(1) || '/' 由于是带Hash的,需要将井号去除
- 接下来一起来看看option打印出来的效果吧,createRouter接收两个值,一个history,一个routes,现在我们已经把history实现了,看看他是否真的接收到了我们的url。
至此,routerlink介绍完毕,routerview未完待续...