3、高級篇
前言
基礎篇鏈接:https://www.cnblogs.com/xiegongzi/p/15782921.html
組件化開發篇鏈接:https://www.cnblogs.com/xiegongzi/p/15823605.html
3.1、組件的自定義事件
3.1.1、綁定自定義事件
這里實現方式有兩種:一種是用v-on搭配VueComponent.$emit實現
ps:此種方式有點類似子傳父;另一種是使用ref屬性搭配mounted()來實現 此種方式:復雜一點,但是更靈活
兩種實現方式都可以,而二者的區別和computed與watch的區別很像
ps:第二種方式可以實現異步操作,如:等到ajax發送請求得到數據之后,再進行事件綁定
接下來看實例,從而了解更明確點,用如下實例做演示 ps:不用前面玩的子傳父啊,但是:自行可以先回顧一下子傳父的傳統方式:父給子一個函數,子執行,參數即為父要得到的數據
3.1.1.1、v-on搭配$emit
實現
v-on是可以簡寫的啊!!!!
3.1.1.2、ref搭配mounted實現
在前面實現的基礎上,子組件代碼不變,在父組件中加入如下的代碼
mounted()中是可以進行異步操作的啊,所以才可以讓這種自定義事件更靈活
另外:既然是事件,那么也可以使用事件修飾符:prevent、stop、once
- 在v-on中,這三者和以前一樣的玩法,都是加在事件名后面即可,如:
@zixeiqing.once = "xxxxx"
- 在ref中,是用在
this.$refs.person.$on('zixieqing',this.demo )
中的$on
這里的,once就是使用$once
,替換掉原來的$on
3.1.2、解綁自定義事件
這玩意用的就是 VueComponent.$off(['EventName'])
這個內置函數來實現解綁的,
- 當然:數組[]中,如果是解綁單個事件,那么[]這個括號不要也行;
- 如果是解綁多個自定義事件,那么使用 , 逗號隔開即可;
- 另外:
$off()
不傳遞參數時,默認是把組件的所有自定義事件都解綁了 ps:有一個解綁一個 - 自定義事件的核心話:給誰綁定事件,那么事件就在誰身上;給誰解綁自定義事件,那么就去誰身上解綁
另外:前面玩Vue生命周期的beforeDestroy時有一個小點只是簡單提了一下,生命周期圖如下
上圖中的內容,有最后一點沒有驗證:
說在beforeDestroy中,會銷毀子組件和自定義事件
說此時銷毀自定義事件
而所謂的銷毀子組件也就好理解了,就是把父組件銷毀之后,那么:子組件也活不成了 ps:要驗證的話,可以使用銷毀vm,然后看旗下的子組件還能活不?答案肯定是活不成的
3.1.3、自定義事件中的兩大坑
子組件是如下的樣子
1、在ref屬性實現的方式中,關於this的指向問題
- 第一種就是將回調函數放到父組件的methods中
此種方式,會發現this指向的是父組件
- 第二種:將回調函數直接放到
this.$refs.people.$on('event',xxxx )]
的xxxx中:
這種情況:會發現,this不再是父組件實例對象,而是子組件的實例對象,但是:可以讓它變為父組件實例對象 ps:把回調的普通函數寫成蘭姆達表達式就可以了
2、組件使用原生DOM事件的坑 ps:了解native修飾符,學后端的人看源碼的時候,對這個修飾符再熟悉不過了
3.1.4、自定義事件總結
自定義事件是一種組件間通信的方式,適用於:子組件 ——> 父組件通信
使用場景:想讓子組件給父組件傳遞數據時,就在父組件中給子組件綁定自定義事件 ps:事件回調在父組件methods / 其他地方 中,而要解綁自定義事件就找子組件本身
綁定自定義事件:
-
1、在父組件中:
<Person @zixieqing="demo"/>
或<Person v-on:zixieqing="demo"/>
-
2、在父組件中:
<Person ref = "demo"/>
.........
methods: {
test(){......}
}
.........
mounted(){
this.$refs.demo.$on('eventName',this.test)
}
-
3、若想讓自定義事件只能觸發一次,可以使用once修飾符 ps:使用v-on的方式實現的那種 或 $once ps:使用ref屬性實現方式的那種
-
觸發自定義事件:
this.$emit('eventName',sendData)
ps:給誰綁定自定義事件,就找誰去觸發 -
解綁自定義事件:
this.$off(['eventName',.......])
ps:給誰綁定自定義事件,就找誰解綁;另:注意解綁事件是單個、多個、全解的寫法 -
組件上也可以綁定元素DOM事件,但是:需要使用native修飾符
-
注意項:通過
this.$refs.xxxx.$on('eventName',回調)
綁定自定義事件時,回調要么配置在父組件的methods中,要么用蘭姆達表達式 ps:或箭頭函數,否則:this執行會出現問題
3.2、全局事件總線
這玩意兒吧,不算知識點,是開發中使用的技巧而已,里面包含的知識在前面全都玩過了,只是:把知識做了巧妙的應用,把自定義事件變化一下,然后加上Vue中的一個內置關系VueComponent.prototype._ _proto _ _ === Vue.prototype
從而實現出來的一個開發技巧
此技巧:可以實現任意組件間的通信
3.2.1、疏通全局事件總線邏輯
但是:現在把思路換一下來實現它
通過上面的分析圖了解之后,就可以分析出:單獨選取的那個組件需要具有如下的特性:
- 1、此組件能夠被所有的組件看到
- 2、此組件可以調用
$on()
、$emit()
、$off()
1、那么為了實現第一步:能夠讓所有的組件都看得到可以怎么做?
- 1)、使用window對象,可以做到( 但是:不推薦用 )
此種方式不推薦用呢,是因為:本來就是框架,誰還去window上放點東西呀,不是找事嗎
2、就是利用Vue中的內置關系 VueComponent.prototype._ _proto _ _ === Vue.prototype
,即:公共組件選為Vue實例對象vm,這個關系怎么來的,這里不再說明了,在基礎篇VueComponent()中已經說明過了,利用此內置關系就是利用:VueComponent可以獲取Vue原型上的屬性和方法,同時選取了Vue實例對象之后, $on()
、$emit()
、$off()
都可以調用了,這些本來就是Vue的內置函數啊,Vue實例對象還沒有這些函數嗎
3.2.2、全局事件總線實例
實現方式: vm + beforeCreate() ps:初始化嘛,讓關系在一開始就建立 + mounted() + $on() + $emit() + beforeDestroy() ps:做收尾工作,解綁自定義事件 + $off()
實例演示:
注:上圖中的 $bus
是自己起的名字,但是開發中一般起的都是這個名字,bus公交車嘛,誰都可以上,而且還可以載很多人,放到組件中就是:誰都可以訪問嘛,加了一個 $
是因為迎合Vue的設計,內置函數嘛,假裝不是程序員自己設計的 ps:實際上,bus還有總線的意思
3.2.3、全局事件總線總結
全局事件總線又名GlobalEventBus
它是一種組件間的通信方式,可以適用於任何組件間通信
全局事件總線的玩法:
- 1、安裝全局事件總線
new Vue({
.......
beforeCreate(){
Vue.prototype.$bus = this
},
......
})
- 2、使用事件總線
- 發送數據:
this.$bus.$emit('EventName',sendData)
- 接收數據:A組件想接收數據,則:在A組件中給$bus綁定自定義事件,把事件的回調放到A組件自身中【 ps:靠回調來得到數據 】
- 發送數據:
// 使用methods也行;不使用,把回調放到$on()中也可以
// ps:推薦使用methods,因為不必考慮$on()中的this問題
methods: {
sendData(){ ...... }
},
........
mounted(){
this.$bus.$on('eventName',receiveData)
},
.......
beforeDestroy(){
this.$bus.$off([ 'eventName' , ..... ])
}
3.3、消息訂閱與發布
什么是消息訂閱與發布?
這個東西每天都見到,就是:關注,關注了人,那別人發了一個通知 / 文章,自己就可以收到,這就是訂閱與發布嘛
這里使用pubsub-js這個庫來演示( 使用其他庫也行,這些玩意兒的思路都一樣,這是第三方庫啊,不是vue自己的 ),其中:
-
pub 就是publish,推送、發布的意思
-
sub 就是subscribe 訂閱的意思
-
也就是:一方發布、一方訂閱嘛,這個東西玩后端的人再熟悉不過了,Redis中就有消息訂閱與發布,而且用的指令都是這兩個單詞,RabbitMQ也是同樣的套路,只是更復雜而已
3.3.1、玩一下pubsub-js
基礎代碼
1、給項目安裝pubsub-js庫,指令:npm install pubsub-js
2、消息發布方
-
2.1、引入pubsub-js庫
-
2.2、使用 publish('msgName' , sendData) 這個API進行數據發送
3、消息接收方
- 3.1、引入pubsub-js庫
- 3.2、使用 subscribe('msgName' , callback) 這個API利用回調進行數據接收
- 3.3、關閉訂閱
4、效果如下
3.3.2、消息訂閱與發布總結
它是一種組件間通信的方式,適用於:任意組件間通信
使用步驟:
-
1、安裝pubsub-js 指令:
npm install pubsub-js
-
2、消息接收方、發送方都要引入pubsub 代碼;:
import pubsub from "pubsub-js
- 數據發送方:
pubsub.publish('msgName',sendData)
- 數據接收方:
- 數據發送方:
// methods可寫可不寫【 ps:推薦寫,不用考慮this的指向問題,和自定義事件一樣的 】
methods: {
demo(){.....}
}
........
mounted(){
// 使用this.msgName把每條訂閱都綁在組件實例對象vc上,方便取消訂閱時獲取到這個訂閱id
this.msgName = pubsub.subscribe('msgName', callback) // 如果不寫methods,那么回調就寫在這里,注意:使用箭頭函數
}
.......
beforeDestroy(){
pubsub.unsubscribe(this.msgName)
}
3.4、插槽
1、基礎代碼
3.4.1、默認插槽
此種插槽適合只占用一個位置的時候
需求、讓食品分類中顯示具體的一張食品圖片、讓電影分類中顯示某一部具體的電影,使用默認插槽改造
<template>
<div class="container">
<Category title = "食品">
<!-- 2、將內容套在組件標簽里面從而攜帶到slot處
此種方式:是vue在解析完App這個組件的模板時,
將整個組件中的內容給解析完了,然后放到了Category組件里面使用slot占位處
所以:slot所放位置 和 這里面代碼解析之后放過去的位置有關
另外:由於是先解析完APP組件中的東西之后 再放到 所用組件里面slot處的位置
因此:這里可以使用css+js,這樣就會讓 模板 + 樣式解析完了一起放過去
不用css+js就是先把模板解析完了放過去,然后找組件里面定義的css+js
-->
<img src="./assets/food.png" alt="照片開小差去了">
</Category>
<Category title = "游戲">
<ul>
<li v-for=" (game,index) in games" :key="index">{{game}}</li>
</ul>
</Category>
<Category title = "電影">
<video controls src="./assets/枕刀歌(7) - 山雨欲來.mp4"></video>
</Category>
</div>
</template>
<script>
import Category from "./components/Category.vue"
export default {
name: 'App',
components: {Category},
data() {
return {
foods: ['紫菜','奧爾良烤翅','各類慕斯','黑森林','布朗尼','提拉米蘇','牛排','熟壽司'],
games: ['王者榮耀','和平精英','英雄聯盟','文明與征服','拳皇','QQ飛車','魔獸爭霸'],
filems: ['無間道','赤道','禁閉島','唐人街探案1','肖申克的救贖','盜夢空間','無雙']
}
},
}
</script>
<style>
.container {
display: flex;
justify-content: space-around;
}
img,video {
width: 100%;
}
</style>
3.4.2、具名插槽
指的就是有具體名字的插槽而已,也就比默認插槽多了兩步罷了
在使用slot進行占位時就利用name屬性起一個名字,然后在傳遞結構時使用slot="xxx"屬性指定把結構內容插入到哪個插槽就OK了
需求、在電影分類的底部顯示"熱門"和"懸疑"
3.4.3、作用域插槽
這個玩意兒的玩法和具名插槽差不多,只是多了一點步驟、多了一個要求、以及解決的問題反一下即可
基礎代碼
需求:多個組件一起使用,數據都是一樣的,但是有如下要求:
- 數據顯示時要求不是全都是無序列表,還可以用有序列表、h標簽......
- 要求data數據是在子組件中 ps:就是Category中,而結構需要寫在父組件中 ps:就是App中,也就是父組件要拿到子組件中的data
- 利用前面已經玩過的子父組件通信方式可以做到,但是麻煩。因此:改造代碼
開始改造代碼:
- 第一步:提data
- 查看效果:
-
可能會想:明明可以將data放到父組件中 / 將結構ul放到子組件中,從而實現效果,為什么非要像上面那樣折騰,沒事找事干?開發中有時別人就不會把數據交給你,他只是給了你一條路,讓你能夠拿到就行
-
第二步:使用作用域插槽進行改造
既然作用域插槽會玩了,那就實現需求吧
另外:父組件接收數據時的scope還有一種寫法,就是使用slot-scope
3.4.4、插槽總結
1、作用:讓父組件可以向子組件指定位置插入HTML結構,也是一種組件間通信的方式,適用於:父組件 ===》 子組件
2、分類:默認插槽、具名插槽、作用域插槽
3、使用方式
- 1)、默認插槽
// 父組件
<Category>
<div>
HTML結構
</div>
</Category>
// 子組件
<template>
<div>
<!-- 定義插槽 -->
<slot>插槽默認內容</slot>
</div>
</template>
- 2)、具名插槽
// 父組件
<template>
<!-- 指定使用哪個插槽 -->
<Category slot = "footer">也可以加入另外的HTML結構</Category>
</template>
// 子組件
<template>
<div>
<!-- 定義插槽 並 起個名字-->
<slot name = "footer">插槽默認內容</slot>
</div>
</template>
- 3)、作用域插槽
// 子組件
<template>
<div class="category">
<h3>{{title}}分類</h3>
<!-- 作用域插槽
1、子組件( 傳遞數據 )
提供一個路口,把父組件想要的數據傳給它【 ps:有點類似於props的思路,只是反過來了 】
:filems中的filems就是提供的路,給父組件傳想要的東西【 ps:對象、數據都行 】
-->
<slot :filems = "filems">這是默認值</slot>
</div>
</template>
<script>
export default {
name: 'Category',
props: ['title'],
data() { // 數據在子組件自身中
return {
filems: ['無間道','赤道','禁閉島','唐人街探案1','肖申克的救贖','盜夢空間','無雙']
}
},
}
</script>
// 父組件
<template>
<div class="container">
<Category title = "電影">
<!--
2、父組件( 接收數據 )
前面說的 多了一個要求 就是這里"必須用template標簽套起來"
怎么接收?使用scope="xxxx"屬性 xxx就是接收到的數據,這個名字隨便取
這個名字不用和子組件中用的 :filems 這個filems這個名字保持一致,因:它接收的就是這里面傳過來的東西
但是:這個數據有點特殊,需要處理一下
-->
<template scope="receiveData">
<!-- {{receiveData}} -->
<!-- 拿到了數據,那"頁面的結構就可以隨插槽的使用者隨便玩"了 -->
<ul>
<li v-for="(filem,index) in receiveData.filems" :key="index">{{filem}}</li>
</ul>
</template>
</Category>
<Category title = "電影">
<!-- ES6中的"結構賦值"簡化一下 -->
<template scope="{filems}">
<ol>
<li v-for="(filem,index) in filems" :key="index">{{filem}}</li>
</ol>
</template>
</Category>
<Category title = "電影">
<!-- ES6中的"結構賦值"簡化一下 -->
<template scope="{filems}">
<h4 v-for="(filem,index) in filems" :key="index">{{filem}}</h4>
</template>
</Category>
<!-- scope還有一種寫法 使用slot-scope-->
<Category title = "電影">
<!-- ES6中的"結構賦值"簡化一下 -->
<template slot-scope="{filems}">
<h4 v-for="(filem,index) in filems" :key="index">{{filem}}</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from "./components/Category.vue"
export default {
name: 'App',
components: {Category},
}
</script>
3.5、Vuex
概念:在Vue實例中集中式狀態( 數據,狀態和數據是等價的 )管理的一個插件,說得通俗一點就是:數據共享,對多個組件間的同一個數據進行讀/寫,不就是集中式管理了嗎( 對立的觀點就是"分布式",學Java的人最熟悉為什么有分布式
github地址:https://github.com/vuejs/vuex 官網的話,在vue官網的生態中有vuex
什么時候用vuex?
- 1、多個組件依賴同一狀態( 數據 )【 ps:兩個帥哥,都想要同一個靚妹 】
- 2、不同組件的行為 需要 變更同一狀態【 ps:洗腳城的兩個妹子 都想要把 客戶錢包中的money變到自己荷包中 】
- 3、上面的使用一句話概括:多組件需要分享數據時就使用Vuex
3.5.1、vuex原理
首先這個東西在官網中有,但是:不全
所以:把官網的原圖改一下
上述內容只是對原理圖有一個大概認識而已,接下來會通過代碼逐步演示就懂了
3.5.2、搭建Vuex環境
1、在項目中安裝vuex 指令:'npm install vuex'
2、編寫store
3、讓store在任意組件中都可以拿到
這樣store就自然出現在其他組件身上了【 ps:利用了vc和vm的內置關系 ,驗證自行驗證,在其他組件中輸出this就可以看到了】
vuex環境搭建小結
- 1、創建文件src/store/index.js
// 引入vuex
import vuex from "vuex"
// 使用vuex —— 需要vue 所以引入vue
import Vue from "vue"
Vue.use(vuex)
// 創建store中的三個東西actions、mutations、state
const actions = {}
const mutations = {}
const state = {}
// 創建store ———— 和創建vue差不多的套路
export default new vuex.Store({
// 傳入配置項 ———— store是actions,mutations,state三者的管理者,所以配置項就是它們
actions, // 完整寫法 actions:actions ,是對象嘛,所以可以簡寫
mutations,state
})
- 2、在main.js中配置store
import App from "./App.vue"
import Vue from "vue"
// 引入store
import store from "./store"
// 由於起的名字是index,所以只寫./store即可,這樣默認是找index,沒有這個index才報錯
const vm = new Vue({
render: h=>h(App),
components: {App},
// 讓store能夠被任意組件看到 ———— 加入到vm配置項中【 ps:和全局事件總線很像 】
store,
template: `<App></App>`,
}).$mount('#app')
3.5.3、簡單玩一下vuex的流程
對照原理圖來看
1、先把要操作的數據 / 共享數據放到state中
2、在組件中使用dispatch這個API把key-value傳給actions【 ps:dispatch學Java的人很熟悉了吧,SpringMVC的中央大腦dispatcherServlet】
- actions就是第一層處理 ps:服務員嘛
2、actions接收key-value ps:要是有邏輯操作,也放在這里面,ajax也是
- 這里就是調用commit這個API把key-value傳給mutation,這個mutation才是真正做事的人 ps:后廚嘛
3、mutations接收key-value
4、查看開發者工具 ps:簡單了解,自行萬一下
- 另外:vuejs devtools開發工具版本不一樣,則:頁面布局也不一樣,但是:功能是一樣的
當然:前面說過,直接在組件中調commit這個API從而去和mutations打交道,這種是可以的,適用於:不需要邏輯操作的過程,示例就自行完了
以上便是簡單了解vuex,前面的例子看起來沒什么用,但是vuex這個東西其實好用得很
3.5.4、認識getters配置項
這玩意兒就和data與computed的關系一樣
3.5.5、四個map方法
3.5.5.1、mapState和mapGetters
1、改造源代碼 —— 使用計算屬性實現
但是:上面這種是我們自己去編寫計算屬性,從而做到的,而Vuex中已經提供了一個東西,來幫我們自動生成計算屬性中的哪些東西,只需一句代碼就搞定
2、使用mapState改造獲取state中的數據,從而生成計算屬性
- 1、引入mapState 代碼:
import {mapState} from "vuex"
- 2、使用mapState ps:對象寫法
- 3、數組寫法 ps:推薦用的一種
...mapState({sum:'sum'})
這里面的sum:'sum'
這兩個是一樣的,那么:一名多用
3、使用mapGetters把getters中的東西生成為計算屬性
- 1、引入mapGetters 代碼:
import {mapState,mapGetters} from "vuex"
- 2、使用mapGetters ps:和mapState一模一樣 ,對象寫法
- 3、數組寫法
3.5.5.2、mapActions 和 mapMutations
會了mapState,那么其他的map方法也會了,差不多的,只是原理是調了不同的API罷了,當然:也會有注意點
mapState 和 mapGetters是生成在computed中的,而mapActions和mapMutations是生成在methods中的
1、mapActions —— 調的API就是dispatch
- 使用mapActions改造
- 1、引入mapActions 代碼:
import {mapActions} from 'vuex'
- 2、使用mapActions ps:對象寫法 准備調入坑中
- 1、引入mapActions 代碼:
- 原因:
- 3、數組寫法。 一樣的,函數名和actions中的名字一樣 ps:一名多用
2、mapMutations 這個和mapActions一模一樣,只是調用的API是commit
所以:此種方式不演示了,會了前面的三種中任意一種,也就會了這個
3.5.6、簡單玩一下組件共享數據
1、在state中再加一點共享數據
2、新增Person組件
3、共享數據
- 在Count組件中獲取person,在person中獲取Count ps:此步不演示,會了前者后者就會了
操作如下:
3.6、路由器 router
3.6.1、認識路由和路由器
路由:就是一組key-value的映射關系 也就是route
-
key就是網站中的那個路徑
-
value 就是function 或 component組件
- function 是因為后端路由( 后端調用函數 對該路徑的請求做響應處理 )
- component 組件就不用多說了
-
路由器 router:就是專門用來管理路由的
ps:理解的話,就參照生活中的那個路由器,它背后有很多插孔,然后可以鏈接到電視機,那個插孔就是key ,而鏈接的電視機就是value嘛
在vue中,router路由器是一個插件庫,所以需要使用npm install vue-router
來進行安裝,這個東西就是專門用來做單頁面網站應用的
所謂的單頁面網站應用就是 SPA,即:只在一個頁面中進行操作,路徑地址發生改變即可,然后就把相應的東西展示到當前頁面,不會發生新建標簽頁打開的情況【 參照美團網址,進行點一下,看看地址、頁面更新、是否新建標簽頁打開、美團就是單頁面網站應用 】
- SPA 整個應用只有
一個完整的頁面
、點擊頁面中的導航鏈接不會刷新頁面
,只會做頁面的局部刷新
、數據需要通過ajax請求獲取
3.6.2、簡單使用路由器
1、准備工作:
- 1)、引入bootstrap.css
2、開始玩路由器 router
-
1)、給項目安裝路由 指令:
npm install vue-router
-
2)、在main.js中引入並使用路由器 ps:路由器是一個插件
- 3)、編寫組件
- 4)、配置路由器 ps:這也叫配置路由規則,就是key-value的形式,在前端中,key是路徑,value是組件
- 5)、把配置的路由規則引入到main.js中
-
6)、在靜態頁面中使用路由 ps:需要記住兩個標簽
<router-link ..... to = "路徑名"></router-link>
和<router-view></router-view>
-
<router-link ..... to = "路徑名"></router-link>
是指:跳轉 其中:路徑名 就是 路由規則中配置的 path 參照a標簽來理解 本質就是轉成了a標簽 -
<router-view></router-view>
是指:視圖顯示 就是告知路由器 路由規則中配置的component應該顯示在什么位置,和slot插槽一樣,占位
-
- 頁面結構源碼如下:
<template>
<div>
<div class="row">
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header"> <h2>Vue Router Demo </h2></div>
</div>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<router-link class="list-group-item" active-class="active" to="./about">About</router-link>
<router-link class="list-group-item" active-class="active" to="./home">Home</router-link>
<!--對照a標簽 <a class="list-group-item active" href="./home.html">Home</a> -->
</div>
</div>
<div class="col-xs-6">
<div class="paner">
<div class="paner-body">
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
- 7)、運行效果如下:
另外:從此處開始,可以先摸索Element-ui組件庫了,這是專門搭配vue來做頁面的網站 和 bootstrap一個性質,這個東西后續要用,網址如下:
vue-router使用小結
-
1、安裝vue-router ,命令:
npm install vue-router
-
2、在main.js中 指令:
import VueRouter from "vue-router"
-
3、應用vue-router插件,指令:
Vue.use(VueRouter)
-
4、編寫router路由規則
// 引入路由器
import VueRouter from "vue-router"
// 引入需要進行跳轉頁面內容的組件
import About from "../components/About.vue"
import Home from "../components/Home.vue"
// 創建並暴露路由器
export default new VueRouter({
routes: [ // 路由器管理的就是很多路由 所以:routes 是一個數組
{ // 數組里面每個路由都是一個對象 它有key和value兩個配置項【 ps:還有其他的 】
path: '/about', // 就是key 也就是路徑名,如:www.baidu.com/about這里的about
component: About // 就是value 也就是組件
},{
path: '/home',
component: Home
},
]
})
- 5、實現切換( active-class 可配置高亮樣式 )
<router-link class="list-group-item" active-class="active" to="./about">About</router-link>
- 6、指定展示位置
<router-view></router-view>
3.6.3、聊聊路由器的一些細節
1、路由組件以后都放到page文件夾下,而一般組件都放到components中
2、路由切換時,“隱藏”了的路由組件,默認是被銷毀掉了,需要時再去重新掛載的
ps:示例自行通過beforeDestroy和mounted進行測試
3、每個組件都有自己的$route
屬性,里面存儲這自己的路由信息
4、整個應用只有一個router,可以通過組件的$router
屬性獲取到
ps:驗證自行把不同組件的這個東西綁定到window對象上,然后等路由組件掛載完畢了,拿到它們進行比對,答案是:false
3.6.4、多級路由
1、在src/page下再新建兩個路由組件
2、給home路由規則編寫多級路由
3、重新編寫Hmoe.vue路由組件
源碼如下:
<template>
<div>
<h2>我是Home的內容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<!-- 多級路由,這里的to后面需要加上父級路徑 先這么寫,它可以簡寫,后續進行處理 -->
<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
</li>
<li>
<router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
</li>
</ul>
<ul>
<router-view></router-view>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'Home'
}
</script>
4、運行效果如下
3.6.5、路由傳參
1、query的字符串傳參寫法 ps:也就是路徑傳參嘛,適合傳遞少量參數
- 1)、編寫數據,改造Message.vue路由組件 ps:傳遞數據者
- 2)、編寫Detail.vue路由組件 ps:數據接收者
- 3)、編寫路由規則
- 4)、效果如下:
2、query的對象寫法 ps:適合傳遞大量參數
運行效果也是一樣的
3.6.6、命名路由
這個東西就是為了處理path中的那個字符串很長的問題,相當於起個別名
實例:
- 1)、修改路由規則
- 2)、使用命名路由精簡path
- 3)、運行效果如下
命名路由小結
-
作用:簡化路由跳轉時的path寫法
-
使用:
- 給命令命名
{
path: '/home',
component: Home,
children: [
{
path: 'news',
component: News
},{
path: 'message',
component: Message,
children: [
{
path: 'detail',
// 使用另一個配置項name 命名路由,從而讓path更精簡
name: 'detail',
component: Detail
}
]
},
]
},
- 簡化路由跳轉寫法
<!-- 簡化前寫法 -->
<router-link
:to="{
path: '/home/message/detail',
query: {
id: m.id,
title: m.title
}
}">
{{m.title}}
</router-link>
<!-- 簡化后寫法 -->
<router-link
:to="{
name: 'detail',
query: {
id: m.id,
title: m.title
}
}">
{{m.title}}
</router-link>
3.6.7、路由另一種傳參 - params
注意:這種傳參必須基於name配置項才可以,待會兒會說明
另外就是:這種傳參也就是后端中的RESTful傳參
1、使用params傳遞參數 ps:數據的傳遞者,這一步和以前的query傳遞沒什么兩樣,只是改了一個名字而已
- 1)、字符串寫法 / 對象寫法
- 2)、修改路由規則
- 3)、獲取參數 ps:和query相比,就是數據存放的位置變了一下而已,可以在mounted中輸出this.$route看一下結構
- 4)、效果如下
路由params傳參小結
- 1、配置路由
{
path: '/home',
component: Home,
children: [
{
path: 'news',
component: News
},{
path: 'message',
component: Message,
children: [
{
// 使用params傳參,則:需要把path的規則改了,就是占位,接對應參數
path: 'detail/:id/:title',
name: 'detail', // 對象寫法,必須保證有這個配置項
component: Detail
}
]
},
]
},
- 2、傳遞參數
<!-- 使用params傳遞參數 -->
<!-- 字符串寫法 -->
<router-link :to="`/home/message/detail/${m.id}/${m.title}`">
{{m.title}}
</router-link>
<!-- 對象寫法
這種寫法必須保證里面是name,而不是params,否則:頁面內容會丟失的
-->
<router-link
:to="{
name: 'detail',
params: {
id: m.id,
title: m.title
}
}">
{{m.title}}
</router-link>
-
注意點:路由攜帶params參數時,若使用的to的對象寫法,則:不能使用path配置項,必須用name配置項
-
3、接收參數
{{$route.params.id}}
{{$route.params.title}}
3.6.8、路由的props配置項
props這個東西在組件的父傳子通信時見過,但是:不是一回事,那是組件間的,現在是路由的,寫法幾乎不一樣
這里有一句話:哪個路由組件要接收數據,props就配置在哪個路由規則中
回到問題:插值語法講究的就是簡單的模板語法,而下圖這種就要不得,所以:要簡化
1、布爾值寫法
- 為true時,則把path接收到的所有params參數以props的形式發給所需路由組件, 如:這里的Detail
- 注意:是params傳遞的參數,所以:這就是缺點之一
- 另外就是:以props形式傳給所需組件,所以:在所需路由組件那邊需要使用pros:['xxxx']來進行接收,從而在所需組件中使用數據時就可以進行簡化了
2、函數寫法
- 這種寫法:是最靈活的一種,props為函數時,該函數返回的對象中每一組key-value都會通過props傳給所需數據的路由組件 ps:如這里的Detail
- 這種寫法可以獲取query傳遞的數據,也可以接收params傳遞的,注意點就是:在函數中調用時的名字變一下即可
- 這種的好處就是:一是query和params都可以接收,二是:把數據接收的邏輯寫到要接收數據的路由組件的路由規則去了,邏輯更清晰,不至於到處找邏輯代碼
3、另外的方式
- 另外的方式,不推薦使用,所以:簡單了解即可
- 1)、在數據使用者處自行抽離代碼,弄成計算屬性 ps:如示例中的Detail路由組件,取數據時在插值語法中麻煩,那就在下方配置計算屬性從而抽離代碼,但是:畫蛇添足,因為:在計算屬性中拿數據時會使用
this.$route.queru / params.xxxx
代碼量大的話,這不就還得多寫N多this嗎 - 2)、對象寫法 —— 不用了解,知道有這么一個東西即可,需要時自行百度即可 ps:這個東西接收數據是死的,開發中基本上用都不用
- 此種方式:是將該對象中所有的key-value的組合最終通過props傳給所需路由組件
3.6.9、router-link中的replace屬性
上面這種模式就是push模式,它的原理就是棧空間,壓棧push嘛 ps:router-link中的默認模式就是push模式
但是還有一種模式是:replace模式,這種模式是產生一個記錄之后,就把上一次的記錄給干掉了,所以效果就是不會有回退的記錄,回退按鈕都點不了 ps:就是走一路短一路,沒后路了 ^ _ ^ ,要實現這種模式也簡單,就是:在router-link中加一個replace屬性即可
這樣之后,再點擊Home / About時,它的歷史記錄不會留下
router-link的replace屬性小結
- 作用:控制路由跳轉時操作瀏覽器歷史記錄的模式
- 瀏覽器的歷史記錄有兩種寫入方式,分別為
push
和replace
,其中:push
是追加歷史記錄,replace
是替換當前記錄,路由跳轉時默認為push
- 開啟replace模式的方式:
<router-link replace ......>News</router-link>
3.6.10、編程式路由導航
這玩意兒就是不再借助 router-link
來實現路由跳轉,前面玩了 $route
,而現在就是來玩的 $router
先看一下 $router
這個東西,順便知道掌握哪些API,就是下圖中的五個
1、玩一下push和replace這兩個API
- push和replace的原理就是前面說的,一個保留歷史記錄,一個會清除上一次的歷史記錄
2、玩一下back和forward這兩個API
- 這兩個就是瀏覽器中的前進和后退
3、玩一下go這個API
- go()中可以用正數和負數,正數表示:前進所填數字步;負數就是后退所填數字的絕對值步
3.6.11、緩存路由組件
- 前面不是說了:路由組件在被切換走之后,是會被銷毀的,因此:這種情況就會導致有時某個路由組件中的數據不應該被銷毀,而是保留起來,所以:就需要借助即將說明的知識點,就是使用了一個
<keep-alive include = "componentName"></keep-alive>
標簽來實現
1、緩存一個路由組件 —— 字符串寫法
方便驗證,加上如下的代碼
2、緩存多個路由組件 —— 數組寫法
演示就不做了
3.6.12、另外的生命鈎子
在基礎篇中就說過:除了哪里講的8個生命鈎子 ps:4對,其實還有三個生命鈎子沒有玩,也說了等到路由之后再整
1、另一對生命鈎子
-
這一對生命鈎子就是:activated 和 deactivated,其中:
- activated 就是激活 ps:我想見你了,就調它
- deactivated 就是失活 ps:我不想見你了,你離開吧,就調它,有點類似於beforeDestroy這個鈎子,但是:處理的情況不一樣
- 適用場景:提前使用了
keep-alive include = "xxx"
保留改組件,切換后不讓其銷毀 ps:beforeDestroy不起作用了 ,那么:又想要最后關掉定時器之類的,就可以使用這兩個鈎子函數
-
實例:
2、另外一個鈎子函數就是nextTick
-
這個東西不演示了,它是為了:讓解析時產生時間差,因為:有些東西需要把解析完之后的樣子插入到頁面中了才可以繼續做另外的事情,因此:就可以借助nextTick,從而:讓一部分模板先被解析好,放入頁面中,然后再解析后面的一些東西時執行一些我們想要的邏輯,如:input框,有這么一個場景,讓input渲染到頁面時,我鼠標的焦點就在input框中,這就可以使用此鈎子函數,在掛載時調用此鈎子,然后把光標聚焦的邏輯放到nextTick回調中
-
nextTick鈎子的玩法官網有,一看就懂
3.6.13、路由守衛 - 重要、開發常用
所謂的路由守衛,就是權限問題,即:擁有什么權限,才可以訪問什么路由
3.6.13.1、全局前置路由守衛
**所謂的全局前置路由守衛,特點之一就體現在前置二字上,它的API是 beforeEach((to, from, next) => { } ),也就是:在路由切換之前回調會被調用 / 初始化渲染時回調會被調用 **
實例:
不方便演示,所以直接說
- to就是切換路由組件之后的組件位置 ps:即 去哪里,目標組件
- 而from 是 切換路由組件之前的組件位置 ps:即 來自哪里 從哪個路由組件來
- 另外 next是一個函數next(),就是放行的意思
現在做一個操作,在瀏覽器中緩存一個key-value,然后訪問News、Message路由時判斷key-value是否對得上,對就展示相應的路由組件,否則:不展示
- 玩點小動作
當然:前面的過程有一個小技巧可以簡化
給路由規則添加一個meta配置項,就是路由元數據,利用這個配置項,我們可以給路由中放一些我們想放的東西進去 ps:哪個路由需要權限驗證,就在哪個路由中加入meta配置項,那就改造吧
3.6.13.2、全局后置路由守衛
這玩意兒就和全局前置路由守衛反着的嘛,但是:處理場景不一樣
全局后置路由守衛 調用的API是 afterEach((to, from ) => {}) ,注意:和全局前置路由守衛相比,少了next參數,后置了嘛,都已經在前面把權限判斷完了,你還考慮放不放行干嘛,這個全局后置路由守衛做的事情其實不是去判斷權限問題,而是收尾,做一些過了權限之后的判斷問題,比如:點擊某個路由組件之后,只要可以查看這個組件內容,那么:就把網頁的頁簽標題給換了
這個API是 初始化渲染時回調會被調用 / 路由組件切換之后會被調用,
實例:
操練一手: ps: 先用純的全局前置路由守衛來做
先加點東西進去
上面看起來成功了,但是有bug,可以試着把network中的網絡調成slow 3G,可以稍微清楚地看到效果 ,想要改成功,就算把項目中 public/index.html的title改了,也是一樣,會有加載過程,因此:想在全局前置路由守衛中達到想要的效果,改出花兒來也莫得辦法,而且在全局前置路由守衛中寫兩遍一樣的代碼根本不標准
想要實現前面的效果,那就需要全局后置路由守衛登場了,掉一下API,里面一行代碼搞定
效果就不演示了,已經達到效果了
ps:注意得把public/index.html中的title改成'大數據智慧雲生平台',不然訪問根目錄時也有加載過程,這不是此知識點的鍋,因為:原生的public/index.html的title是讀取的package.json中第二行的name
3.6.13.3、獨享路由守衛
這玩意兒就是指:某一個路由獨享的路由守衛,調用的API是: beforeEnter((to, from, next)=>{}),它是指:在進入配置這個API的路由之前回調會被調用,其中:to、from、next的意思和前面全局前置路由守衛一樣,但注意:這種沒有什么后置之類的,它只有這一個前置,即:獨享路由守衛
最后:路由守衛之間,是可以隨意搭配的
實例:
3.6.14、路由器的兩種工作模式
這兩種模式是:hash和history模式
- hash模式 路由器的默認模式 就是路徑中有一個#,這#后面的內容不用隨http傳給服務器,服務器也不會收到 ps:前端玩一下而已,注意:這個hash和后端中的hash算法哪些東西不一樣啊,別搞混了
- history模式 這個就好理解了嘛,就是沒有了那個#,然后路徑中ip:port之后的東西是會隨着http傳給服務器的
hash和history兩種模式的區別:
- 1、hash模式 路徑中有#,且#后的內容不會隨http傳給服務器;而history模式 路徑中沒有# ip:port之后的東西會隨http傳給服務器
- 2、hash模式的兼容性好,而history模式的兼容性略差
在路由器中hash和history兩種模式的切換 在路由規則中加個全新的配置項mode即可
3.6.15、關於項目上線的問題
了解這個東西是因為前面說的hash和history的另一個區別,在上線時有個坑 / 注意點
1、編寫完了程序之后打包項目
- 啟動項目一直用的是
npm run serve
,在腳手架時就說過還有一個命令:npm run build
,那時說過:后端要的前面資源是HTML+CSS+JS,所以此時項目打包就需要用到它了 - 先把路由器的工作模式切換成history模式,然后再打包,這樣方便演示bug
2、使用node+express框架編寫一台小服務器模擬一下上線
-
自行新建一個文件夾,然后使用vscode打開
-
1)、讓文件夾變成合法包 指令:
npm init
- 2)、安裝express 指令:
npm install express
注意:這一步很容易因為自己當初配置nodejs時操作不當,導致權限不夠啊,就會報一堆warn和error
- 3)、新建一個js文件,編寫內容如下
- 源碼如下:
// 1、引入express 注意:這里就不是ES6的模塊化了,而是commanjs模塊化
const express = require('express')
// 2、創建一個app服務實例對象
const app = express()
// 3、端口號監聽
app.listen(8001,(err)=>{ // err是一個錯誤對象
if(!err) console.log("服務器啟動成功");
})
// 4、配置一個后端路由
app.get('/person',(req,res)=>{ // req就是request res就是response
res.send({
name: '紫邪情',
age: 18
})
})
- 4)、啟動服務器 指令:
node server
- 5)、訪問服務器中的端口測試一下
3、准備工作弄完了,現在把剛剛使用 npm run build
打包的dist中的文件復制到服務器中去
- 在服務器中新建一個static / public文件夾
ps:這兩個文件夾后端的人很熟悉了,就是SpringBoot中的那兩個,這里就不解釋了,這兩個文件夾鍵哪一個都可以
4、讓復制進去的文件能夠被服務器認識
重新執行node server
開始演示路由器的兩種工作模式的另一個坑
ps:別忘記有個權限認證啊,在緩存把對應東西放上,不然有些路由組件點不了
整bug,隨便點一些路由組件之后,刷新頁面
ps:只要保證路徑不是localhost:8001即可,讓它后面有點東西
這就是history模式的坑,但是:切換成hash就不會出現這樣,出現上述的情況是因為:剛剛我們在頁面中隨便點路由組件都有頁面是因為:那些都是靜態頁面嘛,那些數據啊、歷史記錄啊都是原本就有的,即:不走網絡請求,但是:刷新之后,是走網絡請求的,也就會把路徑中ip:port之后的東西隨着http發給服務器了,它去服務器找資源就是找http://localhost:8001/home/news
中的/home/news,服務器中那有這個資源,所以:404唄
想要history模式也和hash一樣,刷新不出錯,就需要找后端人員進行處理,需要后端人員配合你這邊拿過去的資源來做,后端處理這種問題的方式有很多,如:Nginx
ps:但是嘛,有時別人甩你個錘子,最終代碼出問題是自己背鍋罷了,所以:自己解決,需要借助一個插件 connect-history-api-fallback
3.7、結語
Vue2到此結束,另外還有一些Vue UI組件庫
移動端
- Vant 鏈接:https://youzan.github.io/vant/#/zh-CN
- Cube UI 鏈接:https://didi.github.io/cube-ui/#/zh-CN
- Mint UI 鏈接:http://mint-ui.github.io/#!/zh-cn
PC端
- Element UI 鏈接:https://element.eleme.cn/#/zh-CN
- IView UI 鏈接:https://www.iviewui.com
接下來的技術點就是:Vue3
鏈接:https://www.cnblogs.com/xiegongzi/p/15875530.html