Vue总结梳理(详解生命周期函数/12种通信方式/Vue路由/Vuex状态管理/Vue所有知识点连串/配合源码分析)

2,301 阅读16分钟

最全的(前端知识)汇总,以便自身复习

上一篇内容:React总结来说(通信/redux/路由/钩子函数/三大解决逻辑共享方案(组件))

由于篇幅过于长,知识体系过于庞杂,请谨慎学习, 转述的我都有放置链接(难理解的也放了demo测试或者图)

技术规范(基础了解,大佬尽可跳过)

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 上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法

ss

创建一个Vue实例开始【new Vue()】看图说话

beforeCreate【beforeCreate钩子函数前,函数时,函数后】

  • beforeCreate

    • 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钩子函数时,函数后】

  • created

    • created时候:在此之前->属性初始化以及属性绑定,created钩子函数这里是能获取到props 、methods 、data 、computed 和 watch的,但是获取不到el(dom)

    • created之后【beforeMount之前】:【简单来说把tempalte编译成render函数的过程】借鉴转载 ||| 这边用到DOM选项

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

      • 判断有没有el【先检查了是否存在渲染位置,在HTML页面写Vue实例的时候每个实例都有el的原因】,没有el选项,则停止编译,也就意味着停止了生命周期,直到在该vue实例上调用vm.$mount(el)挂载

        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.cn/post/6844903913355739143#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>