Vue2、Vue3+TS

160 阅读40分钟

vue2

1.Vue基本原理

当一个Vue创建实例时,Vue会遍历data中的属性,用Object.defineProperty将它们转为getter\setter,并且在内部追踪相关依赖,在属性被访问和修改是通知变化。每个组件实例都有相对的 watch程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,congershi它们关联的组件得以更新。

1.1.响应式原理

数据劫持结合发布者-订阅者模式,,通过Object.defineProperty来劫持各个属性的getter、setter。在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤

Observe:被劫持的数据对象 Compile:vue编译器 Watcher:订阅者 Dep:收集Wather订阅者们

  1. 需要给Observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter、getter这样的属性,给这个对象某个值赋值,就会触发 setter,那么就能监听到数据变化

  2. Compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据变动,收到通知,更新视图

  3. Watcher订阅者是 ObserveCompile之间通信的桥梁,主要做的事情:

    1. 在自身实例化时往属性订阅器 Dep里添加自己
    2. 自身必须有一个 update() 方法
    3. 待属性变动 dep.notice() 通知时,能调用自身的 update() 方法,并触发Compile中绑定的回调。

2.项目架构

大致事情:项目构建,引入必要插件,代码规范,提交规范,常用库和组件

  1. 用vite或者create-vue创建项目
  2. 引入必要插件:路由插件vue-router,状态管理vuex/pinia,ui库常用的有element-plus和antd-vue,http工具常用axios
  3. 其他常用的库有vueUse,nprogress,图标可以使用vite-svg-loader
  4. 代码规范:结合prettie和eslint
  5. 提交规范:可以使用husky,lint-staged,commitlint

目录结构

.vscode:项目中vscode配置

plugin:vite插件的plugin配置

public:页头,icon之类的公共文件,会被打包到dist根目录下

src:项目代码

api:http的一些接口配置

assets:css之类的静态资源

layout:项目布局

router:路由配置

store:状态管理Pinia的配置

utils:工具方法类

view:项目的页面文件

2.1.初始化过程中new Vue(options)的作用
  • 处理组件配置项,初始化根组件时进行了选项合并操作,将全部配置合并到根组件的局部配置上。初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到vm.$options选项中,以提高代码的执行效率
  • 初始化组件实例的关系属性,比如 parent,children,root,refs等,处理自定义事件
  • 调用beforeCreated钩子函数
  • 初始化组件inject配置项,得到ret[key]=val形式的配置对象,然后对该配置对象进行响应式处理,并代理每个key到vm实例上
  • 数据响应式,处理props,methods,data,computed,watch等选项
  • 解析组件配置项上的provide对象,将其挂载到vm._provided属性上,调用created钩子函数
  • 如果发现配置项上有el选项,自动调用$mount方法,也就是说有了el选项,就不用手动调用mount方法。
2.2.Vue中data属性可以和methods中方法同名吗

可以同名,methods的方法名会被data属性覆盖,调试台也会出现报错,但是不影响执行

3.动态绑定Class和Style

v-bind:class={{'类名':bool,'类名':bool,...}}
v-bind:style='styleObject'

4.vue常用修饰符

.v-on

.stop—调用 event.stopPropagation阻止默认事件

.prevent—调用 event.preventDefault阻止默认行为

.native—监听组件根元素的原生事件

v-bind

.prop—作为一个DOM property绑定而不是作为一个attribute绑定

.camel—将kababCase转化为camelCase

.sync—语法糖,扩展成一个更新父组件绑定值的v-on侦听器

v-modle

.lazy—取代input监听change事件

.number—输入字符串为有效数字

.trim—输入首尾空格过滤

5.vue内置组件

component

渲染一个“元组件”为动态组件,依的值来决定哪个被渲染

在一个多标签的页面中使用 is attribute 来切换不同的组件(tab栏切换)

transition

在vue插入、更新、移除DOM时,提供多种过度动画效果

transition-group

用于给列表统一设置过渡动画

keep-alive

主要用于保留组件状态或者避免组件重新渲染

  • include 属性用于指定那些组件会被缓存,有多种设置方式
  • exclude 属性用于指定哪些组件不会被缓存
  • max 属性用于设置最大缓存个数

slot

name-string 用于命名插槽

6.v-if与v-show

  • 原理不同

    v-show:一定会渲染,只是修改display的属性

    v-if:根据条件渲染

  • 应用场景不同

    频繁切换用-show,不频繁切换用v-if

v-if 是真正的条件渲染,它会在切换过程中组件会被销毁和重建,操作的实际上是dom元素的创建和销毁。

v-show 不管初始条件是什么,都会被渲染,只是简单的基于CSS进行切换。

7.v-for

v-for循环绑定key是为了提升vue渲染性能

  • key主要作用是高效更新虚拟DOM,因为它可以精准找到相同节点,patch过程高效
  • vue在patch过程中判断两个节点是不是相同,key是一个必要条件,同时避免数组下标作为key,因为key值不是唯一的话,vue无法区分它,以及在使用相同标签元素过渡切换的时候,只会替换其内部属性而不会触发过渡效果
  • 从源码得知,vue判断两个节点是否相同主要是通过判断两者的元素类型和key,如果不设置key,就可会认为这个节点是相同的,之后去做更新操作,就造成大量不必要的DOM更新

8.v-modle的实现

原理:v-bind和v-on结合

  1. 作用在表单上动态绑定了input的value指向了message变量,并且在触发input事件的时候去动态把message设置为目标值
  2. 作用在组件上 在自定义组件中,v-modle默认会利用名为value的prop和名为input的事件

本质是一个父子组件通信的语法糖,通过prop和$.emit实现

//父组件
<input v-model='aa'><input>
//等价于
<input v-bind:value = 'aa' v-on:input='aa=$event.target.value'><input>
//子组件
<input v-bind:value='aa' v-on:input='onmessage'><input>
    
props: {value:aa}
methods: {
    onmessage(e) {
        $emit('input', e.target.value)
    }
}

默认情况下,一个组件上的v-model会把value作用prop且把input作用 event。但是一些输入类型,比如单选框和复选框按钮可能想使用value prop来达到不同的目的。使用model选项可以回避这些情况产生的冲突。js监听input输入框输入数据改变,用oninput,数据改变以后就会立刻触发这个事件,通过input事件把数据$emit出去,在父组件接收。父组件设置v-model的值为input $emit过来的值。

9.v-molde和.sync

相同点:都是语法糖,都可以实现父子组件中的数据双向通讯

v-modle:

  • 父组件v-model='',子组件@(input,value)
  • 一个组件只能绑定一个v-modle
  • 针对的是最终操作结果,是双向绑定结果,是,是一种操作

.sync

  • 父组件你:my-prop-name.sync 子组件@update:my-prop-name 的模式代替事件触发,实现父子组件双向绑定
  • 一个组件可以多个属性用.sync修饰符,可以同时绑定多个prop
  • 针对更多的是各种各样的状态,是状态相互传递,是,是一种操作

10.computed和watch

computed

  • 它支持缓存,只有依赖数据发生变化,下一次获取computed的值才会重新计算它的值
  • 不支持异步,当Computed中有异步操作,无法监听数据变化
  • Computed默认会走缓存,计算属性是基于它们的响应式依赖进行缓存, 也就是基于date声明过的,或者父组件传递过来的props中的数据进行计算
  • 在Computed中,属性有一个get方法和一个set方法,当数据发生变化的时候,会调用set方法

watch

  • 不支持缓存,更多的是观察作用,无缓存性,类似于某些数据的监听回调,每当监听的数据发生变化时都会执行回调进行后续操作

  • 支持异步监听

  • 监听的函数接受两个参数,第一个参数是最新的值,第二个参数是变化的值

  • 监听数据必须是date中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个参数

    • imediate:组件加载立即触发回调函数
    • deep:深度监听,发现数据内部变化,在复杂数据类型中使用,例如数组中的对象发生变化,需要注意的是,无法监听到数组和对象内部的变化

运用场景

  • 当需要进行数值计算,并且依赖于其他数据时,使用Computed,可以利用Computed的缓存特性,避免每次获取值时都要重新计算
  • 当需要在数据变化时执行异步或者开销较大的操作时,使用watch,选项允许执行异步操作(访问一个),限制执行该操作的频率,并在最终结果前,设置中间状态。这些都是计算属性无法做到的
10.1.computed和methods

对于最终结果两种方式是相同的,不同点在于,计算属性是基于它的依赖进行缓存,只有在它的相关依赖发生改变时才会重新求值,methods调用总会执行该函数

11.组件

组件就是把图形、非图形的各种逻辑抽象为一个统一的概念(组件)俩实现开发的模式,在vue中每一个.vue文件都可以视为一个组件

组件的优势: 降低代码耦合度,调试方便,可维护性高

11.1.封装组件
  1. 先分析需求:确定业务需求,把页面中可以复用的解构,样式以及功能
  2. 具体步骤:Vue.component或者在 new Vue配置components中定义组件名。可以在props中接受给组件的传参,子组件可以通过$emit向父组件传值
11.2.组件中name属性的作用
  1. 项目使用keep-alive时,可搭配组件name进行缓存过滤
  2. DOM做递归组件时需要调用自身name
  3. Vue-devtools调试工具里显示的组件名称是由Vue中组件name决定的
  4. 动态切换组件
11.3.组件的命名规范
  • 给组件命名有两种方式:一种是使用链式命名”my-component“,一种是大驼峰命名”MyComponent“
  • 以为要遵循W3C规范中的自定义组件名,避免和当前以及未来html元素相冲突
11.4.扩展组件
  1. 常见的组件扩展方式有:minxs,slots,extends等。

  2. 混入minxs是分发Vue组件中可复用功能非常灵活的一种方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项

    //复用代码
    const mymixin = {
      methods: {
        dosomething() {}
      }
    }
    ​
    //全局混入:将混入对象传入
    Vue.mixin(mymixin)
    ​
    //局部混入:做数组项设置到mixins选项,仅作用于当前组件
    const Comp = {
      mixins: [mymixin]
    }
    
  3. 插槽主要用于Vue组件内容分发,也可用于组件扩展。如果要精确分发到不同位置可以使用具名插槽,如果要使用子组件中的数据可以使用作用域插槽

  4. extends也可以起到扩展组件的目的

  5. 混入的数据和方法不能明确判断来源且可能和当前组件内变量产生命名冲突,Vue3中引入的composition api可以很好解决这些问题,利用独立出来的响应式模块可以很方便的编写独立逻辑并提供响应,然后在setup选项中组合使用,增强代码的可读性和维护性

11.5.Scoped作用与原理
  • 作用:组件css作用域,避免子组件内部的css样式被父组件覆盖。
  • 原理:给元素添加一个自定义属性v-data-xxxxx,通过属性选择器提高css权重值
11.6.函数式组件

函数式组件可以理解为没有内部状态,没有生命周期钩子函数,没有this(不需要实例化的组件)

为什么使用函数组件

  1. 关键原因是函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能好于普通组件
  2. 函数式组件结构比较简单清晰

函数式组件和普通组件的区别

  • 函数式组件需要在声明组件时指定functional
  • 函数式组件不需要实例化,所以没有this,this是通过render函数的第二个参数代替;由于没有实例化,外部通过ref去引组件时,实际引用的是HTMLElement
  • 函数式组件没有生命周期钩子函数,故不能使用计算属性,watch等待。
  • 函数式组件不能通过$emit对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传来的事件
  • 函数式组件的props可以不用显示声明,所以没有在props里声明的属性都会被自动隐式解析为props,而普通的组件所有未声明的属性都会被解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)

12.插件

插件通常用来为Vue添加全局功能,常见的有以下几种

  • 添加全局方法或属性:如 vue-custom-element
  • 添加全局资源:指令\过滤器\过渡等。如 vue-touch
  • 添加全局公共组件 Vue.component()
  • 添加全局公共指令 Vue.directive()
  • 通过全局混入来添加一些组件选项,如vue-router
  • 添加vue实例方法,通过把他们添加到Vue.prototype上实现
  • 一个库,可以提供自己的API,同时提供上面提及的一个或多个功能

13.Vue2、3注册全局组件

Vue2使用Vue.component('组件名',组件对象)

Vue3

const app = CreatApp(App)
Vue.component('组件名',组件对象)

·

14.Vue2、3封装自定义插件并使用

Vue2

在components.index.js里,定义一个函数或对象,在里面可以使用Vue.compoent全局注册组件,并暴露出去

在main.js里面使用Vue.use(),参数类型必须是Object或Fuction

Vue3

在components.index.ts里,定义一个函数或对象,在里面可以使用app.compoent全局注册组件,并暴露出去

在main.ts里面使用app.use(),参数类型必须是Object或Fuction


如果是Function这个函数就会被当作install方法

如果是object则需要定义一个install方法

15.组件通讯

  1. propspropsemit(父子)

    • 父组件向子组件传值

      1. props只能是父组件向子组件进行传值,props是的父子组件之间形成一个单向下行绑定。子组件的数据会随着父组件不断更新
      2. props可以定义多个数据,也可接受各种类型的数据,也可以传递函数
      3. props命名规则:若在props中使用驼峰形式,模板中需要用短横线的形式
    • 子组件向父组件传值

      $emit绑定一个自定义事件,当这个事件被执行时就会像参数传递给父组件,父组件通过v-on监听并接受

  2. 注入依赖provide\inject(父子,祖孙)

    provide\inject是Vue提供的两个钩子,和data、methods是同级。并且provide的书写形式和data一样

    • provide钩子用来发送数据或方法
    • inject钩子用来接收数据和方法

    在父组件中

    provide() {
        return {
            num: this.num
        }
    }
    

    在子组件中

    inject: ['num']
    

    还可以这样写用于访问父组件中的所有属性

    /父组件/
    provide() {
        return {
            app: this
        }
    }
    data() {
        return {
            num:1
        }
    }
    ​
    /子组件/
    inject: ['app']
    console.log(this.app.num)
    

    注意:注入依赖提供的属性都是非响应式的

  3. parent \ children (父子)

    使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法

    <template>
     <div>
       <span>{{message}}</span>
       <p>获取父组件的值为:{{parenValue}}</p>
     </div>
    </template><script>
      export default {
        data() {
          return {
            message: 'vue'
          };
        },
        computed: {
          parentValue() {
            return this.$parent.msg
          }
        }
      }
    </script>
    

    使用$children可以让组件访问子组件的实例,但是不能保证顺序,并且访问的数据也不是响应式的

    <template>
     <div>
       <div>{{msg}}</div>
       <child></child>
       <buttom @click='change'>点击改变子组件</buttom>
     </div>
    </template><script>
    import child from "/child.vue";
      export default {
        components: {
          child
        }
        data() {
          return {
            msg: 'Welcome'
          };
        },
        methods: {
          change() {
            this.$children[0].message = 'JavaScript'
          }
        }
      }
    </script>
    

    以上,子组件获取到了父组件的parentValue值,父组件改变了子组件message的值

    需要注意的是

    • 通过parent访问的是上一级父组件的实例,可以使用 {root来访问根组件的实例
    • 在组件中使用$children拿到的是所有的子组件的实例,它是一个数组并且是无序的
    • 在根组件#app上拿$parent得到的是new Vue()的实例,在实例上再拿 parent 得到的是undefined,而在最底层的子组件拿 children是个空数组
    • children的值是数组,parent是对象
  4. ref$ref(父子,兄弟)

    ref:这个属性用在子组件上,它的引用就指向子组件实例,可以通过实例来访问组件的数据和方法

    这种方式也是实现兄弟组件通讯,子组件A通过this.$emit通知父组件调用函数,父组件的函数里面用

    this.$refs拿到子组件B的方法,这样就实现兄弟组件通讯

    在子组件中:

      export default {
        data () {
          return {
            name: 'JavaScript'
          }
        },
        methods: {
          sayHello () {
            console.log('hello~');
          }
        }
      }
    

    在父组件中:

    <template>
    <child ref='child'>
      </component-a>
    </child>
    </template><script>
    import child from './child.vue'
      export default {
        components: {
          child
        },
        mounted () {
          this.$refs.child.sayHello()
        }
      }
    </script>
    
  5. attrs/listeners(祖孙)

    inheriAttrs:它的默认值为true,继承所有的父组件处props之外的所有属性

    inheriAttrs:false只继承class属性

    $attrs:继承所有父组件属性,除props传递的属性、class和style,一般用在子组件的子元素上

    $listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合

    v-on="$listeners"将所有的事件监听器指向这个组建的某个特定的子元素(相当于子组件继承父组件的事件)

    A组件(APP.vue)

  6. eventBus事件总线(emit/on)任意组件通讯

    创建事件中心管理组件之间的通讯

    //event-bus.js
    import Vue from 'vue'
    export const EventBus = new Vue()
    

    发送事件 假设有两个兄弟组件firstCom和secondCom

    <template>
    <div>
      <first-com></first-com>
      <second-com></second-com>
    </div>
    </template><script>
    import firstCom from './firstCom.vue'
    import secondCom from './secondCom.vue'
    ​
      export default {
        components: {
          firstCom
          secondCom
        }
      }
    </script>
    

    在firstCom组件中发送事件

    <template>
    <div>
      <button @clik='add'></button>
    </div>
    </template><script>
    import { EventBus } from './event-bus.js'
    export default {
      data () {
        return {
          num: 8
        }
      },
      methods: {
        add() {
          EventBus.$emit('addition',{
            num: this.num++
          })
        }
      }
    }
    </script>
    

    在secondCom组件中接收事件

    <template>
    <div>求和:{{count}}</div>
    </template><script>
    import { EventBus } from './event-bus.js'
    export default {
      data () {
        return {
          count: 0
        }
      },
      mounted () {
        EventBus.$on('addition', param => {
          this.count = this.count + param.num
        })
      }
    }
    </script>
    

    在上述代码中,就相当于将num的值存储在事件总线中,其他组件可以直接访问,事件总线相当于一个桥梁,不用组件通过它来通讯

    虽然看起来比较简单,但项目过大,使用这种方式通讯后期维护起来很困难

    总结

    1. 父子组件通讯

      • 子组件通过props属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过emit触发事件来向父组件发送数据
      • 通过ref属性给子组件设置一个名字,父组件通过$refs组件名来获取子组件,子组件通过parent获取父组件,这样也可以实现通讯
      • 使用provide/inject,在父组件中通过provide提供变量,在子组件中通过inject将变量注入到组件中,无论子组件有多深,只要调用了inject那么就可以注入provide中的数据
    2. 兄弟组件通讯

      • 使用eventBus的方法,本质是通过创建一个空的vue实例来作为消息的传递对象,通信的组件引入这个实例,需要通讯的组件通过在这个实例上监听和触发事件,来实现消息传递
      • 通过parent/refs来获取兄弟组件,也可以进行通讯
    3. 任意组件通讯

      如果业务逻辑复杂,很多组件需要同时处理一些公共数据,这是可以使用vex,vex的思想就是将着一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他的组件就可以对这个公共的数据进行读写操作,达到解耦的目的

16.单向数据流

所有的prop都使其父子之间形成单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行,这样可以防止从子组件意外改变父组件的状态,从而导致数据流向难以理解,导致数据混乱。

额外的,每次父组件发生更新时,子组件中的所有prop都会刷新为最新值。这意味不能在子组件内部改变props,如果这样做了,Vue会在浏览器的控制台中发出警告,子组件想修改时,只能通过 $emit派发一个自定义事件,父组件收到后修改

有两种常见的试图修改prop的情形

  • 这个用来传递一个初始值,子组件接下来希望将其作为一个本地的数据来使用。在这种情况中,最好定义一个本地的data属性并将这个prop作为其初始值。

    props: ['initialCounter']
    data: function () {
      return {
        counter: this.initialCounter
      }
    }
    
  • 这个以一种初始的值传入且需要进行转换。在这种情况下,最好使用这个prop的值来定义一个计算属性

    props: ['size']
    computed: {
      normalizesSize: function () {
        return this.size.trim().toLowerCase()
      }
    }
    

17.vue的生命周期

vue实例从创建到销毁的过程就是生命周期,四大阶段八大钩子

  • 第一阶段:创建前后

    beforeCreate: 数据观测和初始化事件还未开始,data的响应式追踪、event\watch都未设置,不能访问到data、computed、watch、method上的方法和数据

    created: 实例创建完成,实例上配置的options包括data、computed、watch、methods等配置完成,但此时渲染节点还未挂在到DOM,不能访问$el属性

  • 第二阶段:渲染前后

    beforeMount: 在挂载前被调用,相关的render函数首次被调用,data里的数据和模板已经生成html,此时html还没有挂在到页面上

    mounted: el被新创建的vm.$el替换,并挂载到实例上去之后调用。此过程进行ajax交互

  • 第三阶段:更新前后

    beforeUpdate: 响应式数据更新时调用,此时虽然响应式数据更新了,但对应的真实DOM还没被渲染

    updated: 在数据更新使虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能导致更新无限循环。该钩子在服务器端渲染期间不被调用

  • 第四阶段:销毁前后

    beforeDestroy: 实例销毁前调用,这一步实=实例仍然可用,this仍能获取实例

    destroyed: 实例销毁后调用,vue实例指示的所有东西都会解绑,所有的监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用

  • 其余三个不常用生命周期函数

    另外还有keep-alive独有的生命周期,分别为activated和deactivated。用keep-alive包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行deactivated钩子函数,命中缓存渲染后会执行activated钩子函数

    errorCaptured钩子,当捕获错误时,会受到三个参数:错误对象,发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回false以阻止该错误继续向上传播

17.1.一般在哪个生命周期请求异步函数

可在钩子函数created、beforeMount、mounted中进行调用,因为在这三个钩子中,data已经创建,可以将服务端返回的数据进行赋值

推荐在created钩子函数中调用异步请求,优点

  1. 能够更快的获取到服务端的数据,减少页面加载时间,用户体验感更好。
  2. SSR不支持beforeMouted、mounted钩子函数,放在created中有助于一致性
17.2.在什么阶段才能操作访问DOM

在钩子函数mounted被调用前,Vue已经将编译好的模板挂到页面上,所有在mounted中可以访问DOM

17.3.父组件可以监听到子组件的生命周期吗

比如父组件Parent和子组件Child,父组件要监听子组件挂载mounted就得做一些逻辑处理。

//Parent.js
<Child @mounted='doSomething'/>
​
//Child.js
mounted () {
  this.$emit('mounted')
}

以上需要手动通过$emit触发父组件事件,更简单的方式可以在父组件引用子组件时通过 @hook来监听

//Parent.js
<Child @hook:mounted='doSomething'/>
​
doSomething() {
  console.log('父组件监听到mouted钩子函数');
}
​
//Child.VUE
mounted () {
  console.log('子组件触发mounted钩子函数');
}
//输出顺序是:子组件触发mounted钩子函数 → 父组件监听到 mounted钩子函数
17.4.Vue实例挂载的过程中发生了生命
  1. 过载过程是指app.mount() ,这个过程整体上做了两件事:初始化和建立更新机制
  2. 初始化会创建组件实例,初始化组件状态,创建各种响应式数据
  3. 建立更新机制这一步会立即执行一次组件更新,这会首次执行组件渲染函数并执行patch将前面获得vnode转换为dom;同时首次执行渲染函数会创建它内部响应式数据和组件更新函数之间的依赖关系,最后将前面得到的AST生成JS代码,也就是render函数
17.5.beforeDestroy钩子的作用(待补充

如果页面上有很多定时器,可以在data选项中创建一个对象timer,给每个定时器取名一一映射在对象timer中,在beforeDestroy构造函数中循环遍历所有的定时器,一次性取消

for(let k in this.timer) {
  clearInterval(k)
}
​
//若只有一个定时器
const timer = setInterval(() => {}, 500);
this.$once('hook:beforeDestroy',()=> {
  clearInterval(timer)
})
17.4.组件缓存keep-alive

是一个抽象组件,它自身不会渲染一个DOM元素,也不会出现在父组件链中

使用场景:多表单切换,对表单内数据进行保存

keep-alive的参数

  • include:名称匹配的组件的会被缓存,include的值为组件名name
  • exclude:不被缓存的组件
  • max:最多缓存 组件数量

keep-alive的使用

  • 搭配使用

  • 搭配路由使用,需要配置路由的meta信息中keep-alive属性

  • 清除缓存组件

    • 在路由跳转之前使用后置路由守卫判断组件是否缓存
    • beforRouteLeave(to,from,next){from.meta.keepAlive=false}

使用keep-alive会将数据保留在内存中,如果每次要进入页面时获取 最新的数据,需要在activated阶段获取,承担原来created钩子函数中获取数据的任务

需要注意:只有组件被keep-alive包裹时,这两个生命周期才会被调用,作为正常的组件,是不会调用的。使用exclued排除之后,就算包裹在keep-alive中,这两个钩子函数依然不会被调用

设置了缓存的组件钩子调用情况

第一次进入:beforeRouteEnter => created => ... => activated => deactivated => beforeRouteLeave

第二次进入:beforeRouteEnter => activated => deactivated => beforeRouteLeave

17.5.Vue2怎么内部监听生命周期钩子(hook)

在Vue组件中,可以通过on,once去监听所有的生命周期钩子函数,如监听组件的updated钩子函数可以写成:

this.$on('hook:updated', () => {})

18.slot插槽的理解和使用场景

插槽是vue内容分发机制,组件内部的模板引擎使用slot元素作为承载内容分发的出口,插槽slot是子组件的一个模板标签元素,而这一标签元素是否显示,以及怎么显示由父组件决定

插槽可拓展组件,去更好地复用和定制化处理

通过slot插槽向组件内部指定位置传递内容,完成组件在不同场景的复用

比如布局组件,表格,下拉选项,弹框显示等内容

18.1默认插槽

子组件用标签来确定渲染位置,标签里面可以放DOM结构,当父组件使用的时候没有往插槽里传内容,标签内DOM结构就会显示在页面

18.2具名插槽

子组件用name属性来表述插槽的名字,不传为默认插槽

18.3作用域插槽

子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件的 v-slot 接受的对象上,父组件中使用v-slot:(简写#)获取子组件的信息,在内容中使用

总结

  • v-slot属性只能在