Vue2技術整理3 - 高階篇 - 更新完畢

紫邪情發表於2022-02-10

3、高階篇

前言





3.1、回顧瀏覽器本地儲存

  • 就是localstorage和sessionstorage這兩個( 前者瀏覽器關閉不會清空,後者關閉瀏覽器會清空 ),二者常用的API都一模一樣,二者裡面存的資料都是key-value的形式
    • 存資料 setItem( 'key', 'value' ) 使用:LocalStorage.setItem( 'key' , 'value' )其他的都是差不多的
    • 取資料 getItem( 'key' ) 注:若key不存在,則:此API返回值是null( 若key是物件字串 那麼用JSON.parse()轉成物件 返回值也是null )
    • 刪除某一個資料 removeItem()
    • 清空所有 clear()
  • 二者儲存的內容大小一般為5M的字串( 不同瀏覽器可能會不一樣 )




3.3、元件的自定義事件

3.3.1、繫結自定義事件
  • 這裡實現方式有兩種:一種是用v-on搭配VueComponent.$emit實現【 ps:此種方式有點類似子傳父 】;另一種是使用ref屬性搭配mounted()來實現【 此種方式:複雜一點,但是更靈活 】
  • 兩種實現方式都可以,而二者的區別和computed與watch的區別很像【 ps:第二種方式可以實現非同步操作,如:等到ajax傳送請求得到資料之後,再進行事件繫結 】,接下來看例項,從而瞭解更明確點,用如下例項做演示【 ps:不用前面玩的子傳父啊,但是:自行可以先回顧一下子傳父的傳統方式:父給子一個函式,子執行,引數即為父要得到的資料 】

image




3.3.1.1、v-on搭配emit實現

image

image

image

  • v-on是可以簡寫的啊!!!!



3.3.1.2、ref搭配mounted實現
  • 在前面實現的基礎上,子元件程式碼不變,在父元件中加入如下的程式碼

image

image

  • mounted()中是可以進行非同步操作的啊,所以才可以讓這種自定義事件更靈活

  • 另外:既然是事件,那麼也可以使用事件修飾符:prevent、stop、once

    • 在v-on中,這三者和以前一樣的玩法,都是加在事件名後面即可,如:'@zixeiqing.once = "xxxxx"'
    • 在ref中,是用在'this.\(refs.person.\)on('zixieqing',this.demo )'中的'\(on'這裡的,once就是使用'\)once',替換掉原來的'$on'



3.3.2、解綁自定義事件
  • 這玩意用的就是'VueComponent.$off( ['要解綁的事件名'] )'這個內建函式來實現解綁的,
    • 當然:陣列[ ]中,如果是解綁單個事件,那麼[ ]這個括號不要也行;
    • 如果是解綁多個自定義事件,那麼使用 , 逗號隔開即可;
    • 另外:$off()不傳遞引數時,預設是把元件的所有自定義事件都解綁了【 ps:有一個解綁一個 】
    • 自定義事件的核心話:給誰繫結事件,那麼事件就在誰身上;給誰解綁自定義事件,那麼就去誰身上解綁

image

  • 另外:前面玩Vue生命週期的beforeDestroy時有一個小點只是簡單提了一下,生命週期圖如下

image

  • 上圖中的內容,有最後一點沒有驗證:

image

說在beforeDestroy中,會銷燬子元件和自定義事件

  • 說此時銷燬自定義事件

image

image

  • 而所謂的銷燬子元件也就好理解了,就是把父元件銷燬之後,那麼:子元件也活不成了【 ps:要驗證的話,可以使用銷燬vm,然後看旗下的子元件還能活不?答案肯定是活不成的 】



3.3.3、自定義事件中的兩大坑

子元件是如下的樣子

  • image


1、在ref屬性實現的方式中,關於this的指向問題

  • 第一種就是將回撥函式放到父元件的methods中
    • image
    • image
    • 此種方式,會發現this指向的是父元件
  • 第二種:將回撥函式直接放到'this.\(refs.people.\)on( 'event',xxxx ) ]'的xxxx中
    • image
    • image
    • 這種情況:會發現,this不再是父元件例項物件,而是子元件的例項物件,但是:可以讓它變為子元件例項物件【 ps:把回撥的普通函式寫成蘭姆達表示式就可以了 】
      • image
      • image


2、元件使用原生DOM事件的坑【 ps:瞭解native修飾符,學後端的人看原始碼的時候,對這個修飾符再熟悉不過了 】

  • image
  • image
  • image
  • image



3.3.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.4、全域性事件匯流排

  • 這玩意兒吧,不算知識點,是開發中使用的技巧而已,裡面包含的只是在前面全都玩過了,只是:把知識做了巧妙的應用,把自定義事件變化一下,然後加上Vue中的一個內建關係VueComponent.prototype._ _proto _ _ === Vue.prototype從而實現出來的一個開發技巧
  • 此技巧:可以實現任意元件間的通訊


3.4.1、疏通全域性事件匯流排邏輯

image

但是:現在把思路換一下來實現它

image



通過上面的分析圖瞭解之後,就可以分析出:單獨選取的那個元件需要具有如下的特性:

  • 1、此元件能夠被所有的元件看到
  • 2、此元件可以呼叫'\(on()'、'\)emit()'、'$off()'


1、那麼為了實現第一步:能夠讓所有的元件都看得到可以怎麼做?

  • 1)、使用window物件,可以做到( 但是:不推薦用 )
    • image
    • image
    • image
    • 此種方式不推薦用呢,是因為:本來就是框架,誰還去window上放點東西呀,不是找事嗎
  • 2、就是利用Vue中的內建關係VueComponent.prototype._ _proto _ _ === Vue.prototype,即:公共元件選為Vue例項物件vm,這個關係怎麼來的,這裡不再說明了,在基礎篇VueComponent()中已經說明過了,利用此內建關係就是利用:VueComponent可以獲取Vue原型上的屬性和方法,同時選取了Vue例項物件之後,'\(on()'、'\)emit()'、'$off()'都可以呼叫了,這些本來就是Vue的內建函式啊,Vue例項物件還沒有這些函式嗎



3.4.2、全域性事件匯流排例項
  • 實現方式:'vm + beforeCreate()【 ps:初始化嘛,讓關係在一開始就建立 】 + mounted() + $on() + $emit() + beforeDestroy()【 ps:做收尾工作,解綁自定義事件 】+ $off()'


例項演示:

  • image
    • 注:上圖中的'\(bus'是自己起的名字,但是開發中一般起的都是這個名字,bus公交車嘛,誰都可以上,而且還可以載很多人,放到元件中就是:誰都可以訪問嘛,加了一個'\)'是因為迎合Vue的設計,內建函式嘛,假裝不是程式設計師自己設計的【 ps:實際上,bus還有匯流排的意思 】
  • image
  • image
  • image



3.4.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.5、訊息訂閱與釋出

什麼是訊息訂閱與釋出?

  • 這個東西每天都見到,就是:關注,關注了人,那別人發了一個通知 / 文章,自己就可以收到,這就是訂閱與釋出嘛
  • 這裡使用pubsub-js這個庫來演示( 使用其他庫也行,這些玩意兒的思路都一樣,這是第三方庫啊,不是vue自己的 ),其中:
    • pub 就是publish,推送、釋出的意思
    • sub 就是subscribe 訂閱的意思
    • 也就是:一方釋出、一方訂閱嘛,這個東西玩後端的人再熟悉不過了,Redis中就有訊息訂閱與釋出,而且用的指令都是這兩個單詞,RabbitMQ也是同樣的套路,只是更復雜而已


3.5.1、玩一下pubsub-js

基礎程式碼

  • image
  • image


1、給專案安裝pubsub-js庫,指令:npm install pubsub-js

  • image


2、訊息釋出方

  • 2.1、引入pubsub-js庫

  • 2.2、使用publish( 'msgName' , sendData )這個API進行資料傳送

    • image


3、訊息接收方

  • 3.1、引入pubsub-js庫
  • 3.2、使用subscribe( 'msgName' , callback )這個API利用回撥進行資料接收
  • 3.3、關閉訂閱
    • image


4、效果如下

  • image



3.5.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.6、插槽

1、基礎程式碼

  • image
  • image
  • image



3.6.1、預設插槽
  • 此種插槽適合只佔用一個位置的時候

需求、讓食品分類中顯示具體的一張食品圖片、讓電影分類中顯示某一部具體的電影,使用預設插槽改造

  • image

  •     <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>
    
    
  • image




3.6.2、具名插槽
  • 指的就是有具體名字的插槽而已,也就比預設插槽多了兩步罷了
  • 在使用slot進行佔位時就利用name屬性起一個名字,然後在傳遞結構時使用slot="xxx"屬性指定把結構內容插入到哪個插槽就OK了


需求、在電影分類的底部顯示"熱門"和"懸疑"

  • image
  • image
  • image



3.6.3、作用域插槽
  • 這個玩意兒的玩法和具名插槽差不多,只是多了一點步驟、多了一個要求、以及解決的問題反一下即可


基礎程式碼

  • image
  • image
  • image



需求:多個元件一起使用,資料都是一樣的,但是有如下要求:

  • 資料顯示時要求不是全都是無序列表,還可以用有序列表、h標籤......
  • 要求data資料是在子元件中【 ps:就是Category中 】,而結構需要寫在父元件中【 ps:就是App中 】,也就是父元件要拿到子元件中的data
  • 利用前面已經玩過的子父元件通訊方式可以做到,但是麻煩。因此:改造程式碼


開始改造程式碼:

  • 第一步:提data
    • image
    • 檢視效果:
      • image
      • 可能會想:明明可以將data放到父元件中 / 將結構ul放到子元件中,從而實現效果,為什麼非要像上面那樣折騰,沒事找事幹?開發中有時別人就不會把資料交給你,他只是給了你一條路,讓你能夠拿到就行
  • 第二步:使用作用域插槽進行改造
    • image
    • image
    • image
    • image


既然作用域插槽會玩了,那就實現需求吧

  • image
  • image
  • image


另外:父元件接收資料時的scope還有一種寫法,就是使用slot-scope

  • image



3.6.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.7、Vuex

  • 概念:在Vue例項中集中式狀態( 資料,狀態和資料是等價的 )管理的一個外掛,說得通俗一點就是:資料共享,對多個元件間的同一個資料進行讀/寫,不就是集中式管理了嗎( 對立的觀點就是"分散式",學Java的人最熟悉為什麼有分散式
  • github地址:https://github.com/vuejs/vuex 官網的話,在vue官網的生態中有vuex
  • 什麼時候用vuex?
    • 1、多個元件依賴同一狀態( 資料 )【 ps:兩個帥哥,都想要同一個靚妹 】
    • 2、不同元件的行為 需要 變更同一狀態【 ps:洗腳城的兩個妹子 都想要把 客戶錢包中的money變到自己荷包中 】
    • 3、上面的使用一句話概括:多元件需要分享資料時就使用Vuex



3.7.1、vuex原理
  • 首先這個東西在官網中有,但是:不全

image

  • 所以:把官網的原圖改一下
    • image
    • 上述內容只是對原理圖有一個大概認識而已,接下來會通過程式碼逐步演示就懂了



3.7.2、搭建Vuex環境

1、在專案中安裝vuex 指令:'npm install vuex'



2、編寫store

  • image


3、讓store在任意元件中都可以拿到

  • image
  • image
  • 這樣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.7.3、簡單玩一下vuex的流程
  • 對照原理圖來看


1、先把要操作的資料 / 共享資料放到state中

  • image
  • image


2、在元件中使用dispatch這個API把key-value傳給actions

  • image
  • actions就是第一層處理【 ps:服務員嘛 】
  • image


2、actions接收key-value【 ps:要是有邏輯操作,也放在這裡面,ajax也是 】

  • 這裡就是呼叫commit這個API把key-value傳給mutation,這個mutation才是真正做事的人【 ps:後廚嘛 】
  • image
  • image


3、mutations接收key-value

  • image
  • image
  • image
  • image


4、檢視開發者工具【 ps:簡單瞭解,自行萬一下 】

  • 另外:vuejs devtools開發工具版本不一樣,則:頁面佈局也不一樣,但是:功能是一樣的

  • image

當然:前面說過,直接在元件中調commit這個API從而去和mutations打交道,這種是可以的,適用於:不需要邏輯操作的過程,示例就自行完了

以上便是簡單瞭解vuex,前面的例子看起來沒什麼用,但是vuex這個東西其實好用得很




3.7.4、認識getters配置項
  • 這玩意兒就和data與computed的關係一樣
  • image
  • image
  • image



3.7.5、四個map方法
3.7.5.1、mapState和mapGetters

image



1、改造原始碼 —— 使用計算屬性實現

  • image
  • image
  • 但是:上面這種是我們自己去編寫計算屬性,從而做到的,而Vuex中已經提供了一個東西,來幫我們自動生成計算屬性中的哪些東西,只需一句程式碼就搞定


2、使用mapState改造獲取state中的資料,從而生成計算屬性

  • 1、引入mapState 程式碼:import {mapState} from "vuex"
  • 2、使用mapState【 ps:物件寫法 】
    • image
    • image
  • 3、陣列寫法【 ps:推薦用的一種 】
    • ...mapState({sum:'sum'})這裡面的sum:'sum'這兩個是一樣的,那麼:一名多用
    • image
    • image



3、使用mapGetters把getters中的東西生成為計算屬性

  • 1、引入mapGetters 程式碼:import {mapState,mapGetters} from "vuex"
  • 2、使用mapGetters【 ps:和mapState一模一樣 ,物件寫法 】
    • image
    • image
  • 3、陣列寫法
    • image



3.7.5.2、mapActions 和 mapMutations
  • 會了mapState,那麼其他的map方法也會了,差不多的,只是原理是調了不同的API罷了,當然:也會有注意點
  • mapState 和 mapGetters是生成在computed中的,而mapActions和mapMutations是生成在methods中的


1、mapActions —— 調的API就是dispatch

image

  • 使用mapActions改造
    • 1、引入mapActions 程式碼: import {mapActions} from 'vuex'
    • 2、使用mapActions【 ps:物件寫法 】 —— 準備調入坑中
      • image
      • image
      • 原因:
        • image
        • image
    • 3、陣列寫法 —— 一樣的,函式名和actions中的名字一樣【 ps:一名多用 】
      • image
      • image


2、mapMutations —— 這個和mapActions一模一樣,只是呼叫的API是commit

  • image
  • 所以:此種方式不演示了,會了前面的三種中任意一種,也就會了這個



3.7.6、簡單玩一下元件共享資料

1、在state中再加一點共享資料

  • image


2、新增Person元件

  • image
  • image
  • image


3、共享資料

  • 在Count元件中獲取person,在person中獲取Count【 ps:此步不演示,會了前者後者就會了 】

操作如下:

  • image
  • image



3.8、路由器 router

3.8.1、認識路由和路由器
  • 路由:就是一組key-value的對映關係 也就是route
    • key就是網站中的那個路徑
    • value 就是function 或 component元件
      • function 是因為後端路由( 後端呼叫函式 對該路徑的請求做響應處理 )
      • component 元件就不用多說了
  • 路由器 router:就是專門用來管理路由的【 ps:理解的話,就參照生活中的那個路由器,它背後有很多插孔,然後可以連結到電視機,那個插孔就是key ,而連結的電視機就是value嘛 】


  • 在vue中,router路由器是一個外掛庫,所以需要使用npm install vue-router來進行安裝,這個東西就是專門用來做單頁面網站應用的
  • 所謂的單頁面網站應用就是 SPA,即:只在一個頁面中進行操作,路徑地址發生改變即可,然後就把相應的東西展示到當前頁面,不會發生新建標籤頁開啟的情況【 參照美團網址,進行點一下,看看地址、頁面更新、是否新建標籤頁開啟、美團就是單頁面網站應用 】
    • SPA 整個應用只有一個完整的頁面、點選頁面中的導航連結不會重新整理頁面,只會做頁面的區域性重新整理、資料需要通過ajax請求獲取



3.12、路由器 router

3.12.1、認識路由和路由器
  • 路由:就是一組key-value的對映關係 也就是route
    • key就是網站中的那個路徑
    • value 就是function 或 component元件
      • function 是因為後端路由( 後端呼叫函式 對該路徑的請求做響應處理 )
      • component 元件就不用多說了
  • 路由器 router:就是專門用來管理路由的【 ps:理解的話,就參照生活中的那個路由器,它背後有很多插孔,然後可以連結到電視機,那個插孔就是key ,而連結的電視機就是value嘛 】


  • 在vue中,router路由器是一個外掛庫,所以需要使用npm install vue-router來進行安裝,這個東西就是專門用來做單頁面網站應用的
  • 所謂的單頁面網站應用就是 SPA,即:只在一個頁面中進行操作,路徑地址發生改變即可,然後就把相應的東西展示到當前頁面,不會發生新建標籤頁開啟的情況【 參照美團網址,進行點一下,看看地址、頁面更新、是否新建標籤頁開啟、美團就是單頁面網站應用 】
    • SPA 整個應用只有一個完整的頁面、點選頁面中的導航連結不會重新整理頁面,只會做頁面的區域性重新整理、資料需要通過ajax請求獲取



3.12.2、簡單使用路由器

1、準備工作:

  • 1)、引入bootstrap.css
    • image



2、開始玩路由器 router

  • 1)、給專案安裝路由 指令:npm install vue-router

  • 2)、在main.js中引入並使用路由器【 ps:路由器是一個外掛 】

    • image
  • 3)、編寫元件

    • image
  • 4)、配置路由器【 ps:這也叫配置路由規則,就是key-value的形式,在前端中,key是路徑,value是元件 】

    • image
  • 5)、把配置的路由規則引入到main.js中

    • image
  • 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插槽一樣,佔位

    • image

    • 頁面結構原始碼如下:

      •     <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)、執行效果如下:

    • image



另外:從此處開始,可以先摸索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.12.3、聊聊路由器的一些細節
  • 1、路由元件以後都放到page資料夾下,而一般元件都放到components中
  • 2、路由切換時,“隱藏”了的路由元件,預設是被銷燬掉了,需要時再去重新掛載的【 ps:示例自行通過beforeDestroy和mounted進行測試 】
    • image
  • 3、每個元件都有自己的$route屬性,裡面儲存這自己的路由資訊
    • image
    • image
  • 4、整個應用只有一個router,可以通過元件的$router屬性獲取到【 ps:驗證自行把不同元件的這個東西繫結到window物件上,然後等路由元件掛載完畢了,拿到它們進行比對,答案是:false 】
    • image



3.12.4、多級路由

1、在src/page下再新建兩個路由元件

  • image


2、給home路由規則編寫多級路由

  • image


3、重新編寫Hmoe.vue路由元件

  • image

  • 原始碼如下:

    •     <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、執行效果如下

  • image



3.12.5、路由傳參

1、query的字串傳參寫法【 ps:也就是路徑傳參嘛,適合傳遞少量引數

  • 1)、編寫資料,改造Message.vue路由元件【 ps:傳遞資料者 】
    • image
  • 2)、編寫Detail.vue路由元件【 ps:資料接收者 】
    • image
  • 3)、編寫路由規則
    • image
  • 4)、效果如下:
    • image


2、query的物件寫法 【 ps:適合傳遞大量引數

  • image
  • 執行效果也是一樣的
    • image



3.12.6、命名路由
  • 這個東西就是為了處理path中的那個字串很長的問題,相當於起個別名


例項:

  • 1)、修改路由規則
    • image
  • 2)、使用命名路由精簡path
    • image
  • 3)、執行效果如下
    • image



命名路由小結

  • 作用:簡化路由跳轉時的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.12.7、路由另一種傳參 - params
  • 注意:這種傳參必須基於name配置項才可以,待會兒會說明
  • 另外就是:這種傳參也就是後端中的RESTful傳參


1、使用params傳遞引數【 ps:資料的傳遞者 】,這一步和以前的query傳遞沒什麼兩樣,只是改了一個名字而已

  • 1)、字串寫法 / 物件寫法
    • image
  • 2)、修改路由規則
    • image
  • 3)、獲取引數 【 ps:和query相比,就是資料存放的位置變了一下而已,可以在mounted中輸出this.$route看一下結構 】
    • image
  • 4)、效果如下
    • image


路由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.12.8、路由的props配置項
  • props這個東西在元件的父傳子通訊時見過,但是:不是一回事,那是元件間的,現在是路由的,寫法幾乎不一樣
  • 這裡有一句話:哪個路由元件要接收資料,props就配置在哪個路由規則中
  • 回到問題:插值語法講究的就是簡單的模板語法,而下圖這種就要不得,所以:要簡化
    • image


1、布林值寫法

  • 為true時,則把path接收到的所有params引數以props的形式發給所需路由元件, 如:這裡的Detail

    • 注意:是params傳遞的引數,所以:這就是缺點之一
    • 另外就是:以props形式傳給所需元件,所以:在所需路由元件那邊需要使用pros:['xxxx']來進行接收,從而在所需元件中使用資料時就可以進行簡化了
  • image



2、函式寫法

  • 這種寫法:是最靈活的一種,props為函式時,該函式返回的物件中每一組key-value都會通過props傳給所需資料的路由元件【 ps:如這裡的Detail 】
    • 這種寫法可以獲取query傳遞的資料,也可以接收params傳遞的,注意點就是:在函式中呼叫時的名字變一下即可
    • 這種的好處就是:一是query和params都可以接收,二是:把資料接收的邏輯寫到要接收資料的路由元件的路由規則去了,邏輯更清晰,不至於到處找邏輯程式碼
  • image


3、另外的方式

  • 另外的方式,不推薦使用,所以:簡單瞭解即可
  • 1)、在資料使用者處自行抽離程式碼,弄成計算屬性 【 ps:如示例中的Detail路由元件,取資料時在插值語法中麻煩,那就在下方配置計算屬性從而抽離程式碼,但是:畫蛇添足,因為:在計算屬性中拿資料時會使用this.$route.queru / params.xxxx 程式碼量大的話,這不就還得多寫N多this嗎
  • 2)、物件寫法 —— 不用瞭解,知道有這麼一個東西即可,需要時自行百度即可【 ps:這個東西接收資料是死的,開發中基本上用都不用 】
    • 此種方式:是將該物件中所有的key-value的組合最終通過props傳給所需路由元件



3.12.9、router-link中的replace屬性

image

  • 上面這種模式就是push模式,它的原理就是棧空間,壓棧push嘛【 ps:router-link中的預設模式就是push模式
    • image
  • 但是還有一種模式是:replace模式,這種模式是產生一個記錄之後,就把上一次的記錄給幹掉了,所以效果就是不會有回退的記錄,回退按鈕都點不了【 ps:就是走一路短一路,沒後路了 ^ _ ^ ,要實現這種模式也簡單,就是:在router-link中加一個replace屬性即可 】
    • image
    • 這樣之後,再點選Home / About時,它的歷史記錄不會留下
      • image


router-link的replace屬性小結

  • 作用:控制路由跳轉時操作瀏覽器歷史記錄的模式
  • 瀏覽器的歷史記錄有兩種寫入方式,分別為pushreplace,其中:push是追加歷史記錄,replace是替換當前記錄,路由跳轉時預設為push
  • 開啟replace模式的方式:<router-link replace ......>News</router-link>



3.12.10、程式設計式路由導航
  • 這玩意兒就是不再借助router-link來實現路由跳轉,前面玩了$route,而現在就是來玩的$router
  • 先看一下$router這個東西,順便知道掌握哪些API,就是下圖中的五個
    • image


1、玩一下push和replace這兩個API

  • push和replace的原理就是前面說的,一個保留歷史記錄,一個會清除上一次的歷史記錄
  • image
  • image


2、玩一下back和forward這兩個API

  • 這兩個就是瀏覽器中的前進和後退
  • image
  • image


3、玩一下go這個API

  • go()中可以用正數和負數,正數表示:前進所填數字步;負數就是後退所填數字的絕對值步
  • image
  • image



3.12.11、快取路由元件
  • 前面不是說了:路由元件在被切換走之後,是會被銷燬的,因此:這種情況就會導致有時某個路由元件中的資料不應該被銷燬,而是保留起來,所以:就需要藉助即將說明的知識點,就是使用了一個<keep-alive include = "componentName"></keep-alive>標籤來實現


1、快取一個路由元件 —— 字串寫法

  • image
  • image
  • image
  • 方便驗證,加上如下的程式碼
    • image
    • image


2、快取多個路由元件 —— 陣列寫法

  • image
  • 演示就不做了



3.12.12、另外的生命鉤子
  • 在基礎篇中就說過:除了哪裡講的8個生命鉤子【 ps:4對 】,其實還有三個生命鉤子沒有玩,也說了等到路由之後再整


1、另一對生命鉤子

  • 這一對生命鉤子就是:activated 和 deactivated,其中:
    • activated 就是啟用 【 ps:我想見你了,就調它 】
    • deactivated 就是失活 【 ps:我不想見你了,你離開吧,就調它,有點類似於beforeDestroy這個鉤子,但是:處理的情況不一樣 】
    • 適用場景:提前使用了keep-alive include = "xxx"保留改元件,切換後不讓其銷燬【 ps:beforeDestroy不起作用了 】,那麼:又想要最後關掉定時器之類的,就可以使用這兩個鉤子函式
  • 例項:
    • image
    • image


2、另外一個鉤子函式就是nextTick

  • 這個東西不演示了,它是為了:讓解析時產生時間差,因為:有些東西需要把解析完之後的樣子插入到頁面中了才可以繼續做另外的事情,因此:就可以藉助nextTick,從而:讓一部分模板先被解析好,放入頁面中,然後再解析後面的一些東西時執行一些我們想要的邏輯,如:input框,有這麼一個場景,讓input渲染到頁面時,我滑鼠的焦點就在input框中,這就可以使用此鉤子函式,在掛載時呼叫此鉤子,然後把游標聚焦的邏輯放到nextTick回撥中
  • nextTick鉤子的玩法官網有,一看就懂
    • image



3.12.13、路由守衛 - 重要、開發常用
  • 所謂的路由守衛,就是許可權問題,即:擁有什麼許可權,才可以訪問什麼路由
3.12.13.1、全域性前置路由守衛
  • **所謂的全域性前置路由守衛,特點之一就體現在前置二字上,它的API是beforeEach( ( to, from, next ) => { } ),也就是:在路由切換之前回撥會被呼叫 / 初始化渲染時回撥會被呼叫 **
  • 例項:
    • image
    • image
    • 不方便演示,所以直接說,to就是切換路由元件之後的元件位置【 ps:即 去哪裡,目標元件 】;而from 是 切換路由元件之前的元件位置 【 ps:即 來自哪裡 從哪個路由元件來 】;另外 next是一個函式next(),就是放行的意思


  • 現在做一個操作,在瀏覽器中快取一個key-value,然後訪問News、Message路由時判斷key-value是否對得上,對就展示相應的路由元件,否則:不展示
    • image
    • image
    • 玩點小動作
      • image


  • 當然:前面的過程有一個小技巧可以簡化
    • image
  • 給路由規則新增一個meta配置項,就是路由後設資料,利用這個配置項,我們可以給路由中放一些我們想放的東西進去【 ps:哪個路由需要許可權驗證,就在哪個路由中加入meta配置項 】,那就改造吧
    • image
    • image
    • image



3.12.13.2、全域性後置路由守衛
  • 這玩意兒就和全域性前置路由守衛反著的嘛,但是:處理場景不一樣
  • 全域性後置路由守衛 呼叫的API是afterEach( ( to, from ) => { } ),注意:和全域性前置路由守衛相比,少了next引數,後置了嘛,都已經在前面把許可權判斷完了,你還考慮放不放行幹嘛,這個全域性後置路由守衛做的事情其實不是去判斷許可權問題,而是收尾,做一些過了許可權之後的判斷問題,比如:點選某個路由元件之後,只要可以檢視這個元件內容,那麼:就把網頁的頁籤標題給換了
  • 這個API是 初始化渲染時回撥會被呼叫 / 路由元件切換之後會被呼叫,


  • 例項:

    • image
    • 操練一手:【 ps: 先用純的全域性前置路由守衛來做 】
      • 先加點東西進去
        • image
      • image
      • image
      • 上面看起來成功了,但是有bug,可以試著把network中的網路調成slow 3G,可以稍微清楚地看到效果 ,想要改成功,就算把專案中 public/index.html的title改了,也是一樣,會有載入過程,因此:想在全域性前置路由守衛中達到想要的效果,改出花兒來也莫得辦法,而且在全域性前置路由守衛中寫兩遍一樣的程式碼根本不標準
        • image
        • image



  • 想要實現前面的效果,那就需要全域性後置路由守衛登場了,掉一下API,裡面一行程式碼搞定
    • image
    • 效果就不演示了,已經達到效果了【 ps:注意得把public/index.html中的title改成'大資料智慧雲生平臺',不然訪問根目錄時也有載入過程,這不是此知識點的鍋,因為:原生的public/index.html的title是讀取的package.json中第二行的name 】



3.12.13.3、獨享路由守衛
  • 這玩意兒就是指:某一個路由獨享的路由守衛,呼叫的API是:beforeEnter( ( to, from, next )=>{ } ),它是指:在進入配置這個API的路由之前回撥會被呼叫,其中:to、from、next的意思和前面全域性前置路由守衛一樣,但注意:這種沒有什麼後置之類的,它只有這一個前置,即:獨享路由守衛
  • 最後:路由守衛之間,是可以隨意搭配的


  • 例項:
    • image
    • image



3.12.14、路由器的兩種工作模式
  • 這兩種模式是:hash和history模式
    • hash模式 路由器的預設模式 就是路徑中有一個#,這#後面的內容不用隨http傳給伺服器,伺服器也不會收到【 ps:前端玩一下而已 】,注意:這個hash和後端中的hash演算法哪些東西不一樣啊,別搞混了
      • image
    • history模式 這個就好理解了嘛,就是沒有了那個#,然後路徑中ip:port之後的東西是會隨著http傳給伺服器的


  • hash和history兩種模式的區別:
    • 1、hash模式 路徑中有#,且#後的內容不會隨http傳給伺服器;而history模式 路徑中沒有# ip:port之後的東西會隨http傳給伺服器
    • 2、hash模式的相容性好,而history模式的相容性略差


  • 在路由器中hash和history兩種模式的切換 在路由規則中加個全新的配置項mode即可
    • image



3.12.15、關於專案上線的問題
  • 瞭解這個東西是因為前面說的hash和history的另一個區別,在上線時有個坑 / 注意點


1、編寫完了程式之後打包專案

  • 啟動專案一直用的是npm run serve,在腳手架時就說過還有一個命令:npm run build,那時說過:後端要的前面資源是HTML+CSS+JS,所以此時專案打包就需要用到它了
  • 先把路由器的工作模式切換成history模式,然後再打包,這樣方便演示bug
    • image
    • image


2、使用node+express框架編寫一臺小伺服器模擬一下上線

  • 自行新建一個資料夾,然後使用vscode開啟

  • 1)、讓資料夾變成合法包 指令:npm init

    • image
  • 2)、安裝express 指令:npm install express 注意:這一步很容易因為自己當初配置nodejs時操作不當,導致許可權不夠啊,就會報一堆warn和error

    • image
  • 3)、新建一個js檔案,編寫內容如下

    • image

    • 原始碼如下:

      •     // 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

      • image
    • 5)、訪問伺服器中的埠測試一下

      • image


3、準備工作弄完了,現在把剛剛使用npm run build打包的dist中的檔案複製到伺服器中去

  • 在伺服器中新建一個static / public資料夾【 ps:這兩個資料夾後端的人很熟悉了,就是SpringBoot中的那兩個,這裡就不解釋了,這兩個資料夾鍵哪一個都可以 】
  • image


4、讓複製進去的檔案能夠被伺服器認識

  • image
  • 重新執行node server 開始演示路由器的兩種工作模式的另一個坑【 ps:別忘記有個許可權認證啊,在快取把對應東西放上,不然有些路由元件點不了 】
    • image
    • 整bug,隨便點一些路由元件之後,重新整理頁面【 ps:只要保證路徑不是localhost:8001即可,讓它後面有點東西 】
      • image
      • 這就是history模式的坑,但是:切換成hash就不會出現這樣,出現上述的情況是因為:剛剛我們在頁面中隨便點路由元件都有頁面是因為:那些都是靜態頁面嘛,那些資料啊、歷史記錄啊都是原本就有的,即:不走網路請求,但是:重新整理之後,是走網路請求的,也就會把路徑中ip:port之後的東西隨著http發給伺服器了,它去伺服器找資源就是找http://localhost:8001/home/news中的/home/news,伺服器中那有這個資源,所以:404唄
      • 想要history模式也和hash一樣,重新整理不出錯,就需要找後端人員進行處理,需要後端人員配合你這邊拿過去的資源來做,後端處理這種問題的方式有很多,如:Nginx【 ps:但是嘛,有時別人甩你個錘子,最終程式碼出問題是自己背鍋罷了 】,所以:自己解決,需要藉助一個外掛 connect-history-api-fallback
        • image
        • image
        • image
        • image
        • image



3.13、結語

Vue2到此結束,另外還有一些Vue UI元件庫



接下來的技術點就是:Vue3

相關文章