Vue總結梳理(詳解生命週期函數/12種通訊方式/Vue路由/Vuex狀態管理/Vue全部知識點連串/配合源碼分析)

最全的(前端知識)彙總,以便自身複習

上一篇內容:React總結來講(通訊/redux/路由/鉤子函數/三大解決邏輯共享方案(組件))

因爲篇幅過於長,知識體系過於龐雜,請謹慎學習, 轉述的我都有放置連接(難理解的也放了demo測試或者圖)css

技術規範(基礎瞭解,大佬儘可跳過)html

Vue

每個DOM元素上的全部屬性成VNode這個對象上都存在對應的屬性。簡單的來講,vnode能夠理解成節點描述對象,它描述了應該怎麼去建立真實的DOM節點源碼前端

生命週期鉤子函數

講一下生命週期的hook

export function callHook (vm: Component, hook: string) {
                  const handlers = vm.$options[hook] // 獲取Vue選項中的生命週期鉤子函數
                  if (handlers) {
                    for (let i = 0, j = handlers.length; i < j; i++) {
                      try {
                        handlers[i].call(vm) // 執行生命週期函數
                      } catch (e) {
                        handleError(e, vm, `${hook} hook`)
                      }
                    }
                  }
                  if (vm._hasHookEvent) {
                    vm.$emit('hook:' + hook)
                  }
                }
複製代碼

Snipaste_2019-08-24_22-32-40.png

全部的生命週期鉤子自動綁定 this 上下文到實例中,所以你能夠訪問數據,對屬性和方法進行運算。這意味着你不能使用箭頭函數來定義一個生命週期方法vue

ss

建立一個Vue實例開始【new Vue()】看圖說話node

beforeCreate【beforeCreate鉤子函數前,函數時,函數後】

  • beforeCreatereact

    • beforeCreate以前:初始化事件【initEvents】和生命週期【initLifecycle 】提取源碼 //// 官方源碼。。。
      Snipaste_2019-08-24_13-58-41.png
      Snipaste_2019-08-24_14-08-17.png
    • beforeCreate鉤子函數這裏獲取不到data與el的
    • beforeCreate以後【created以前】:props 、methods【方法】 、data 、computed 和 watch 的初始化處理源碼官方源碼
      Snipaste_2019-08-24_14-26-29.png

created【created鉤子函數時,函數後】

  • createdgit

    • created時候:在此以前->屬性初始化以及屬性綁定,created鉤子函數這裏是能獲取到props 、methods 、data 、computed 和 watch的,可是獲取不到el(dom)github

    • created以後【beforeMount以前】:【簡單來講把tempalte編譯成render函數的過程】借鑑轉載 ||| 這邊用到DOM選項 算法

      Snipaste_2019-08-24_14-40-11.png

      • 判斷有沒有el【先檢查了是否存在渲染位置,在HTML頁面寫Vue實例的時候每一個實例都有el的緣由】,沒有el選項,則中止編譯,也就意味着中止了生命週期,直到在該vue實例上調用vm.$mount(el)掛載 vue-router

        ss

        vm.$mount(el) //這個el參數就是掛在的dom接點
        複製代碼

        sss

        • 存在el則就去判斷有無template【看最上面的官方圖解】--》有template輸出render函數模板,否者把外部HTML做爲模板編譯【template中的模板優先級要高於outer HTML的優先級
    • 示例

      <!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>vue生命週期學習</title>
             <script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script>
           </head>
           <body>
             <span id="app">
               <!--html中修改的-->
               <h1>{{message + '這是在outer HTML中的'}}</h1>
             </span>
           </body>
           <script>
             var vm = new Vue({
               el: '#app',
               template: "<h1>{{message +'這是在template中的'}}</h1>", //在vue配置項中修改的
               data: {
                 message: 'Vue的生命週期'
               }
           </script>
           </html>
      複製代碼

      template替換了外部的HTML做爲模板

      ss

      註釋掉template

      ss

      el的判斷要在template以前了~是由於vue須要經過el找到對應outer template【外部的HTML】

    • 最後談一談render函數【el與template都存在的時候,再判斷是否有render函數】render官方文檔

      Vue 選項中的 render 函數若存在,則 Vue 構造函數不會從 template 選項或經過 el 選項指定的掛載元素中提取出的 HTML 模板編譯渲染函數【render函數選項 > template選項 > outer HTML】

      Snipaste_2019-08-24_15-17-45.png

      • 借用上面的例子【render直接顯示render函數裏的】

        new Vue({
                       el: '#app',
                       render: function(createElement) {
                           return createElement('h1', 'this is createElement')
                       }
                   })
        複製代碼

        ss

beforeMount【beforeMount鉤子函數時,函數後】

  • beforeMount

    • beforeMount的時候:$el【created與beforeMount】和data、props 、methods 、data 、computed 和 watch【created以前】都已初始化

    • beforeMount以後【mounted以前】:(官方圖解)【建立 vm.$ el(裏面的全部節點)替換el(只是一個節點)選項】 源碼 這裏能看到執行了一次render函數【生成真實DOM】

      // 咱們在觀察者的構造函數中將其設置爲vm._watcher
                 //由於觀察者的初始補丁可能會調用$ forceUpdate(例如,在子組件的掛載掛鉤內)
                 //這依賴於已定義的vm._watcher
                  new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */);
                  hydrating = false;
      
                  // 手動掛載的實例,在本身掛載的調用掛載在其插入的掛鉤中爲渲染建立的子組件調用
                  if (vm.$vnode == null) {
                    vm._isMounted = true;
                    callHook(vm, 'mounted');
                  }
      複製代碼

      Snipaste_2019-08-24_16-02-21.png

      都會調用_render()函數_render函數源碼,而後生成真實DOM【看到源碼,當數據變化(更新鉤子函數)的時候還會回來執行而後_render函數】

mounted【mounted鉤子函數時,函數後】

  • mounted

    • mounted的時候:在此以前依然生成了真實DOM($el一樣掛載到實例上),因此大部分的數據請求(也能夠在created,那個時候方法屬性已經初始化)都在這裏(由於真實Dom依然存在)

    注意 mounted 不會承諾全部的子組件也都一塊兒被掛載。若是你但願等到整個視圖都渲染完畢,能夠用 vm.$nextTick 替換掉 mounted 文檔

    mounted: function () {
                this.$nextTick(function () {
                  // Code that will run only after the
                  // entire view has been rendered
                })
              }
    複製代碼
    • 談一談vm.$nextTick( [callback] )【Vue.nextTick相似 文檔

      [瞭解Event Loop](https://juejin.im/post/5d54225ce51d4561fd6cb4c3#heading-12)
      複製代碼

      在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM

      methods: {
                   // ...
                   example: function () {
                     // 修改數據
                     this.message = 'changed'
                     // DOM 尚未更新
                     this.$nextTick(function () {
                       // DOM 如今更新了
                       // `this` 綁定到當前實例
                       this.doSomethingElse()
                     })
                   }
                 }
      複製代碼

Vue實例周期函數到這裏爲止,坐等數據變化

beforeUpdate【beforeUpdate鉤子函數時,函數後】

  • beforeUpdate

    • 瞭解Vue是如何追蹤變化源碼如何追蹤變化 //// 白話講解 【三個文件:observer文件夾下的index.js是最關鍵的,提供了defineReactive方法(對definePrototype的封裝),給每個data掛上get/set || dep.js文件【通知哪個依賴數據被改變】通知watcher實例收集依賴以及從新渲染 || watcher.js文件提供了watcher實例(添加data依賴方法) 】

      • 每一個組件實例都對應一個 watcher 實例(生命週期函數的時候被建立),它會在組件渲染的過程當中把「接觸」過的數據屬性記錄爲依賴(源碼)

      • data在初始化的時候源碼defineReactive 135行,Vue 對象屬性 data 的屬性會被 reactive 化,即會被設置 getter 和 setter 函數【對於全部被 Vue reactive 化的屬性來講都有一個 Dep 對象與之對應】。

    • beforeUpdate時:vue 實例的 _isMounted 爲 true 的話,直接調用 beforeUpdate 鉤子【以前發生的確定是data變化】

      Vue.prototype._update = function (vnode, hydrating) {
                    var vm = this;
                    if (vm._isMounted) {
                      callHook(vm, 'beforeUpdate');
                    }
                };
      複製代碼
    • beforeUpdate以後:執行_render函數,組件從新渲染

      function callUpdatedHooks (queue) {
                  var i = queue.length;
                  while (i--) {
                    var watcher = queue[i];
                    var vm = watcher.vm;
                    if (vm._watcher === watcher && vm._isMounted) {
                      callHook(vm, 'updated');
                    }
                  }
                }
      複製代碼

updated【updated鉤子函數時】

  • updated :data的數據已經更新完畢【視圖渲染完畢】

beforeDestroy與destroyed

更新鉤子函數結束了,beforeDestroy與destroyed不觸發是不會執行編譯的【如同el同樣,沒有的話也是不能編譯下去的】

Snipaste_2019-08-24_21-14-18.png

觸發方式爲:vm.$destroy()--》徹底銷燬一個實例。清理它與其它實例的鏈接,解綁它的所有指令及事件監聽器(文檔

在大多數場景中你不該該調用vm.$destroy()這個方法。最好使用 v-if(源碼解讀) 和 v-for 指令以數據驅動的方式控制子組件的生命週期。

beforeDestroy

  • beforeDestroy時:檢測被銷燬了,這裏還能訪問到Vue實例,能夠在這裏移除本身的定時器【在此以前就是監聽到組件被銷燬】

    Vue.prototype.$destroy = function () {
              var vm = this;
              if (vm._isBeingDestroyed) {
                return
              }
              callHook(vm, 'beforeDestroy');
          };
    複製代碼

在卸載前,檢查是否已經被卸載,若是已經被卸載,就直接 return 出去

  • beforeDestroy後【destroyed前】: 執行了一系列的銷燬動做,包括從 parent 的 $children 中刪掉自身,刪除 watcher,當前渲染的 VNode 執行銷燬鉤子函數等,執行完畢後再調用 destroy 鉤子函數【Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬】

    Vue.prototype.$destroy = function () {
                  vm._isBeingDestroyed = true;
                  // 從父級那裏刪除本身
                  var parent = vm.$parent;
                  if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
                    remove(parent.$children, vm);
                  }
                  // 拆解觀察者
                  if (vm._watcher) {
                    vm._watcher.teardown();
                  }
                  var i = vm._watchers.length;
                  while (i--) {
                    vm._watchers[i].teardown();
                  }
                  // 從凍結對象的數據中刪除引用可能沒有觀察者。
                  if (vm._data.__ob__) {
                    vm._data.__ob__.vmCount--;
                  }
                  // 準備執行最後一個鉤子
                  vm._isDestroyed = true;
                  // 在當前渲染的樹上調用destroyed hook
                  vm.__patch__(vm._vnode, null);
    
                  callHook(vm, 'destroyed');
                  // turn off all instance listeners.
                  vm.$off()
                  // remove __vue__ reference
                  if (vm.$el) {
                    vm.$el.__vue__ = null
                  }
                  // release circular reference (#6759)
                  if (vm.$vnode) {
                    vm.$vnode.parent = null
                  }
                }
              }
    複製代碼

destroyed【銷燬完畢狀態】

銷燬完畢

父組件是沒有能力去檢測本身的銷燬的 父組件進行v-if或者v-for操做,子組件就能檢測本身是否被銷燬

銷燬完結了一個實例的生命週期

activated/deactivated:當組件激活/停用的時候調用( -----keep-alive 組件激活/停用時調用。Tab切換 :is)

<button @click="selectComponent='app-one'">one</button>
            <button @click="selectComponent='app-two'">two</button>
            <button @click="selectComponent='app-there'">there</button>
            <button @click="selectComponent='app-four'">four</button>
            <component :is="selectComponent"></component>

            同上接着講點擊按鈕以後就會銷燬上一個實例,即每次都會重置(影響性能的優化)
            即 ======包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們
            <keep-alive>
                <component :is="selectComponent"></component>
            </keep-alive>

            當組件在 <keep-alive> 內被切換,
            它的 activated 和 deactivated 這兩個生命週期鉤子函數將會被對應執行
複製代碼
  • keep-alive:主要用於保留組件狀態或避免從新渲染,包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們【當組件在 keep-alive 內被切換,它的 activated 和 deactivated 這兩個生命週期鉤子函數將會被對應執行。】

errorCaptured:當捕獲一個來自子孫組件的錯誤時被調用。此鉤子會收到三個參數:錯誤對象、發生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子能夠返回 false 以阻止該錯誤繼續向上傳播。

通訊(12種通訊方式)

父-->子【傳遞props、經過經過v-model傳遞、借用 children或者refs操做子組件實例)】

父向子傳遞數據經過props

  • 父組件

    <app-header :msgs="msg" ></app-header>
              
              data(){
                  return{
                    msg:"Welcome to Hello World"
                  }
                }
    複製代碼
  • 子組件

    props:{
                      msgs:{
                          type:String,         或者      props:["msgs"]
                          required:true
                          }
                      }
              <p class="lead">{{msgs}}</p>---------->直接調用
    複製代碼

經過特殊的v-model傳遞

  • 父組件

    <child v-model="message"></child>
    
                data() {
                  return {
                    message: 'hello'
                  }
                }
    複製代碼
  • 子組件:藉助v-model=v-bind:value+v-on:input原理

    <template>
                  <span>
                    <input type="text" v-model="mymessage" @change="changeValue">
                  </span>
             </template>
    
            <script>
              
              export dufault{
                props: {
                      value: String, // v-model 會自動傳遞一個字段爲 value 的 props 屬性
                    }
              data() {
                  return {
                    mymessage: this.value
                  }
               },
              methods: {
                 changeValue() {
                    this.$emit('input', this.mymessage); //經過如此調用能夠改變父組件上 v-model 綁定的值
                  }
                }
            }
            </script>
    複製代碼

遵照數據自上而下單向數據流的原則

即子組件沒有能力修改父組件裏面私有屬性的能力

Snipaste_2019-08-24_15-48-36.png

  • 父-->子

    • 父組件【$children 並不保證順序,也不是響應式的】

      methods: {
            change() {
              this.$children[0].aaa = "sssss";
              console.log(this, this.$children[0].aaa);
            }
      }
      複製代碼
    • 子組件

      data() {
            return {
              aaa: "ssssssssss"
            };
          }
      複製代碼

子-->父【傳遞function、發佈訂閱】

子組件向父組件傳遞分爲兩種類型。

  1. 經過傳遞屬性function
  2. 經過訂閱發佈發送數據
  • 經過傳遞屬性function

    • 父組件

      <app-footer :foot="footer"></app-footer>
      
         methods:{
            footer(val){//val其實就是$event
              // console.log(val);
              this.msg=val; //獲取
            }
          }
      複製代碼
      • 子組件

        <button @click="send">子傳遞父</button>--》傳遞條件,也就是點擊以後傳遞數據
        
        //子組件監聽,對function:footer進行監聽
        props:{
           foot:{
               type:Function,         或者      props:["foot"]
              }
        }
        
        //最後傳參,值傳遞回去
        methods:{
               send(){
                   this.foot(this.year)
               }
           }
        複製代碼
  • 發佈訂閱模式

    • 父組件

      <app-header @change="datas"></app-header>--->//監聽事件--@(監聽的是change)
            //監聽到後調用的事件
            methods:{
                datas(val){
                      // this.$on("ch")
                      console.log(val)
                    }
                 }
      複製代碼
    • 子組件

      created () {
                // 在須要的傳遞數據的時候調用sendData方法,這裏模擬調用
                //調用這個方法,即(created的時候)何時把數據傳遞給父組件
                this.sendData();
            }
         //方法
        methods:{
            sendData ()
            //經過$emit
            this.$emit("change","我這就改變") //自身寫的事件change,XXX是傳遞的值
             }
          }
            
            --------------------------------------------------------------
             (2)調用事件-------同上
               <a @click="status" href="#" role="button">Learn more</a>
               methods:{
                    status(){
                        this.$emit("change","改變冒險")
                    }
                }
      複製代碼

父-->'孫'(2.4.0新增的 attrs 與 $listeners)

【獨特狀況--->A、B、C組件,A是B的父親,B是C的父親】,其餘狀況不適用

Snipaste_2019-08-22_20-36-32.png

Vue 2.4.0新增 attrs 與 listeners解決A->B->C,這跨一層組件通訊的臃腫問題

跟props關係密切

  • $attrs: 繼承全部的父組件屬性(除了prop傳遞的屬性、class 和 style )

  • A組件

    • 傳遞屬性與事件給B組件(子組件)

    • 傳遞倆個屬性messagea與message

    • 傳遞倆個事件getData與getChildData

  • B組件

    • props只接受messagea,messagea就是HelloABC,$attrs就爲{「message」:「Hello」}
    • props不接受任何父元素的屬性【官方文檔所說的不聲明任何的prop】,attrs的值就爲{「messagea」:「HelloABC」,「message」:「Hello」},能夠經過v-bind=" $attrs " 傳遞給組件C
    • listeners正如官方所說的,包含了父組件(組件A)【(不含 .native 修飾器的)】的v-on監聽的全部事件,能夠經過v-on=" $listeners "傳遞給組件C
    • 題外話(嘗試了API裏的全部實例屬性)--》mounted鉤子函數裏:this.xxx

      • (接收了父組件的props),$props能獲取到{「messagea」:「HelloABC" }

      • $el能獲取到當前示例的DOM元素【B組件的Dom元素】

      • $options能獲取到當前實例的自定義屬性(好比組件的名字)

      • $parent能獲取父實例【A組件】

      • $root能獲取到父實例【A組件】,若是沒父實例,實例就是本身【B組件】

      • $children能獲取到全部的直接子組件(不保證順序),子組件包括(router-link)這一類路由組件

      • $refs能獲取到ref註冊的組件實例

      • $slots能獲取到被分發的(有具體槽名)的VNode節點

        • 子組件(error.vue)

          • 子組件能夠經過 this.$slots獲取到父組件裏面被使用的插槽

          • 若是該插槽沒有被使用(例如父組件沒使用插槽cc,cccccc會出如今父組件的app-error裏面),也就是會被子組件替代;不然會出現(slot爲cc的內容代替子組件的插槽name爲cc的內容)

            <slot name="aa">aaaaaa</slot>
              <slot name="bb">bbbbbb</slot>
              <slot name="cc">cccccc</slot>
            複製代碼
        • 父組件

          <app-error v-bind="$attrs" v-on="$listeners">
                  <span slot="aa"></span>
                  <span slot="bb"></span>
                  <span slot="cc"></span>
                </app-error>
          
                import error from "error.vue";
          
                components: {
                    "app-error": error
                  }
          複製代碼
      • scopedSlots能獲取到相對應的$slots的VNode 的函數

        //B組件
          <template>
           <span class="B">
               <p>props: {{messagea}}</p>
               <p>$attrs: {{$attrs}}</p>
           <hr>
           <!-- C組件中能直接觸發A組件的事件是由於v-on="$listeners"把父組件V-on監聽的全部事件傳遞回去 -->
           <!-- 經過v-bind 綁定$attrs屬性,C組件能夠直接獲取到A組件中傳遞下來的props(除了B組件中props聲明的) -->
           <app-c v-bind="$attrs" v-on="$listeners"></app-c>
           </span>
          </template>
          <script>
           import C from './C.vue';
           export default {
               props: ['messagea'],
               components: { 'app-c':C },
               data () {
                   return {};
               },
               inheritAttrs: false,
               mounted () {
                   this.$emit('getData');
               }
           };
          </script>
        複製代碼
  • C組件

    • B組只獲取messagea的props,所以attr會把message傳遞下來【致使 $attrs爲undefind】

    • listeners把父組件的事件傳遞下來,使用 $emit觸發事件

      <template>
                  <span class="C">
                      <p>props: {{message}}</p>
                      <p>$attrs: {{$attrs}}</p>
                  </span>
             </template>
             <script>
              export default {
              props: ['message'],
              inheritAttrs: false,
                  mounted () {
                      this.$emit('getChildData');
                  }
              };
             </script>
      複製代碼
  • 怎麼傳遞B組件以聲明的props或者B組件自身的屬性呢?

    • 需求:爲了達到把B組件自身的屬性或者**以聲明的props(messagea)**傳遞給C組件----->能夠綁定屬性messagec實現,可是會引起一個值得思考的問題【C組件的$attrs的值究竟是什麼了,是message仍是messagec,亦或者都有】

      <app-c v-bind="$attrs" v-on="$listeners" :messagec="messagec"></app-c>
      複製代碼
      • 先說結論

        1. C組件不聲明props,attrs值爲{"messagec":messagec,"message":message}【複合體】--- $props值爲undefind
        2. C組件props聲明messagec,attrs值爲{"message":message}--- $props值爲{"messagec":messagec}
        3. C組件props聲明message,attrs值爲{"messagec":messagec}--- $props值爲{"message":message}
        4. C組件隨意聲明props(xxx),attrs值爲{"messagec":messagec,"message":message}【複合體】--- $props值爲{xxx:undefind}
      • 總結

        1. 子組件無論props有無聲明,父組件綁定攜帶的值都會經過【v-bind="$attrs"】(父組件已經聲明過的props除外)來到 子組件
        2. 子組件聲明瞭相對應的props,相對應的值會給到props,其他的值就會給到attrs,符合前面所說的 $attrs定義
        3. 可是這不是咱們想要的
  • 最後談一談inheritAttrs$attrs的問題【inheritAttrs默認值爲true,能解決默認行爲(將會「回退」且做爲普通的 HTML 特性應用在子組件的根元素上)】----inheritAttrs默認爲true【這個選項不影響 class 和 style 綁定】實例說話

    • 父組件

      <app-error v-bind="$attrs" v-on="$listeners" :messagec="messagec">
      複製代碼
    • 子組件

      props: ["message"]
      複製代碼

      Snipaste_2019-08-23_15-03-20.png

      props: ["messagec"]
      複製代碼

      Snipaste_2019-08-23_15-10-04.png

    • 實驗結果【inheritAttrs爲true的基礎上】

      • 組件內未被註冊的屬性將做爲普通html元素屬性被渲染
      • 子組件設置inheritAttrs爲false會阻止這種行爲(難以維護組件的乾淨)
    • 格外補充inheritAttrs的問題

      • 父組件

        <app-error type="text">
        複製代碼
      • 子組件

        <template>
              <h1 type="number">
           </template>
        複製代碼
      • 總結:默認狀況下父組件的text會覆蓋number,爲了不這種(默認行爲,父做用域的不被認做 props 的特性綁定 (attribute bindings) 將會「回退」且做爲普通的 HTML 特性應用在子組件的根元素上)狀況inheritAttrs設置爲false

跨組件多級通訊【provide 與 inject、中央事件總線 EventBus、broadcast以及 $dispatch、Vuex】

  • provide 與 inject

    provide 與 inject這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。若是你熟悉 React,這與 React 的上下文特性很類似。

    • 父組件(父組件提供provide)

      <script>
                     export default {
                     provide: {
                          message:'messageaaa'
                        }
                     };
                 </script>
      複製代碼
    • 子組件(無論多深的子組件)進行inject注入

      <script>
                     export default {
                         inject: ["message"], // 獲得父組件傳遞過來的數據
                     };
                 </script>
      複製代碼

    provide 和 inject 主要爲高階插件/組件庫提供用例。並不推薦直接用於應用程序代碼中

    提示:provide 和 inject 綁定並非可響應的。這是刻意爲之的。然而,若是你傳入了一個可監聽的對象,那麼其對象的屬性仍是可響應的

    • 中央事件總線 EventBus

    暴露一個Vue實例,全部的通訊經過這個總站EventBus進行操做【實例方法vm . emit與vm .on】

    • 入口文件(暴露實例)

      export const Bus =new Vue();
      複製代碼
    • 父組件

      import {Bus} from "xxxx"
            
                <a  @click="status" href="#" role="button">Learn more</a>
                
                methods:{
                        status(){
                            Bus.$emit("change","改變冒險")//數據給Bus總站
                        }
                    }
                    
      注意參數【第一個是事件名、第二個數組】
      複製代碼

      Snipaste_2019-08-23_16-19-33.png

    • 非父子組件(監聽事件名)接收消息

      created(){ //生命週期鉤子函數裏調用,有個回調
                    Bus.$on("change",data=>{
                        // console.log(data);
                        this.year=data;
                    })
                }
      複製代碼
  • Vue2.x刪除了一對跨組件通訊的api【 broadcast(廣播)方法以及dispatch方法】,Element地址與Iview地址又從新實現了該方法

須要用到的時候混入該方法

function broadcast(componentName, eventName, params) {
          /*遍歷當前節點下的全部子組件*/
          this.$children.forEach(child => {
            /*獲取子組件名稱*/
            var name = child.$options.componentName;

            if (name === componentName) {
              /*若是是咱們須要廣播到的子組件的時候調用$emit觸發所需事件,在子組件中用$on監聽*/
              child.$emit.apply(child, [eventName].concat(params));
            } else {
              /*非所需子組件則遞歸遍歷深層次子組件*/
              broadcast.apply(child, [componentName, eventName].concat([params]));
            }
          });
        }
        export default {
          methods: {
            /*對多級父組件進行事件派發*/
            dispatch(componentName, eventName, params) {
              /*獲取父組件,若是以及是根組件,則是$root*/
              var parent = this.$parent || this.$root;
              /*獲取父節點的組件名*/
              var name = parent.$options.componentName;

              while (parent && (!name || name !== componentName)) {
                /*當父組件不是所需組件時繼續向上尋找*/
                parent = parent.$parent;

                if (parent) {
                  name = parent.$options.componentName;
                }
              }
              /*找到所需組件後調用$emit觸發當前事件*/
              if (parent) {
                parent.$emit.apply(parent, [eventName].concat(params));
              }
            },
            /*
                向全部子組件進行事件廣播。
                這裏包了一層,爲了修改broadcast的this對象爲當前Vue實例
            */
            broadcast(componentName, eventName, params) {
              broadcast.call(this, componentName, eventName, params);
            }
          }
        };
複製代碼
  • 其實這裏的broadcast與dispatch實現了一個定向的多層級父子組件間的事件廣播及事件派發功能。完成多層級分發對應事件的組件間通訊功能。

  • broadcast經過遞歸遍歷子組件找到所需組件廣播事件,而dispatch則逐級向上查找對應父組件派發事件。

  • broadcast須要三個參數,componentName(組件名),eventName(事件名稱)以及params(數據)。根據componentName深度遍歷子組件找到對應組件emit事件eventName。

  • dispatch一樣道理,須要三個參數,componentName(組件名),eventName(事件名稱)以及params(數據)。根據componentName向上級一直尋找對應父組件,找到之後emit事件eventName。

  • dispatch是向上派發事件(跨級)

    • 子組件

      <a @click="rrr">派發</a>
            
            
            import emitter from "xxxxx";
            export default {
               mixins: [emitter],
               methods: {
                    rrr() {
                       this.dispatch("appError", "changes", "大撒大撒");
                       console.log(this.$parent.$parent);
                     }
              }
      複製代碼
    • 父組件

      export default {
            componentName: "appError"
              created() {
                this.$on("changes", data => {
                  console.log(data);
                });
              }
            }
      複製代碼
  • broadcast(廣播)向下廣播事件(跨級)

    • 父組件

      <a @click="rrr">廣播</a>
      
          import emitter from "xxxxx";
          export default {
              mixins: [emitter],
              methods: {
                       rrr() {
                          this.broadcast("appError", "changes", "大撒大撒");
                          console.log(this.$children[0].$children[0]);
                        }
                 }
            }
      複製代碼
    • 子組件

      export default {
           componentName: "appError"
             created() {
               this.$on("changes", data => {
                 console.log(data);
               });
             }
           }
      複製代碼
  • 多頁面應用請使用Vuex

特殊通訊 【利用路由傳參或者本地存儲localstorage】

Vuex

建立store

Vuex 使用單一狀態樹——是的,用一個對象就包含了所有的應用層級狀態 至此它便做爲一個「惟一數據源 (SSOT)」而存在。這也意味着,每一個應用將僅僅包含一個 store 實例

  • 導入store

    import  Vuex from 'Vuex'
              import {store} from 'xxxx'//引入
    
              new Vue({
                el: '#app',
                store,//------引入
                render: h => h(App)
              }).$mount("#app");
    複製代碼
  • 生成store

    • use過程當中,Vue會調用到Vuex的install函數499行源碼

      import Vue from 'vue'
            import  Vuex from 'Vuex'
      
            Vue.use(Vuex);
      
            export const store=new Vuex.Store({
             //初始化數據
              state:{},
            //獲取state數據【在這裏數據能夠作一步處理】
            //就像計算屬性computed同樣,getter 的返回值會根據它的依賴被緩存起來
            //且只有當它的依賴值發生了改變纔會被從新計算
              getters:{},
            //修改state數據
              mutations:{},
           // 異步修改數據
              actions:{}
            });
      複製代碼
  1. Vuex 經過 store 選項,提供了一種機制將狀態從根組件「注入」到每個子組件中(需調用 Vue.use(Vuex))
  2. 經過在根實例中註冊 store 選項**,該 store 實例$store** 訪問到會注入到根組件下的全部子組件中,且子組件能經過this.

state

一種狀態一種容器,存放項目中須要定義所有的全局變量,多頁面應用共享狀態

  1. Vuex 的狀態存儲是響應式的。當 Vue 組件從 store 中讀取狀態的時候,若 store 中的狀態發生變化,那麼相應的組件也會相應地獲得高效更新。
  2. 你不能直接改變 store 中的狀態。改變 store 中的狀態的惟一途徑就是顯式地提交(commit) mutations。這樣使得咱們能夠方便地跟蹤每個狀態的變化,從而讓咱們可以實現一些工具幫助咱們更好地瞭解咱們的應用

再次強調,咱們經過提交 mutation 的方式,而非直接改變 store.state.count,是由於咱們想要更明確地追蹤到狀態的變化。這個簡單的約定可以讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變

  • store

    import Vue from 'vue'
              import  Vuex from 'Vuex'
    
              Vue.use(Vuex);
    
              export const store=new Vuex.Store({
               //初始化數據
                state:{
                   count:null
                },
              });
    複製代碼
  • 組件

    computed: {
              count () {
                return this.$store.state.count
              }
            }
    複製代碼
  • mapState 輔助函數【官方解釋:幫助咱們生成計算屬性,讓你少按幾回鍵】

    1. 須要計算其餘的屬性,方法一樣能夠往mapState裏面

    2. 能夠提取mapState獲取到store的依賴計算的屬性

      //官方示例
        // 在單獨構建的版本中輔助函數爲 Vuex.mapState
       import { mapState } from 'vuex'
       export default {
         // ...
         computed: mapState({
           // 箭頭函數可以使代碼更簡練
           count: state => state.count,
      
           // 傳字符串參數 'count' 等同於 `state => state.count`
           countAlias: 'count',
      
           // 爲了可以使用 `this` 獲取局部狀態,必須使用常規函數
           countPlusLocalState (state) {
             return state.count + this.localCount
           }
         })
       }
      複製代碼
  • mapState爲何能等於computed【簡寫】

    1. computed是一個對象
    2. mapState 函數返回的是一個對象
  • 若是this.xxx與this.$store.state.xxx【this.count 爲 store.state.count】相同,能夠簡寫使用字符串

    computed: mapState([
           // 映射 this.count 爲 store.state.count
           'count'
         ])
    複製代碼
    • 若是須要把最終對象傳給 computed 屬性

      computed: {
           localComputed () { /* ... */ },
           // 使用對象展開運算符將此對象混入到外部對象中
           ...mapState({
             // ...
           })
         }
      複製代碼

getter

  • 用處

Vuex 容許咱們在 store 中定義「getter」(能夠認爲是 store 的計算屬性)。就像計算屬性同樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被從新計算

  • Getter 接受 state 做爲其第一個參數【經過state.xxx隨意獲取state上的狀態】

    const store = new Vuex.Store({
               state: {
                 count:[5,1,8,6]
               },
               getters: {
                 Todos: state => {
                   return state.count.filter(todo => todo>5)
                 }
               }
             })
    複製代碼
  • Getter 也能夠接受其餘 getter 做爲第二個參數【經過getters.xxx隨你獲取本身身上的全部計算屬性】

    getters: {
              // ...
              TodosCount: (state, getters) => {
                return getters.Todos.length
              }
            }
    複製代碼
  • 組件

    computed: {
               TodosCount () {
                  return this.$store.getters.TodosCount
                 }
               }
    複製代碼

特殊技巧:(傳參)經過方法訪問

getTodoById: (state) => (id) => {
        return state.todos.find(todo => todo.id === id)
      }

    store.getters.getTodoById(2)//傳遞參數
複製代碼
  • mapGetters輔助函數:store 中的 getter 映射到局部計算屬性【藉助...三點展開運算符】

    <template>
                  <span>
                      result:{{doubleCount}}
                      result:{{stringCount}}
                  </span>
              </template>
    
              <script>
              //mapGetters 輔助函數僅僅是將 store 中的 getter 映射到局部計算屬性:
              import {mapGetters} from 'vuex'
              export default {
                  computed:{
                      //...三點展開運算符將一個數組轉爲用逗號分隔的參數序列。
                      ...mapGetters(['doubleCount','stringCount']),
                  }
              }
              </script>
    複製代碼

mutation

更改 Vuex 的 store 中的狀態的惟一方法是提交 mutation【同步事件】

  • 接受 state 做爲第一個參數

    mutations: {
          increment (state) {
            // 變動狀態
            state.count++
          }
        //另外一種寫法
        increment:(state) =>{
            // 變動狀態
            state.count++
          }
        }
    複製代碼
  • 須要觸發一個類型爲 increment 的 mutation 時,調用此函數【相應的type 調用 store.commit 方法】

    store.commit('increment')
    複製代碼
  • 能夠傳入額外的載荷(第二個參數)【大多數狀況下,載荷應該是一個對象,包含多個字段而且記錄的 mutation 會更易讀】

對象風格的提交方式

store.commit('increment', 10)---->所以store.commit('increment', {amount: 10})

    --------------------------------------------------
    // ...
    mutations: {
      increment (state, n) {
        state.count += n
      }
       //----->所以
     increment (state, payload) {
        state.count += payload.amount
      }
    }
複製代碼
  • 使用常量替代 Mutation 事件類型用不用常量取決於你——在須要多人協做的大型項目中,這會頗有幫助。但若是你不喜歡,你徹底能夠不這樣作

  • 組件

    methods: {
          increment(){
            this.$store.commit('increment')  
          }
        }
    複製代碼
  • mapMutations輔助函數

    methods: {
          ...mapMutations(['increment']) //this.increment=store.mutations.increment的時候
        }
    複製代碼

action

Action 提交的是 mutation,而不是直接變動狀態【所以他必然擁commit,在什麼事件以後去觸發commit的同步事件】 Action 能夠包含任意異步操做

  • Action 函數接受一個與 store 實例具備相同方法和屬性的 context 對象【介紹到 Modules 時,你就知道 context 對象爲何不是 store 實例自己了】

    actions: {
              increment (context) {
                context.commit('increment')//context.state 和 context.getters
              }
            }
    複製代碼
    • 解構簡化寫法

      actions: {
          increment ({ commit }) {
            commit('increment')
          }
        }
      複製代碼
  • 異步事件

    actions: {
                incrementAsync ({ commit }) {
                  setTimeout(() => {
                    commit('increment')
                  }, 1000)
                }
              }
    複製代碼
  • 支持載荷payload

    actions: {
              // 頁碼約束的全部用戶數據
              List({commit},payload) {
                    setTimeout(() => {
                    commit('increment',payload.amount)
                  }, payload.time)
              }
          }
    複製代碼
  • 組件

    methods: {
         increment(){
           this.$store.dispatch('increment');
         }
       }
    複製代碼
  • mapActions輔助函數

    methods: {
                  // increment(){
                  //     this.$store.dispatch('increment')
                  // },,
                  ...mapActions(['increment'])
                }
    複製代碼
  • 組合寫法

    • promise

      actions: {
              actionA ({ commit }) {
                return new Promise((resolve, reject) => {
                  setTimeout(() => {
                    commit('someMutation')
                    resolve()
                  }, 1000)
                })
              }
            }
      
            ---------------------------------------------------
      
            store.dispatch('actionA').then(() => {
                  // ...
                })
      
            ----------------------------------------------------
             //組件
            this.$store.dispatch('actionA').then(() => {
                  // ...
                })
      複製代碼
    • 調用另外一個action

      actions: {
              // ...
              actionB ({ dispatch, commit }) {
                return dispatch('actionA').then(() => {
                  commit('increment')
                })
              }
            }
      複製代碼
      • 利用 async / await

        actions: {
              async actionA ({ commit }) {
                commit('gotData', await getData())
              },
              async actionB ({ dispatch, commit }) {
                await dispatch('actionA') // 等待 actionA 完成
                commit('gotOtherData', await getOtherData())
              }
            }
        複製代碼

一個 store.dispatch 在不一樣模塊中能夠觸發多個 action 函數。在這種狀況下,只有當全部觸發函數完成後,返回的 Promise 纔會執行。

Module

const base={
                      state:{},
                      getters:{},
                      mutations:{},
                      actions:{}
                    };

            export default base;

            ------------------------------------


           export default new Vuex.Store({
                  modules: {
                    base
                  }
              });
複製代碼
  • 設置模塊module,局部模塊以後能夠經過rootState獲取根狀態【getters與actions均可以】

    const base = {
                // ...
                getters: {
                  sumWithRootCount (state, getters, rootState) {
                    return state.count + rootState.count
                  }
                }
                actions: {
                  incrementIfOddOnRootSum ({ state, commit, rootState }) {
                    if ((state.count + rootState.count) % 2 === 1) {
                      commit('increment')
                    }
                  }
                }
              }
    複製代碼
  • 繁瑣的訪問命名空間,添加 namespaced: true 的方式使其成爲帶命名空間的模塊

爲了分割臃腫的store,Vuex 容許咱們將 store 分割成模塊

熱重載store.hotUpdate(),使 action 和 mutation 成爲可熱重載模塊

Vue-Router

建立路由

基本方法源碼地址

  • 建立路由

    import Router from "vue-router";
    
    
      Vue.use(Router);
    
      export default new Router({
          routes: []
          })
    複製代碼
  • 路由導入Vue實例

    import  Vuex from 'Vuex'
              import router from 'xxxx'//引入
    
              new Vue({
                el: '#app',
                router,
                render: h => h(App)
              }).$mount("#app");
    複製代碼

匹配路由

  • 動態路徑參數 以冒號開頭(上面通訊的傳參)

    { path: '/user/:id', component: User }
    複製代碼

當匹配到一個路由時,參數值會被設置到 **this.route.params**,能夠在每一個組件內經過this.route.params獲取參數

Snipaste_2019-08-25_17-28-10.png

/user/foo 導航到 /user/bar,原來的組件實例會被複用(渲染同個組件,比起銷燬再建立,複用則顯得更加高效)【意味着組件的生命週期鉤子不會再被調用】-------watch (監測變化) $route 對象

watch: {
            $route(newVal,oldVal){
            this.datas=newVal.params.id
            // console.log(newVal);
        }
      }
複製代碼
  • 經過 /search?q=vue傳遞參數

    { path: '/search', component: Search }
    複製代碼

    當匹配到一個路由時,參數值會被設置到 **this.route.query**,能夠在每一個組件內經過this.route.query獲取參數

  • 匹配全部路由

    {
                // 會匹配全部路徑
                path: '*'
              }
              {
                // 會匹配以 `/user-` 開頭的任意路徑
                path: '/user-*'
              }
    複製代碼
    • 設置404【redirect重定向,alias別名】(含有通配符的路由應該放在最後

      「別名」的功能讓你能夠自由地將 UI 結構映射到任意的 URL,而不是受限於配置的嵌套路由結構

      {path: '*', redirect: '/404' , alias: '/404'}
      複製代碼
  • 高級匹配

嵌套組件以及嵌套路由

{ path: '/user', 
     components:{
                default:User,
                'head-bottom':Collapse
            },
      children: [
        {
          // 當 /user/:id 匹配成功,
          // UserProfile 會被渲染在 User 的 <router-view> 中
          path: '/:id',
          component: UserProfile
        },
        {
          // 當 /user/posts 匹配成功
          // UserPosts 會被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
    ---------------------------------------------------
        // ------頁面/user
        
        <router-view></router-view>//User默認頁面
        <router-view name="head-bottom"></router-view>//Collapse頁面

        //---------頁面/user/2

        <router-view></router-view>//UserProfile頁面
        <router-view name="head-bottom"></router-view>//Collapse頁面

        //---------頁面/user/posts

        <router-view></router-view>//UserPosts頁面
        <router-view name="head-bottom"></router-view>//Collapse頁面
複製代碼

編程式導航

  • 聲明式

    <router-link 
      :to="{name:'userdetail',params:{id:index},query:{text:hero},hash:'#footer'}"></router-link>
    
        ----------------------------------------------------------
         name須要路由聲明瞭name,否者使用 path: `/user/${userId}`
    複製代碼
  • 編程式 >>>屬性跟聲明式同樣,須要什麼屬性就加什麼屬性

    router.push({ path: `/user/${userId}`, query: { plan: 'private' }})
    複製代碼

路由命名

{
  path: '/user/:userId',
  name: 'user',
  component: User
}
複製代碼

路由傳參

  • 若是 props 被設置爲 true,route.params 將會被設置爲組件屬性【props必須接受須要的參數屬性

    const User = {
        props: ['id'],
        template: '<div>User {{ id }}</div>'
      }
    
      --------------------------------------------------------
      { path: '/user/:id', component: User, props: true }
       //或者
       { path: '/user/:id', component: User, props: (route) => ({ params: route.params.id } }
       //或者
       { path: '/user/:id', component: User, props: { aaa: 545454 } }
    複製代碼

導航守衛

  • 全局【2.5.0新增beforeResolve(beforeEach解析以後被調用)】

    • to: Route: 即將要進入的目標

    • from: Route: 當前導航正要離開的路由

    • next(): 進行管道中的下一個鉤子

      • next(false): 中斷當前的導航
      • next('/')或者next({ path: '/' }):跳轉到一個不一樣的地址

      確保要調用 next 方法,不然鉤子就不會被 resolved

      //進去以前的先作什麼
            router.beforeEach((to, from, next) => {
              // ...
            })
      
            //進去以後作什麼【不會接受 next 函數也不會改變導航自己】
            router.afterEach((to, from) => {
              // ...
            })
      複製代碼
  • 路由獨享的守衛

    {
            path: '/foo',
            component: Foo,
            beforeEnter: (to, from, next) => {
              // ...
            }
          }
    複製代碼
  • 組件內的守衛【beforeRouteEnter/beforeRouteUpdate(路由改變前,組件就已經渲染完了, 邏輯稍稍不一樣)數據獲取】

    const Foo = {
                template: `...`,
                beforeRouteEnter (to, from, next) {
                  // 在渲染該組件的對應路由被 confirm 前調用
                  // 不!能!獲取組件實例 `this`
                  // 由於當守衛執行前,組件實例還沒被建立
                },
                beforeRouteUpdate (to, from, next) {
                  // 在當前路由改變,可是該組件被複用時調用
                  // 舉例來講,對於一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
                  // 因爲會渲染一樣的 Foo 組件,所以組件實例會被複用。而這個鉤子就會在這個狀況下被調用。
                  // 能夠訪問組件實例 `this`
                },
                beforeRouteLeave (to, from, next) {
                  // 導航離開該組件的對應路由時調用
                  // 能夠訪問組件實例 `this`
                }
              }
    
    
          ---------------------------------------------------------------------------
             //beforeRouteEnter 是支持給 next 傳遞迴調的惟一守衛
              beforeRouteEnter (to, from, next) {
                next(vm => {
                  // 經過 `vm` 訪問組件實例
                })
              }
    複製代碼

Snipaste_2019-08-25_19-46-44.png

路由元信息

  • 用法一

咱們知道咱們瀏覽一些網站的時候有須要驗證登陸的也有不須要驗證登陸的,若是全部頁面都在作成驗證登陸的話對於用戶的體驗也是極差的,因此這個時候路由元信息就起到了很大的做用

const router = new VueRouter({
          routes: [
            {
              path: '/foo',
              component: Foo,
              children: [
                {
                  path: 'bar',
                  component: Bar,
                  // a meta field
                  meta: { requiresAuth: true }
                }
              ]
            }
          ]
        })


        ----------------------------------------------------------------------------------

        let router = new VueRouter({
            routes
        });
        router.beforeEach((to, from, next) => {
            //判斷路由記錄是否須要驗證登陸
            if(to.matched.some(current => current.meta.needLogin)){
                //只要記錄上須要登陸 咱們就得驗證
                /*
                 * 本身封裝方法判斷登陸 sessionstorage localstorage cookie啥的自行決定
                 */
                 let isLogin = getLoginStatus();//本身定義的判斷登陸的方法
                 if(!isLogin) {
                     next({
                         path: '/login', //跳轉到登陸頁
                         query: {
                             redirect: to.fullPath //登陸頁須要知道從哪跳過來的,方便登陸成功後回到原頁面
                         }
                     });
                 } else {
                     next();
                 }
            } else {
                next();
            }
        });
        ATT: next必定要執行否則鉤子函數不會resolved。
複製代碼
  • 用法二
    Snipaste_2019-08-25_20-04-19.png

滾動行爲

Vue.use(VueRouter);
        //實例化
        export const router=new VueRouter({
          routes,
          mode:"hash",
          scrollBehavior(to,from,savedPosition){ //跳轉,滾到錨點
            // console.log(to);
            if(to.hash){ //判斷是否又錨點
              return {selector :to.hash} //選中錨點這個對象
            }
          }
        });
複製代碼

建立路由列表【動態導入(路由懶加載)】

  • 方式一

    const User= resolve=>{ //解決所有都在一個build.js的問題
          require.ensure('./components/user/user.vue',()=>{
              resolve(require('./components/user/user.vue'))
          })
      }
    複製代碼
  • 方式二

    {path: "/product",name: "product",component: () => import('xxxx')}
    複製代碼

Vue指令與Vue過濾

  • 過濾

    • 全局

      //main.js
        Vue.filter('to-lowercase',function(value){
          return value.toLowerCase();//獲取到擁有to-lowercase標籤下的值
        });
        //App.vue
        <p>{{ text | toUppercase | to-lowercase }}</p> ----一層層過濾
      複製代碼
    • 局部

      //App.vue
        <p>{{ text | toUppercase | to-lowercase }}</p>
      
        export default {
           filters:{
            toUppercase(value){
              return value.toUpperCase();
            }
          }
        }
      複製代碼
  • 指令

    • 全局

      //main.js
            Vue.directive('color',{
                bind(el,binding,vNode,oldVnode){
                  console.log(binding);
                  let delay=0;
                  if(binding.modifiers.delay){
                    // console.log("object");
                    delay=binding.modifiers.delay;
                     setTimeout(() => {
                      if(binding.value){
                        el.style.color=binding.value.fontcolor;
                        el.style.background=binding.value.ground;
                      }
                     }, delay);
                  }
                  if(binding.arg=="size"){
                    el.style.fontSize="30px"
                 }
                }
            });
      
            //App.vue
            <p v-color:size.delay="{fontcolor:'red',ground:'green',delay:500}">Switched</p>
      複製代碼
    • 局部

      export default {
              directives:{
                'xx':{
                  bind(el,binding,vNode,oldVnode){
                  console.log(binding);
                  let delay=0;
                  if(binding.modifiers.delay){
                    // console.log("object");
                    delay=binding.modifiers.delay;
                     setTimeout(() => {
                      if(binding.value){
                        el.style.color=binding.value.fontcolor;
                        el.style.background=binding.value.ground;
                      }
                     }, delay);
                  }
                  if(binding.arg=="size"){
                    el.style.fontSize="30px"
                 }
                }
                }
              }
            }
      複製代碼

(組件)插槽slot

  • 插槽

    • 主組件

      <app-slot>
                  <h2>11111111</h2>
                 <span>22222222</span>
           </app-slot>
           import slot from './components/slot/slot'
           ===》
           components:{
               "app-slot":slot,
             },
           
           <div>
                   <slot></slot>
           </div>
      複製代碼
    • 父組件:(決定有什麼槽,能讓相對應slot插進來進來【先引入子組件(插入槽中)】)

      //調整卡槽裏面內容
      <app-slot>
             <h2 slot="title">11111111</h2>
            <span slot="content">22222222</span>
            .......
            <span>sssssss</span>
      </app-slot>
      複製代碼
    • 子組件:(決定有什麼slot插銷,尤爲相對應的name)

      <div>
               <slot name="content">。。。。。。。</slot>
               <slot name="title">。。。。。。。。</slot>
               <slot>。。。。</slot>//這個會解析剩下的......以及<span></span>
       </div>
      複製代碼

API

Vue.extend

Vue.extend返回的是一個擴展實例構造器,也就是預設了部分選項的Vue實例構造器。其主要用來服務於Vue.component,用來生成組件。 extend 是構造一個組件的語法器,你給它參數 他給你一個組件 而後這個組件你能夠做用到Vue.component 這個全局註冊方法裏, 也能夠在任意vue模板裏使用

var myVue = Vue.extend({
         // 預設選項
        }) // 返回一個「擴展實例構造器」
         
        // 而後就能夠這樣來使用
        var vm = new myVue({
         // 其餘選項
        })
複製代碼

Vue.set 與 vm.$set

參考

Vue是不能檢測對象新增屬性的變化(檢測存在屬性的變化)的與數組的變化的【data裏面添加修改屬性是不會引發視圖渲染的】

data:{
                info:{id:1,price:15,name:"套餐A"}
            },
            methods:{
                add:function(){
                    // 給info對象添加msg屬性並賦值
                    //this.info.msg="hello";
                     this.info.msg="hello";
                }
            }

   ---------------------------------------------------

           <div id="app">
                   {{info.msg}}
               <button @click="add">添加</button>
            </div>
複製代碼

啥師

在實例建立以後添加新的屬性到實例上,不會觸發視圖更新**【使用$set】**。(修改對象裏面已經存在的屬性,則一樣直接修改便可)

data:{
                 info:{id:1,price:15,name:"套餐A"}
               },
            methods:{
                 add:function(){
                   // 給info對象添加msg屬性並賦值
                   //this.info.msg="hello";
                   this.$set(this.info,"msg","hello");
                     }
              }
複製代碼

數組的變化一樣不會是視圖被渲染【definePrototype的緣由】,怎麼利用$set修改呢?

data:{
          arr2 = [‘foo’,'bar','baz'];
        }

        this.$set(this.arr2, 1, 'alpha');
複製代碼

Vue.delete 與 vm.$delete

參考

同上(Vue是不能檢測對象的刪除屬性的變化(可以檢測存在屬性的變化)的【data裏面刪除屬性是不會引發視圖渲染的】)

  • $delete刪除對象屬性

    data:{
                      info:{id:1,price:15,name:"套餐A"}
                  },
                  methods:{
                      add:function(){
                          // 給info對象添加msg屬性並賦值
                          //this.info.msg="hello";
                          this.$set(this.info,"msg","hello");
                      }
                      del:function(){
                          // 刪除info對象裏面的price屬性
                          this.$delete(this.info,"price");
                      }
                  }
    複製代碼

數組的變化一樣不會是視圖被渲染【definePrototype的緣由】,怎麼利用$delete刪除呢?

data:{
          arr2 = [‘foo’,'bar','baz'];
        }

        this.$delete(this.arr2, 1);
複製代碼

Vue.mixin

混入也能夠進行全局註冊。使用時格外當心!一旦使用全局混入,它將影響每個以後建立的 Vue 實例。使用恰當時,這能夠用來爲自定義選項注入處理邏輯

// 爲自定義的選項 'myOption' 注入一個處理器。
            Vue.mixin({
              created: function () {
                var myOption = this.$options.myOption
                if (myOption) {
                  console.log(myOption)
                }
              }
            })

            new Vue({
              myOption: 'hello!'
            })
            // => "hello!"
複製代碼

請謹慎使用全局混入,由於它會影響每一個單首創建的 Vue 實例 (包括第三方組件)。大多數狀況下,只應當應用於自定義選項,就像上面示例同樣。推薦將其做爲插件發佈,以免重複應用混入。

Vue.compile(用處不大,詳細看文檔)

Vue.observable

讓一個對象可響應(返回的對象能夠直接用於渲染函數和計算屬性內,而且會在發生改變時觸發相應的更新)記錄

  • 配合provide / inject(使其變成響應式)

    <a @click="rrr">ppp</a>
    
              <script>
              import Vue from "vue";
    
              export default {
               provide() {
                  this.theme = Vue.observable({
                    saq: "ppp"
                  });
                  return {
                    theme: this.theme,
                  };
                },
              methods: {
                    rrr() {
                        this.theme.saq = "aaaaaa";
                      }
                  }
              }
              </script>
    複製代碼
  • 使用Vue.observable()進行狀態管理【替代Vuex的一種方案】

    // 準備個文件store.js - /store/store.js
              import Vue from 'vue'
    
              export const store = Vue.observable({ count: 0 })  //定義一個變量
              export const mutations = {  //定義一個方法,未來在組件中調用這個方法從而能改變上面的變量count值
                setCount (count) {
                  store.count = count
                }
              }
    複製代碼
  • 組件

    <template>
                      <div>
                          <p>你點+-,看我能不能根據狀態去動態改變</p>
                          <label for="bookNum">數量</label>
                          <button @click="setCount(count+1)">+</button>
                          <span>{{count}}</span>
                          <button @click="setCount(count-1)">-</button>
                      </div>
                  </template>
    
                  <script>
                  import { store, mutations } from '../store/store' // Vue2.6新增API Observable
    
                  export default {
                    name: 'Add',
                    computed: {
                      count () {
                        return store.count //用於去渲染以前Observable中定義的變量count
                      }
                    },
                    methods: {
                      setCount: mutations.setCount
                    }
                  }
                  </script>
    複製代碼

選項 / 組合

extends 與 mixins

參考詳解vue mixins和extends的巧妙用法

混合mixins和繼承extends

其實兩個均可以理解爲繼承,mixins接收對象數組(可理解爲多繼承),extends接收的是對象或函數(可理解爲單繼承)

const extend = {
         created () {
          console.log('extends created')
         }
        }
        const mixin1 = {
         created () {
          console.log('mixin1 created')
         }
        }
        const mixin2 = {
         created () {
          console.log('mixin2 created')
         }
        }
        export default {
         extends: extend,
         mixins: [mixin1, mixin2],
         name: 'app',
         created () {
          console.log('created')
         }
        }
複製代碼
  • 優先調用mixins和extends繼承的父類,extends觸發的優先級更高,相對因而隊列

  • push(extend, mixin1, minxin2, 自己的鉤子函數)

    const extend = {
             data () {
              return {
               name: 'extend name'
              }
             }
            }
    
            const mixin1 = {
             data () {
              return {
               name: 'mixin1 name'
              }
             }
            }
    
            const mixin2 = {
             data () {
              return {
               name: 'mixin2 name'
              }
             }
            }
    複製代碼
  • 測試

    // name = 'name'
              export default {
               mixins: [mixin1, mixin2],
               extends: extend,
               name: 'app',
               data () {
                return {
                 name: 'name'
                }
               }
              }
              // 只寫出子類,name = 'mixin2 name',extends優先級高會被mixins覆蓋
              export default {
               mixins: [mixin1, mixin2],
               extends: extend,
               name: 'app'
              }
              // 只寫出子類,name = 'mixin1 name',mixins後面繼承會覆蓋前面的
              export default {
               mixins: [mixin2, mixin1],
               extends: extend,
               name: 'app'
              }
    複製代碼
    • 組件自己自帶的data不會被覆蓋
    • mixins: [mixin2, mixin1]會使得mixin1覆蓋mixin2
    • mixins會覆蓋extends

選項 / 其餘

name選項

  • 遞歸調用本身【name:'DetailList'與detail-list】

    <div>
                  <div v-for="(item,index) of list" :key="index">
                    <div>
                      <span class="item-title-icon"></span>
                      {{item.title}}
                    </div>
                    <div v-if="item.children" >
                      <detail-list :list="item.children"></detail-list>
                    </div>
                  </div>
               </div>
              <script>
              export default {
                name:'DetailList',//遞歸組件是指組件自身調用自身
                props:{
                  list:Array
                }
              }
              </script>
    複製代碼
  • vue-tools調試工具裏顯示的組件名稱是由vue中組件name決定的

delimiters選項

默認的插值的寫法是{{}},可是在某些狀況下,咱們須要使用一些不同的方式,好比這樣${}

<div id="app">
          ${num}
          <button @click="add">addNumber</button>
        </div>

        new Vue({
          el: "#app",
          data: {
            num : 1
          },
          methods : {
            add : function() {
              console.log("原生 add");
              this.num++;
            }
          },
          delimiters: ['${', '}']
        });
複製代碼

functional選項

提供了一個 functional 開關,設置爲 true 後,就可讓組件變爲無狀態、無實例的函數化組件。由於只是函數,因此渲染的開銷相對來講,較小、 函數化的組件中,Render 函數,提供了第二個參數 context 做爲上下文,data、props、slots、children 以及 parent 均可以經過 context 來訪問

示例

  • 函數化組件不經常使用,它應該應用於如下場景
    1. 須要經過編程實如今多種組件中選擇一種
    2. children、props 或者 data 在傳遞給子組件以前,處理它們

代碼調試

model選項

定製v-model【默認狀況下,一個組件上的 v-model 會把 value 用做 prop 且把 input 用做 event】,一些輸入類型好比單選框和複選框按鈕可能想使用 value prop 來達到不一樣的目的。使用 model 選項能夠迴避這些狀況產生的衝突

Vue.component('my-checkbox', {
          model: {
            prop: 'checked',
            event: 'change'
          },
          props: {
            // this allows using the `value` prop for a different purpose
            value: String,
            // use `checked` as the prop which take the place of `value`
            checked: {
              type: Number,
              default: 0
            }
          },
          // ...
        })
複製代碼

comments選項:(當設爲 true 時,將會保留且渲染模板中的 HTML 註釋。默認行爲是捨棄它們)

實例方法 / 數據

vm.$watch

<div id="demo">{{fullName}}</div>

            var vm = new Vue({
             el: '#demo',
             data: {
             firstName: 'Foo',
             lastName: 'Bar',
             fullName: 'Foo Bar'
             }
            })

            vm.$watch('firstName', function (val) {
             this.fullName = val + ' ' + this.lastName
            })

            vm.$watch('lastName', function (val) {
             this.fullName = this.firstName + ' ' + val
            })
複製代碼

實例方法 / 事件

vm.$once

與vm.$on同樣【觸發一次,在第一次觸發以後移除監聽器】

this.$once("qqqqq", data => {
          console.log(data);
        });
複製代碼

vm.$off

移除on與emit事件

  • 移除自定義事件監聽器。

    • 若是沒有提供參數,則移除全部的事件監聽器;

    • 若是隻提供了事件,則移除該事件全部的監聽器;

    • 若是同時提供了事件與回調,則只移除這個回調的監聽器。

實例方法 / 生命週期

vm.$forceUpdate

在使用Vue框架開發時,在函數中改變了頁面中的某個值,在函數中查看是修改爲功了,但在頁面中沒有及時刷新改變後的值 運用 this.$forceUpdate() 強制刷新

特殊特性

key

源碼

講到key,能夠講到vue最核心的部分patch【patching算法(diff在這裏面),對比新舊的倆個vnode得欸差別】,Vue的diff算法是基於snabbdom改造過來 vue就是經過patch來渲染真實dom,並非暴力覆蓋原有DOM,而是對比新舊的倆個vnode之間有什麼不一樣(新增刪除修改節點)

參考

  • diff流程圖

當數據發生改變時,set方法會讓調用Dep.notify通知全部訂閱者Watcher,訂閱者就會調用patch給真實的DOM打補丁,更新相應的視圖

ss

  • 擁有key:使用key來給每一個節點作一個惟一標識,Diff算法就能夠正確的識別此節點,找到正確的位置區插入新的節點

ss

  • 沒有key:就地更新策略(複用)

若是不使用 key,Vue 會使用一種最大限度減小動態元素而且儘量的嘗試修復/再利用相同類型元素的算法

ss

有相同父元素的子元素必須有獨特的 key。重複的 key 會形成渲染錯誤【強制替換元素/組件而不是重複使用它】

<ul>
      <li v-for="item in items" :key="item.id">...</li>
    </ul>
複製代碼
  • 應用場景

    • 完整地觸發組件的生命週期鉤子

    • 觸發過渡

      <transition>
          <span :key="text">{{ text }}</span>
        </transition>
      複製代碼

is【動態加載組件】

與內置組件component組合使用【緊接着組合keep-alive達到不銷燬實例】

<button @click="selectComponent='app-one'">one</button>
                <button @click="selectComponent='app-two'">two</button>
                <button @click="selectComponent='app-there'">there</button>
                <button @click="selectComponent='app-four'">four</button>
                <component :is="selectComponent"></component>

                同上接着講點擊按鈕以後就會銷燬上一個實例,即每次都會重置(影響性能的優化)
                即 ======包裹動態組件時,會緩存不活動的組件實例,而不是銷燬它們
                <keep-alive>
                <component :is="selectComponent"></component>
                </keep-alive
複製代碼

內置組件

transition

具體屬性文檔

進入/離開 & 列表過渡

  • 動畫: 須要將動畫內容放置到transition中, 可是transition只容許有一個元素內容,若是裏面存有多個元素,則將會報錯 【transition-group組件】

    <transition name="fade">
        <!-- v-if和v-show之間的區別應該是十分明顯的,
        一個是元素存在,一個是不存在了 -->
        <div class="alert alert-info" v-show="show">This is some info</div>
      </transition>
    
          -------------------------------------------------
    
      <transition-group tag="ul" name="slide">
        <li v-for="item in items" :key="item.id">
          {{ item.text }}
        </li>
      </transition-group>複製代碼
相關文章
相關標籤/搜索