單頁面的興起離不開前端路由
,記得在小白時期剛接觸SPA單頁面應用
這種概念時,我一度懷疑這種技術靠不靠譜,心中充滿着不少不解,好比:把全部東西都寫在一個頁面上難道沒有性能問題?若是某個地方報錯了那頁面是否是就崩了?javascript
後來隨着作了一個又一個SPA
項目,逐漸打消了這種顧慮。下面咱們就來研究下單頁面的靈魂,路由
是怎麼個實現邏輯吧。html
拿 vue-router舉例,vue-rouer
有兩種工做模式分別是hash
和history
,咱們先來了解下#
hash前端
看到#
最容易聯想到的就是 錨點 了 , 就像看 掘金 文章同樣,能夠利用錨點實現點擊跳轉vue
這應該是#
(hash)最本質的用法了java
#
不觸發網頁重載#
還有一個重要特色就是改變#
後面的內容後並不會致使頁面刷新node
也就是這個特性使得使用#
實現前端路由成爲可能,咱們在這裏先進行大膽的假設:hash實現原理就是監聽#
後面內容的變化而後動態渲染出對應的組件webpack
從hash
來看,咱們能夠淺淺的得出一個結論,要想實現前端路由,必需要知足 頁面URL
變化時,頁面不能刷新 這一條件git
咱們在History Api
中發現 pushState 彷佛也知足這一條件github
下面咱們使用代碼驗證,代碼也十分簡單,設置一個定時器,一段時間後經過此api改變頁面url,看頁面是否刷新web
<!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
時須要先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 哦,萬分感謝!
若有幫助,歡迎點贊關注哦!😘