by yugasun from yugasun.com/post/you-ma… 本文可全文轉載,但需要保留原作者和出處。
對於單頁面應用,前端路由是必不可少的,官方也提供了 vue-router 庫 供我們方便的實現,但是如果你的應用非常簡單,就沒有必要引入整個路由庫了,可以通過 Vuejs 動態渲染的API來實現。
我們知道元件可以通過 template
來指定模板,對於單檔案元件,可以通過 template
標籤指定模板,除此之外,Vue 還提供了我們一種自定義渲染元件的方式,那就是 渲染函式 render,具體 render
的使用,請閱讀官方文件。
接下來我們開始實現我們的前端路由了。
簡易實現
我們先執行 vue init webpack vue-router-demo
命令來初始化我們的專案(注意初始化的時候,不要選擇使用 vue-router)。
首先,在 src
目錄先建立 layout/index.vue
檔案,用來作為頁面的模板,程式碼如下:
<template>
<div class="container">
<ul>
<li><a :class="{active: $root.currentRoute === '/'}" href="/">Home</a></li>
<li><a :class="{active: $root.currentRoute === '/hello'}" href="/hello">HelloWord</a></li>
</ul>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'Layout',
};
</script>
<style scoped>
.container {
max-width: 600px;
margin: 0 auto;
padding: 15px 30px;
background: #f9f7f5;
}
a.active {
color: #42b983;
}
</style>
複製程式碼
然後,將 components/HelloWorld.vue
移動到 src/pages
,並修改其程式碼,使用上面建立的頁面模板包裹:
<template>
<layout>
<!-- 原模板內容 -->
</layout>
</template>
<script>
import Layout from '@/layout';
export default {
name: 'HelloWorld',
components: {
Layout,
},
// ...
};
</script>
<!-- ... -->
複製程式碼
當然還需要新增一個 404頁面
,用來充當當使用者輸入不存在的路由時的介面。
最後就是我們最重要的步驟了,改寫 main.js
,根據頁面 url
動態切換渲染元件。
1.定義路由對映:
// url -> Vue Component
const routes = {
'/': 'Home',
'/hello': 'HelloWorld',
};
複製程式碼
2.新增 VueComponent
計算屬性,根據 window.location.pathname
來引入所需要元件。
const app = new Vue({
el: '#app',
data() {
return {
// 當前路由
currentRoute: window.location.pathname,
};
},
computed: {
ViewComponent() {
const currentView = routes[this.currentRoute];
/* eslint-disable */
return (
currentView
? require('./pages/' + currentView + '.vue')
: require('./pages/404.vue')
);
},
},
});
複製程式碼
3.實現渲染邏輯,render 函式提供了一個引數 createElement
,它是一個生成 VNode 的函式,可以直接將動態引入元件傳參給它,執行渲染。
const app = new Vue({
// ...
render(h) {
// 因為元件是以 es module 的方式引入的,
// 此處必須使用 this.ViewComponent.default 屬性作為引數
return h(this.ViewComponent.default);
}
});
複製程式碼
history 模式
簡易版本其實並沒有實現前端路由,點選頁面切換會重新全域性重新整理,然後根據 window.location.pathname
來初始化渲染相應元件而已。
接下來我們來實現前端路由的 history
模式。要實現頁面 URL 改變,但是頁面不重新整理,我們就需要用到 history.pushState() 方法,通過此方法,我們可以動態的修改頁面 URL,且頁面不會重新整理。該方法有三個引數:一個狀態物件,一個標題(現在已被忽略),以及可選的 URL 地址,執行後會觸發 popstate
事件。
那麼我們就不能在像上面一樣直接通過標籤 a
來直接切換頁面了,需要在點選 a
標籤是,禁用預設事件,並執行 history.pushState()
修改頁面 URL
,並更新修改 app.currentRoute
,來改變我們想要的 VueComponent
屬性,好了原理就是這樣,我們來實現一下。
首先,編寫通用 router-link
元件,實現上面說的的 a
標籤點選邏輯,新增 components/router-link.vue
,程式碼如下:
<template>
<a
:href="href"
:class="{active: isActive}"
@click="go"
>
<slot></slot>
</a>
</template>
<script>
import routes from '@/routes';
export default {
name: 'router-link',
props: {
href: {
type: String,
required: true,
},
},
computed: {
isActive() {
return this.href === this.$root.currentRoute;
},
},
methods: {
go(e) {
// 阻止預設跳轉事件
e.preventDefault();
// 修改父級當前路由值
this.$root.currentRoute = this.href;
window.history.pushState(
null,
routes[this.href],
this.href,
);
},
},
};
</script>
複製程式碼
對於 src/main.js
檔案,其實不需要做什麼修改,只需要將 routes
物件修改為模組引入即可。如下:
import Vue from 'vue';
// 這裡將 routes 物件修改為模組引入方式
import routes from './routes';
Vue.config.productionTip = false;
/* eslint-disable no-new */
const app = new Vue({
el: '#app',
data() {
return {
currentRoute: window.location.pathname,
};
},
computed: {
ViewComponent() {
const currentView = routes[this.currentRoute];
/* eslint-disable */
return (
currentView
? require('./pages/' + currentView + '.vue')
: require('./pages/404.vue')
);
},
},
render(h) {
// 因為元件是以 es module 的方式引入的,
// 此處必須使用 this.ViewComponent.default 屬性作為引數
return h(this.ViewComponent.default);
},
});
複製程式碼
好了,我們的 history
模式的路由已經修改好了,點選頭部的連結,頁面內容改變了,並且頁面沒有重新整理。
但是有個問題,就是當我們點選瀏覽器 前進/後退
按鈕時,頁面 URL 變化了,但是頁面內容並沒有變化,這是怎麼回事呢?
因為當我們點選瀏覽器 前進/後退
按鈕時,app.currentRoute
並沒有發生改變,但是它會觸發 popstate 事件,所以我們只要監聽 popstate
事件,然後修改 app.currentRoute
就可以了。
既然需要監聽,我們就直接新增程式碼吧,在 src/main.js
檔案末尾新增如下程式碼:
window.addEventListener('popstate', () => {
app.currentRoute = window.location.pathname;
});
複製程式碼
這樣我們現在無論是點選頁面中連結切換,還是點選瀏覽器 前進/後退
按鈕,我們的頁面都可以根據路由切換了。
hash 模式
既然實現 history 模式
,怎麼又能少得了 hash 模式
呢?既然你這麼問了,那我還是不辭勞苦的帶著大家實現一遍吧(賣個萌~)。
什麼是 URL hash 呢?來看看 MDN 解釋:
Is a DOMString containing a '#' followed by the fragment identifier of the URL.
也就是說它是頁面 URL中 以 #
開頭的一個字串標識。而且當它發生變化時,會觸發 hashchange
事件。那麼我們可以跟 history 模式
一樣對其進行監聽就行了,對於 history 模式
,
這裡需要做的修改無非是 src/routes.js
的路由對映如下:
export default {
'#/': 'Home',
'#/hello': 'HelloWorld',
};
複製程式碼
給 src/layout/index.vue
中的連結都新增 #
字首,然後在 src/main.js
中監聽 hashchange
事件,當然還需要將 window.location.hash
賦值給 app.currentRoute
:
window.addEventListener('hashchange', () => {
app.currentRoute = window.location.hash;
});
複製程式碼
最後還有個問題,就是單個面初始化的時候,window.location.hash
值為空,這樣就會找不到路由對映。所以當頁面初始化的時候,需要新增判斷,如果 window.location.hash
為空,則預設修改為 #/
,這樣就全部完成了。
不同模式切換版本
實際開發中,我們會根據不同專案需求,使用不同的路由方式,這裡就需要我們新增一個 mode
引數,來實現路由方式切換,這裡就不做講解了,感興趣的讀者,可以自己嘗試實現下。
總結
實際上,一個完整的路由庫,遠遠不止我們上面演示的程式碼那麼簡單,還需要考慮很多問題,但是如果你的專案非常簡單,不需要很複雜的路由機制,自己實現一遍還是可以的,畢竟 vue-router.min.js
引入進來程式碼體積就會增加 26kb
,具體如何取捨,還是視需求而定。
盡信書,不如無書,當面對
問題/需求
時,多點自主的思考和實踐,比直接接受使用要有用的多。