前言
单页面的兴起离不开前端路由
,记得在小白时期刚接触SPA单页面应用
这种概念时,我一度怀疑这种技术靠不靠谱,心中充满着很多不解,比如:把所有东西都写在一个页面上难道没有性能问题?如果某个地方报错了那页面是不是就崩了?
后来随着做了一个又一个SPA
项目,逐渐打消了这种顾虑。下面我们就来研究下单页面的灵魂,路由
是怎么个实现逻辑吧。
url中#(hash)的含义
拿 vue-router举例,vue-rouer
有两种工作模式分别是hash
和history
,我们先来了解下#
hash
锚点
看到#
最容易联想到的就是 锚点 了 , 就像看 掘金 文章一样,可以利用锚点实现点击跳转
这应该是#
(hash)最本质的用法了
改变#
不触发网页重载
#
还有一个重要特点就是改变#
后面的内容后并不会导致页面刷新
也就是这个特性使得使用#
实现前端路由成为可能,我们在这里先进行大胆的假设:hash实现原理就是监听#
后面内容的变化然后动态渲染出对应的组件
History Api
从hash
来看,我们可以浅浅的得出一个结论,要想实现前端路由,必须要满足 页面URL
变化时,页面不能刷新 这一条件
我们在History Api
中发现 pushState 似乎也满足这一条件
下面我们使用代码验证,代码也十分简单,设置一个定时器,一段时间后通过此api改变页面url,看页面是否刷新
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
setTimeout(() => {
history.pushState(null, "", "/a"); // 核心代码
}, 3000);
</script>
</body>
</html>
注意看下面的url,会从127.0.0.1/aa.html
变成127.0.0.1/a
,且页面并未刷新。
这也为用来实现前端路由创造了可能。下面我们以vue-router
为例,来学习下SPA的前端路由
到底是如何实现的。
vue-router工作流程
vue插件
我们在使用vue-router
时需要先use
一下,可见vue-router
本质上来说是 vue插件
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
Vue.use(VueRouter); // 插件使用
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"),
},
];
const router = new VueRouter({
routes,
});
export default router;
既然是插件,我们就先来弄清楚 vue
插件的运行机制,先来弄清楚一下几个问题。
-
vue.use
都干了什么事?我们可以在
vue2.0
源码中找到答案export function initUse(Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { const installedPlugins = this._installedPlugins || (this._installedPlugins = []); if (installedPlugins.indexOf(plugin) > -1) { return this; } const args = toArray(arguments, 1); args.unshift(this); // 第一个参数设置为vue实例 // 核心代码 if (typeof plugin.install === "function") { plugin.install.apply(plugin, args); } else if (typeof plugin === "function") { plugin.apply(null, args); } installedPlugins.push(plugin); return this; }; }
vue.use()
方法做的最主要的事就是调用插件的install
方法,然后把vue
实例传给插件,供插件使用。所以我们在开发插件时必须要暴露出一个install
方法,供vue
调用 -
为什么
vue-rouer
需要在main.js
中挂载,而有的插件不需要我们在使用
vue-router
通常是这样//router/index.js import Vue from "vue"; import VueRouter from "vue-router"; Vue.use(VueRouter); // 用过use方法调用vue-router const routes = []; const router = new VueRouter({ routes, }); export default router;
然后在
main.js
中挂载import Vue from "vue"; import App from "./App.vue"; import router from "./router"; Vue.config.productionTip = false; new Vue({ router // 挂载 }).$mount("#app");
在初次使用vue-router
时,就有一种疑惑,老子都通过use
方法调用了,为什么在这里还要挂载?这显然多此一举
但我在vue
官网看到关于vue-router
插件介绍时,我感觉这件事并没有这么简单
再来看看 vue-router 源码时,发现 通过全局混入来添加一些组件选项 的意思就是通过混入的方式拿到vue-rouer
的配置项
// vue-router src/install.js
Vue.mixin({
beforeCreate () {
//判断是不是根组件
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current) // 设置成响应式数据
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed () {
registerInstance(this)
}
})
那问题来了, 为什么用混入的方式去拿vue-router
配置呢?其实也不难理解,我们在使用vue.use(vueRouter)
的时候,这个时候vue还没有new
出来,也就是说vue.use(vueRouter)
要比new vue()
先执行... 文字描述太难了,还是画图吧
以上逻辑都顺理成章,且从源码中得知vue-router也是这样做的,但是有时候我还是认为在new vue中挂载router 是多余的,我们明明也可以通过参数传过去,例如:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import VueRouter from "vue-router";
Vue.use(VurRouter,router) // 在main.js中调用use,把router当做第二个参数传过去
那在vue-router中也可以拿到vue-rotuer
配置项,如下所示:
class VueRouter{}
VueRouter.install=(vue,option)=>{}
那vue-rotuer
为什么没有这样做呢?欢迎大家在留言讨论
vue-router
运行机制
vue-router
核心功能就是当URL
改变时自动渲染对应组件
一图胜过千言万语,我们来总结下vue-rouer
主工作流程图
这里可能会牵扯到一些细节,比如:
<router-view>
及<router-link>
如何实现;- 组件是如何被渲染的;
- 嵌套路由如何渲染
<router-view>
及<router-link>
其实就是在vue-rotuer
注册的Vue
全局组件,其中<router-link>
比较简单,其实就是一个a
便签,下面我们着重研究下<router-view>
及嵌套路由如何渲染
需要注意的是组件渲染是 重外到内,先渲染父组件,如果发现父组件中存在<router-view>
在去渲染对应的子组件,直到所有组件渲染完成。
渲染是通过render
函数的h
方法进行渲染,以下是vue-rouer
关于router-view
部分核心代码
简易版vue-rotuer
实现
vue-rotuer
源码虽然不长,但想要完全读懂也并不简单,我把核心代码抽离出来,实现了简易版的vue-router,效果如下所示:
主要实现功能有:
- 实现根据路由渲染对应组件,并实现嵌套渲染
- 实现
this.$rouer.push()
方法,其他方法可自由扩展
具体细节实现如下:
router-link
实现
function isActive(location) {
return window.location.hash.slice(1) === location;
}
export default {
functional: true,
render(h, { props, slots }) {
const active = isActive(props.to) ? "my-vue-router-active" : "";
return h(
"a",
{
attrs: {
href: `#${props.to}`,
class: [`${active}`],
},
},
slots().default[0].text
);
},
};
router-view
实现
export default {
functional: true,
render(h, { parent, data }) {
let route = parent.$route;
let matched = route.matched;
data.routerView = true;
let deep = 0;
while (parent) {
if (parent.$vnode && parent.$vnode.data.routerView) {
deep++;
}
parent = parent.$parent;
}
let record = matched[deep];
if (!record) {
return h();
}
let component = record.component;
return h(component, data);
},
};
源码地址: simple-vue-router
个人项目:基于webpack自动生成路由打包多页面应用 lyh-pages,欢迎大家 star 哦,万分感谢!
最后
如有帮助,欢迎点赞关注哦!😘