by yugasun from https://yugasun.com/post/you-may-not-know-vuejs-11.html
本文可全文轉(zhuǎn)載呕缭,但需要保留原作者和出處讲坎。
對于單頁面應(yīng)用,前端路由是必不可少的曹锨,官方也提供了 vue-router 庫 供我們方便的實現(xiàn)扛吞,但是如果你的應(yīng)用非常簡單辜王,就沒有必要引入整個路由庫了,可以通過 Vuejs 動態(tài)渲染的API來實現(xiàn)于样。
我們知道組件可以通過 template
來指定模板抡爹,對于單文件組件掩驱,可以通過 template
標簽指定模板,除此之外冬竟,Vue 還提供了我們一種自定義渲染組件的方式欧穴,那就是 渲染函數(shù) render,具體 render
的使用泵殴,請閱讀官方文檔涮帘。
接下來我們開始實現(xiàn)我們的前端路由了。
簡易實現(xiàn)
我們先運行 vue init webpack vue-router-demo
命令來初始化我們的項目(注意初始化的時候笑诅,不要選擇使用 vue-router)焚辅。
首先映屋,在 src
目錄先創(chuàng)建 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
早处,并修改其代碼湾蔓,使用上面創(chuàng)建的頁面模板包裹:
<template>
<layout>
<!-- 原模板內(nèi)容 -->
</layout>
</template>
<script>
import Layout from '@/layout';
export default {
name: 'HelloWorld',
components: {
Layout,
},
// ...
};
</script>
<!-- ... -->
當然還需要添加一個 404頁面
,用來充當當用戶輸入不存在的路由時的界面砌梆。
最后就是我們最重要的步驟了默责,改寫 main.js
,根據(jù)頁面 url
動態(tài)切換渲染組件咸包。
1.定義路由映射:
// url -> Vue Component
const routes = {
'/': 'Home',
'/hello': 'HelloWorld',
};
2.添加 VueComponent
計算屬性桃序,根據(jù) 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.實現(xiàn)渲染邏輯烂瘫,render 函數(shù)提供了一個參數(shù) createElement
媒熊,它是一個生成 VNode 的函數(shù),可以直接將動態(tài)引入組件傳參給它坟比,執(zhí)行渲染芦鳍。
const app = new Vue({
// ...
render(h) {
// 因為組件是以 es module 的方式引入的,
// 此處必須使用 this.ViewComponent.default 屬性作為參數(shù)
return h(this.ViewComponent.default);
}
});
history 模式
簡易版本其實并沒有實現(xiàn)前端路由葛账,點擊頁面切換會重新全局刷新柠衅,然后根據(jù) window.location.pathname
來初始化渲染相應(yīng)組件而已。
接下來我們來實現(xiàn)前端路由的 history
模式籍琳。要實現(xiàn)頁面 URL 改變菲宴,但是頁面不刷新,我們就需要用到 history.pushState() 方法趋急,通過此方法喝峦,我們可以動態(tài)的修改頁面 URL,且頁面不會刷新宣谈。該方法有三個參數(shù):一個狀態(tài)對象愈犹,一個標題(現(xiàn)在已被忽略),以及可選的 URL 地址闻丑,執(zhí)行后會觸發(fā) popstate
事件漩怎。
那么我們就不能在像上面一樣直接通過標簽 a
來直接切換頁面了,需要在點擊 a
標簽是嗦嗡,禁用默認事件勋锤,并執(zhí)行 history.pushState()
修改頁面 URL
,并更新修改 app.currentRoute
侥祭,來改變我們想要的 VueComponent
屬性叁执,好了原理就是這樣茄厘,我們來實現(xiàn)一下。
首先谈宛,編寫通用 router-link
組件次哈,實現(xiàn)上面說的的 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) {
// 阻止默認跳轉(zhuǎn)事件
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 屬性作為參數(shù)
return h(this.ViewComponent.default);
},
});
好了,我們的 history
模式的路由已經(jīng)修改好了撬槽,點擊頭部的鏈接此改,頁面內(nèi)容改變了,并且頁面沒有刷新侄柔。
但是有個問題共啃,就是當我們點擊瀏覽器 前進/后退
按鈕時,頁面 URL 變化了勋拟,但是頁面內(nèi)容并沒有變化勋磕,這是怎么回事呢?
因為當我們點擊瀏覽器 前進/后退
按鈕時敢靡,app.currentRoute
并沒有發(fā)生改變挂滓,但是它會觸發(fā) popstate 事件,所以我們只要監(jiān)聽 popstate
事件啸胧,然后修改 app.currentRoute
就可以了赶站。
既然需要監(jiān)聽,我們就直接添加代碼吧纺念,在 src/main.js
文件末尾添加如下代碼:
window.addEventListener('popstate', () => {
app.currentRoute = window.location.pathname;
});
這樣我們現(xiàn)在無論是點擊頁面中鏈接切換贝椿,還是點擊瀏覽器 前進/后退
按鈕,我們的頁面都可以根據(jù)路由切換了陷谱。
hash 模式
既然實現(xiàn) history 模式
烙博,怎么又能少得了 hash 模式
呢?既然你這么問了烟逊,那我還是不辭勞苦的帶著大家實現(xiàn)一遍吧(賣個萌~)渣窜。
什么是 URL hash 呢?來看看 MDN 解釋:
Is a DOMString containing a '#' followed by the fragment identifier of the URL.
也就是說它是頁面 URL中 以 #
開頭的一個字符串標識宪躯。而且當它發(fā)生變化時乔宿,會觸發(fā) hashchange
事件。那么我們可以跟 history 模式
一樣對其進行監(jiān)聽就行了访雪,對于 history 模式
详瑞,
這里需要做的修改無非是 src/routes.js
的路由映射如下:
export default {
'#/': 'Home',
'#/hello': 'HelloWorld',
};
給 src/layout/index.vue
中的鏈接都添加 #
前綴掂林,然后在 src/main.js
中監(jiān)聽 hashchange
事件,當然還需要將 window.location.hash
賦值給 app.currentRoute
:
window.addEventListener('hashchange', () => {
app.currentRoute = window.location.hash;
});
最后還有個問題坝橡,就是單個面初始化的時候泻帮,window.location.hash
值為空,這樣就會找不到路由映射驳庭。所以當頁面初始化的時候刑顺,需要添加判斷,如果 window.location.hash
為空饲常,則默認修改為 #/
,這樣就全部完成了狼讨。
不同模式切換版本
實際開發(fā)中贝淤,我們會根據(jù)不同項目需求,使用不同的路由方式政供,這里就需要我們添加一個 mode
參數(shù)播聪,來實現(xiàn)路由方式切換,這里就不做講解了布隔,感興趣的讀者离陶,可以自己嘗試實現(xiàn)下。
總結(jié)
實際上衅檀,一個完整的路由庫招刨,遠遠不止我們上面演示的代碼那么簡單,還需要考慮很多問題哀军,但是如果你的項目非常簡單沉眶,不需要很復(fù)雜的路由機制,自己實現(xiàn)一遍還是可以的杉适,畢竟 vue-router.min.js
引入進來代碼體積就會增加 26kb
谎倔,具體如何取舍,還是視需求而定猿推。
盡信書片习,不如無書,當面對
問題/需求
時蹬叭,多點自主的思考和實踐藕咏,比直接接受使用要有用的多。