PS:轉載請注明出處
作者: TigerChain
地址http://www.lxweimin.com/p/9a7d79249741
本文出自 TigerChain 簡書 手把手教 Vue 系列
教程簡介
- 1、閱讀對象
本篇教程適合新手閱讀,老手直接略過 - 2、教程難度
初級,本人水平有限,文章內容難免會出現問題,如果有問題歡迎指出,謝謝 - 3、Demo 地址:https://github.com/tigerchain/vue-lesson 請看 06、Vue路由 這一節
- 4、演示地址
正文
一、什么是路由
以前在 React 的文章中說過路由這個東西,這里再說一下「再次加深一下記憶」。路由是什么我們可能不太理解,但是我說一個東西我們一定知道,就是"路由器",路由器的功能用一句話概括就是:數據從一個網絡到另一個網絡就是靠路由器來完成的[當然路由器的功能不僅僅于此]。
我們說的程序開發中的路由不是指路由器和網絡協議中的路由,但是基本思想是一樣的。而路由又分為前端路由和后端路由。
我們來看一個路由的簡易圖吧,有了這個圖,大家對路由就有一個大致的了解了。
1、后端路由
舉個栗子,分配一個站點,服務器地址是:http://192.168.1.200:8899
,在這個網站中提供了三個界面
http://192.168.1.200:8899/index.html 主頁
http://192.168.1.200:8899/about/aboutus.html 關于我們頁面
http://192.168.1.200:8899/feedback.html 反饋界面
當我們在瀏覽器輸入 http://192.168.1.200:8899/index.html
來仿問界面的時候,web 服務器就會接收到這個請求,然后把 index.html 解析出來,并找到相應的 index.html 并展示出來,這就是路由的分發,路由的分發是通過路由功能來完成的
2、前端路由
雖然前端路由和后端路由的實現方式不一樣,但是原理都有是相同的,在 H5 的 history Api 出來之前,前端路由的功能都是通過 hash 「散列值」 來實現的,hash 能兼容低版本的瀏覽器
PS:后端路由每次仿問一個頁面都要向瀏覽器發送請求,然后服務端再響應解析,在這個過程中肯定會存在延遲,但是前端路由中仿問一個新的界面的時候只是瀏覽器的路徑改變了,沒有和服務端交互「所以不存在延遲」,這個對用戶體驗來說是大大的提高。如下所示
http://192.168.1.200:8080/#/index.html
http://192.168.1.200:8080/#/about/aboutus.html
http://192.168.1.200:8080/#/feedback.html
由于 web 服務器不會解析 # 后面的東西「所以通過 hash 能提高性能」,但是客戶端的 js 可以拿到 # 后面的東西,有一個方法是 window.location.hash 來讀取,使用這個方法來匹配到不同的方法上「配合前端的一些邏輯操作就完成路由功能,剩下只是關心接口調用」
3、舉個栗子
假設有一個地址
http://www.xxx.com/path/a/b/c.html?key1=Tiger && key2=Chain && key3=fuck#/path/d/e.html
- 1、我們把這個地址分析一下「這個地址基本上包含了一個復雜地址的所有情況」
http:協議
www.xxx.com:域名
/path/a/b/c.html:路由,即服務器上的資源
?key1=Tiger && key2=Chain && key3=fuck:這個很好理解 Get 請求的參數
#/path/d/e.html:hash 也叫散列值,也叫錨點
上面的 hash 是和瀏覽器交互的,其它的都是和服務器進行交互
通過上述我們知道,前端路由的實現方式有兩種:
(1)、一是改變 hash 值,監聽 hashchange 事件,可以兼容低版本瀏覽器
(2)、二是通過 H5 的 history API 來監聽 popState 事件,使用 pushState 和 replaceState 實現
- 2、hash 改變,不會導致瀏覽器刷新「請求服務器」,我們來寫個 demo 驗證一下
先看一下效果圖
從圖中我們可以看到,使用 hash 并不會導致瀏覽器刷新,并且我們 js 拿到了 hash 值并且打印出來了
- 3、源碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>hash 實現前端路由</title>
<style>
#nav {
margin: 0;
border:0;
height: 40px;
border-top: #060 2px solid;
margin-top: 10px;
border-bottom: #060 2px solid;
background-color: red;
}
#nav ul {
margin: 0;
border: 0;
list-style: none;
line-height: 40px;
}
#nav li {
display: block;
float: left;
}
#nav a {
display: block;
color: #fff;
text-decoration: none;
padding: 0 20px;
}
#nav a:hover {
background-color: orange;
}
</style>
</head>
<body>
<h3>使用 hash 實現前端路由</h3>
<hr/>
<a href="#hash1">#hash1</a>
<a href="#hash2">#hash2</a>
<a href="#hash3">#hash3</a>
<a href="#hash4">#hash4</a>
<p/>
<div id = "show-hash-result" style="color:blue">
點擊上面鏈接,并觀察瀏覽器
</div>
<h4>定義一個簡單的 tab 路由頁面</h4>
<div id="nav">
<ul>
<li><a href="#/index.html">首頁</a></li>
<li><a href="#/server">服務</a></li>
<li><a href="#/mine">我的</a></li>
</ul>
</div>
<div id="result"></div>
<script type="text/javascript">
window.addEventListener("hashchange", function(){
//變化后輸出當前地址欄中的值
document.getElementById("show-hash-result").innerHTML = "當前的 hash 值是: "+location.hash;
//打印出當前 hash 值
console.log("當前的 hash 值是:"+window.location.hash) ;
});
</script>
<!-- 定義 router 的 js 代碼塊 -->
<script type="text/javascript">
//自定義一個路由規則
function CustomRouter(){
this.routes = {};
this.curUrl = '';
this.route = function(path, callback){
this.routes[path] = callback || function(){};
};
this.refresh = function(){
if(location.hash.length !=0){ // 如果 hash 存在
this.curUrl = location.hash.slice(1) || '/';
if(this.curUrl.indexOf('/')!=-1){ //這里粗略的把 hash 過濾掉
this.routes[this.curUrl]();
}
}
};
this.init = function(){
window.addEventListener('load', this.refresh.bind(this), false);
window.addEventListener('hashchange', this.refresh.bind(this), false);
}
}
//使用路由規則
var R = new CustomRouter();
R.init();
var res = document.getElementById('result');
R.route('/hash1',function () {
document.getElementById("show-hash-result").innerHTML = location.hash;
})
R.route('/index.html', function() {
res.style.height='150px';
res.style.width='300px';
res.style.background = 'green';
res.innerHTML = '<html>我是首頁</html>';
});
R.route('/server', function() {
res.style.height='150px';
res.style.width='300px';
res.style.background = 'orange';
res.innerHTML = '我是服務頁面';
});
R.route('/mine', function() {
res.style.background = 'red';
res.style.height='150px';
res.style.width='300px';
res.innerHTML = '我的界面';
});
</script>
</body>
</html>
以上代碼只是為了演示前端路由的作用,一般情況下,這種路由我們是不需要自己寫的,使用 react/vue 都會有相應的路由工具類,我們發現了 hash 只會改變瀏覽器地址,不會刷新瀏覽器
- H5 的 history
window 的 history 提供了對瀏覽器歷史記錄的訪問功能,并且它暴露了一些方法和屬性,讓你在歷史記錄中自由的前進和后退,并且在 H5 中還可以操作歷史記錄中的數據。
我們在 chrome 瀏覽器的調試窗口中在 Console 中輸入 window.history,會得到 history 的一些方法和屬性,如下圖所示
總結一下 history 的 API 如下:
interface History {
readonly attribute long length;
readonly attribute any state;
void go(optional long delta);
void back();
void forward();
//h5 引進以下兩個方法
void pushState(any data, DOMString title, optional DOMString? url = null);
void replaceState(any data, DOMString title, optional DOMString? url = null);
};
- 1、
back():
在歷史記錄中后退
history.back() ;
- 2、
forward:
在歷史記錄中前進
history.forward();
- 3、
go():
移動到指定的歷史記錄點
history.go(-1)
其中正數是前進「+1就是前進一個界面」,負責是后退的意思「-1就是后退一個界面」
4、
length:
hisgory 的屬性,顯示 history 的長度5、
pushState(data,title[,url]):
給歷史記錄堆棧頂部添加一條記錄
history.pushState(data,title[,url])
如果想更進一步的了解 H5 的 history ,推薦看這里:https://developer.mozilla.org/en-US/docs/Web/API/History_API,非常值得一看
從上面我們了解到,使用 H5 的 history 的 pushState 可以代替 hash,并且更加優雅,廢話不多說,我們直接上效果圖
從效果圖中我們可以看到前端路由實現了,點擊各個導航沒有刷新瀏覽器,并且點擊瀏覽器的回退按鈕,會顯示上一次記錄,這都是使用 h5 history 的 pushState 和監聽 onpopstate 實現的,這就是一個簡單的 SPA ,基本上實現了和上面 hash 一樣的功能
源碼
我們只看核心代碼
<h4>使用 h5 實現前端路由</h4>
<ul>
<li> <a onclick="home()">首頁</a></li>
<li> <a onclick="message()">消息</a></li>
<li> <a onclick="mine()">我的</a></li>
</ul>
<div id="showContent" style="height:240px;width:200px;background-color:red">
home
</div>
<script type="text/javascript">
function home() {
// 添加到歷史記錄棧中
history.pushState({name:'home',id:1},null,"?page=home#index")
showCard('home')
};
function message() {
history.pushState({name:'message',id:2},null,"?page=message#haha")
showCard('message')
}
function mine(){
history.pushState({
id:3,
name:'mine'
},null,"?name=tigerchain&&sex=man")
showCard('mine')
}
// 監聽瀏覽器回退 并且刷新到指定內容
window.addEventListener('popstate',function (event) {
var content = "";
if(event.state) {
content = event.state.name;
}
console.log(event.state)
console.log("history 中的歷史棧中的 name :"+content)
showCard(content)
})
// 此方法和上面的方法是一毛一樣的,只是兩種不同的寫法而已
// window.onpopstate = function (event) {
// var content = "";
// if(event.state) {
// content = event.state.name;
// }
// showCard(content);
// }
function showCard(name) {
console.log("當前的 hash 值是:"+location.hash)
document.getElementById("showContent").innerHTML = name;
}
</script>
以上就是通過 H5 的 history 實現的一個前端路由
我們稍微總結一下:
http://192.168.1.200:8080/index
http://192.168.1.200:8080/about/aboutus.html/#/flag=1
http://192.168.1.200:8080/feedback
- 后端路由:每次仿問都要向 server 發送一個請求,server 需要響應解析,會有延遲「網絡不好更嚴重」
- 前端路由:只是改變瀏覽器的地址,不刷新瀏覽器,不和服務端交互,所以性能有大大的提高「用戶體驗提高」,并且前端路由有兩種實現方式
(1)、實現 hash 并監聽 hashchange 事件來實現
(2)、使用 H5 的 hisgory 的 pushState() 監聽 popstate 方法來實現
到這里,我們大概對路由有一個整體的了解了,下面我們看看 veu 的路由
二、Vue 路由
Vue 中的路由,推薦使用官方支持的 vue-router 庫,當然我們可以不使用 vue-router 可以使用三方的路由庫,或者自己牛 b 完全可以自己寫一個路由庫「使用 hash 或 history」
本文中我們使用 vue-router 3.0.1來講解,考慮到團隊開發協作,我們先寫一個使用 html 引入 vue.js 的方式來使用 vue-router,后面專注說使用模塊化開發「使用 vue-cli 創建項目中使用 vue-router,這應該是團隊開發的最佳方式」
html 中 引入 vue-router
1、廢話不多了,直接寫一個簡單的 SPA 應用來感受一下
效果如下:
從上圖中我們可以看到,我們使用 vue-router 實現了一個簡單的類 hash 的路由功能
2、源碼
- 1、引入 vue.js 和 vue-router.js
- 2、定義 Main、Message、Mine 組件
- 3、聲明路由規則
聲明路由規則,把路徑所對應要跳轉的組件先定義出來「相當于一個配置項」,到時候瀏覽器地址指定到對應的路由下會自動跳轉到所對應的組件「這樣就完成了路由功能」
- 4、創建 router 實例
創建 router 實例,并把 routes 傳遞進去
- 5、注冊 router「把 router 注入到指定的掛載點下」
經過以上幾步,路由功能就完成了,我們現在定義了路徑,定義了路由所對應的組件,那組件要顯示在哪里呢?那就前面 1 中的圖中所顯示的 <router-view />
中
可是如何讓我在點擊不同的按鈕進不同的路由「不是的組件呢?」,這里就要使用到 <router-link to="路由配置中的路徑">比如首頁<router-link />
,從名字就可以看出來就是路由鏈接到那個路徑,路徑會匹配出相應的組件顯示在 router-view
中
這樣我們就完成了 vue-router 的基本使用,使用聲明式導航 <router-link :to="...">
「其實就是創建了一個 a 標簽」來完成了導航的鏈接
模塊化「組件式開發」中 vue-router 的使用
如果使用 vue 開發手機 webapp ,那么頁面跳轉就非常多,路由使用的非常非常多,這樣就更能體現出路由的強大之處,在這里我們使用 vue-cli 來創建一個 webapp ,來模擬一個簡單的手機 app ,體驗一下路由
效果如下:
- 1、初始化項目
使用 vue-cli 命令創建項目的時候,我們選擇安裝 vue-router,默認進去項目中就會有 vue-router 的配置,創建好的項目結構如下:
我們看到創建的目錄結構多了一個 router 目錄和 index.js 文件「vue-cli默認給添加的,如果你選了安裝 vue-router 的話」
- 2、文件簡單的分析
我們來看看 router 下的 index.js「路由配置文件」
我們可以看到默認 vue-cli 創建的帶路由的項目把 router 配置文件單獨的寫在了 router/index.js 文件中了,并且我們看到默認指定打開的是 HelloWorld 組件「想配置路由組件,引入組件配置即可」
再看 main.js
一般來說 APP.vue 就是我們應用的首頁,我們在 main.js 中注入路由,從而讓整個應用都具有路由功能
- 3、再看 App.vue
核心就一個 <router-view/>
即可路由組件要顯示的地方,默認路由路徑是 / 對應的是 HelloWorld 組件,所以運行起應用就會顯示 HelloWorld 組件,這里不顯示運行后的結果了,我們使用 vue-cli 創建的 demo 樣式看的太多了,自行運行查看即可
經過以上分析,我們基本上就把 vue-cli 帶路由的 demo 說完了「核心就這幾個東西,剩下的無非就是配置路由,然后組件組合,然后各種路由跳轉」
- 4、修改 demo,一步步修改成效果圖的樣式
添加 First.vue 文件「核心代碼
樣式和數據「mockList 就是一個數組」就不截圖了,完整例子可以查看 源碼:https://github.com/tigerchain/vue-lesson/tree/master/06、Vue路由/vue-cli-router-webapp
其中的條目點擊方法 nav(index)
就是路由跳轉功能
以上點擊方法我們使用編程式導航「跳轉」,當然你也可以不傳參數
this.$router.push({name:xxx,params:{xxxx}})
細心的朋友會發現,我們這個 name 叫 second 這是那里來的,它其實就是我們在 router/index.js 中配置的別名,再看 router/index.js 文件之前,我們先添加一個 Second.vue 組件
添加 Second.vue 組件「核心代碼
這個沒有什么好說的,就是一個有導航條并且接收到 First.vue 路由跳轉傳遞過來的參數
修改 router/index.js 文件
前面我們使用跳轉 this.$router.push({name:'second',params:{itemData:this.mockList[index]}})
中傳了一個 name 為 second ,我們說了這是在 router/indes.js 中配置的,下面我們來看此文件
我們看到 / 對應的是 First 組件,/second 對應的是 Second 組件,并且分別給了 name 屬性「在路由 push 的時候就可以使用到」,沒什么好說的,當然你也可以不寫 name 屬性,路由跳轉有幾種寫法
// 命名的路由
router.push({ path: '/second', params: { xxx:xxx }})
// 對象
router.push({ name: 'second', params: { xxx:xxx }})
等幾種方式,如果有 name 屬性直接使用即可,如果沒有就使用 path「router/inde.js 中配置的路由 path」 即可,具體可以看官網:https://router.vuejs.org/zh-cn/essentials/navigation.html
如果想返回上一個界面,那么就使用 this.$router.go(-1)
和 h5 的 history 是一樣的
修改 App.vue
我們來修改 App.vue 來讓每個組件的內容都能全屏顯示「我這里使用的 flexbox 布局,具體看源碼」
這沒有什么好說的,都是一些 css 樣式的設置
運行查看效果
從效果圖中我們可以看到我們已經實現了兩個界面之間的跳轉「通過路由功能」只不過這個界面跳轉有點生硬,顯的很不友好,下面我們給界面跳轉添加一個動畫
添加界面跳轉動畫
說到 vue 的跳轉動畫,我們就要說說 transition 過度效果,簡單的說 transition 就是控制組件或標簽的進入和離開的過度「這里不過多的介紹,我們看如何修改代碼
修改 App.vue
- 第一步:給 router-view 添加過慮效果
- 第二步:給 transitionName 設置個屬性值
在這里我們給 transitionName 設置一個值「這個值可以隨便起一個名字,這里我就叫做 slide-right,向右滑動」
- 第三步:給 transitionName 添加進入,退出效果
這里我們給過度設置效果,一般使用 name-enter「過渡開始的狀態」 name-enter-active「定義進入過渡的狀態」、name-leave「離開過渡的開始狀態」、name-leave-active「離開的過度狀態」,其中 name 是可以的值是可以動態設置的,類如上面的 slide-right 和 slide-left,但是后面的部分是固定的,下面我們來使用 name
- 第四步:監聽路由變化
我們先把路由定義一個統一的返回方法,打開 router/index.js,添加如下代碼,使用 js 的 prototype「prototype 屬性使您有能力向對象添加屬性和方法」 屬性即可
這樣一來,router 就有了統一的返回方法,直接調用即可,我們修改返回按鈕的返回方法如下:
this.$router.goBack()
- 第五步:監聽路由變化
在這里我們使用到了 Vue 的 watch「這里不專門說,以后有需要會抽出來說」 方法,修改如下:
這樣我們就可以監聽是進入界面還是返回界面「這里 watch 和 data 方法是平級的,如果是返回的話,則從左邊滑入到右邊,打開新界面就是從右邊滑到左邊」
通過以上步驟,我們就給路由添加了一個過渡效果,我們來看看效果,就和剛開始的效果圖是一樣的
這樣我就完成了組件式開發中的路由的基本功能,結束了嗎?這里本應該可以結束了,但是我們前面學過組件,并且 vue 的核心特點之一就是組件化開發,我們這里使用的頭部功能「兩個界面頭部如下」
我們可以看到兩個界面的頭部是何其的相似,這里我們完全可以把這個頭部封裝成一個組件呀,下面我們繼續改造我們的代碼
封裝頭部組件
- 1、新建一個 Head.vue 文件「核心代碼」
- 2、在 First.vue 和 Second.vue 中引入
分三步:
第一步:引入組件
第二步:注冊組件
第三步:使用組件
想在那個 vue 組件中使用以上三步就可以搞定了
到這里我們就把 head 組件封裝完成了,并且達到使用的上的,具體可以看源代碼
到此為止,我們把 vue-router 就介紹完了,總結一下
三、總結
- 路由的分類:前端路由和后端路由「區別和聯系」
- 使用 hash 和 history 分別實現前端路由
- vue-router 的基本使用方法「使用 html 引入和模塊化開發兩種方式」
- vue-router 的舉例子「手機端開發 webapp」
點贊富一生,轉發富五代,更多文章請關注我的微信公號來查閱
公眾號:TigerChain