Vue 全家桶的各种重点技术点

什么是库?什么是框架?

  • 是将代码集合成一个产品,库是我们调用库中的方法实现自己的功能。
  • 框架 是为解决一类问题而开发的产品,框架是我们在指定的位置编写好代码,框架帮我们调用。

声明式框架

Vue3 依旧是声明式的框架,用起来简单(代码更加简单,不需要关注实现,按照要求填代码就可以)

  • 命令式和声明式的区别?
    1. jQuery 时代编写的代码都是命令式的,命令式框架重要的特点是 关注过程
    2. 声明式框架更加关注结果。命令式的代码封装带了 Vue 源码中,过程靠 Vue 内部来实现
// 命令式编程:
let numbers = [1, 2, 3, 4, 5]
let total = 0
for (let i = 0; i < numbers.length; i++) {
  total += numbers[i] // 关注了过程
}
console.log(total)

// 声明式编程:
let total1 = numbers.reduce((memo, cur) => {
  return memo + cur
})
console.log(total1)

Monorepo 管理项目

Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo)中管理多个模块/包(package)。Vue3 源码采用了 monorepo 方式进行管理,将模块拆分到 packages 目录中

  • 一个仓库可维护多个模块,不用到处找仓库
  • 方便版本管理和依赖管理,模块之间的引用,调用都非常方便

Vue 的基本使用

$ npm init -y
$ npm install vue
  1. 安装
npm install -g @vue/cli
npm install -g @vue/cli-service-global
vue create vue-online-edit
  1. 初始化
? Check the features needed for your project:
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
>(*) Router
>(*) Vuex
>(*) CSS Pre-processors
 ( ) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

vue serve 是用来单独开发一个 vue 组件用的。vue serve 这个命令需要全局安装之后,才能使用。使用 npm install -g @vue/cli-service-global 安装之后,再来使用 vue serve 命令。

Vue 中的模板

<script src="node_modules/vue/dist/vue.js"></script>
<!-- 3.外部模板 -->
<div id="app">{{name}}</div>
<script>
  const vm = new Vue({
    el: '#app',
    data: {
      name: 'tmc',
      age: 25,
    },
    // 2.内部模板
    template: '<div>{{age}}</div>',
    // 1.render函数
    render(h) {
      return h('h1', ['hello,', this.name, this.age])
    },
  })
</script>

注意:我们默认使用的是 runtime-with-compiler 版本的 vue,带 compiler 的版本才能使用 template 属性,内部会将 template 编译成 render 函数。

流程如下:

  • 会先查找用户传入的 render
  • 若没有传入 render 则查找 template 属性
  • 若没有传入 template 则查找 el 属性,如果有 el,则采用 el 的模板

响应式原则

  1. Vue 内部会递归的去循环 vue 中的 data 属性,会给每个属性都增加 gettersetter,当属性值变化时会更新视图
  2. 重写了数组中的方法,当调用数组方法时会触发更新,也会对数组中的数据(对象类型)进行了监控

Vue 中的缺陷:

  • 对象默认只监控自带的属性,新增的属性响应式不生效 (层级过深,性能差)
  • 数组通过索引进行修改 或者 修改数组的长度,响应式不生效

Vue 额外提供的 API:

vm.$set(vm.arr, 0, 100) // 修改数组内部使用的是splice方法
vm.$set(vm.address, 'number', '6-301') // 新增属性通过内部会将属性定义成响应式数据
vm.$delete(vm.arr, 0) // 删除索引,属性

为了解决以上问题,Vue3.0 使用 Proxy 来解决

let obj = {
  name: { name: 'jw' },
  arr: ['吃', '喝', '玩'],
}
let handler = {
  get(target, key) {
    if (typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], handler)
    }
    return Reflect.get(target, key)
  },
  set(target, key, value) {
    let oldValue = target[key]
    if (!oldValue) {
      console.log('新增属性')
    } else if (oldValue !== value) {
      console.log('修改属性')
    }
    return Reflect.set(target, key, value)
  },
}
let proxy = new Proxy(obj, handler)

实例方法

  1. vm._uid -> 每个实例的唯一标识
  2. vm.$data === vm._data -> 实例的数据源
  3. vm.$options -> 用户传入的属性
  4. vm.$el -> 当前组件的真实 dom
  5. vm.$nextTick -> 等待同步代码执行完毕
  6. vm.$mount -> 手动挂载实例
  7. vm.$watch -> 监控数据变化

自定义指令 - 钩子函数

指令定义对象可以提供如下几个钩子函数:

  • bind:只调用一次,指令第一次绑定到元素时调用
  • inserted:被绑定元素插入父节点时调用
  • update:所在组件的 VNode 更新时调用,组件更新前状态
  • componentUpdated:所在组件的 VNode 更新时调用,组件更新后的状态
  • unbind:只调用一次,指令与元素解绑时调用
// 1.el 指令所绑定的元素,可以用来直接操作 DOM
// 2.bindings 绑定的属性
// 3.Vue编译生成的虚拟节点  (context)当前指令所在的上下文
bind(el,bindings,vnode,oldVnode){ // 无法拿到父元素 父元素为null
    console.log(el.parentNode,oldVnode)
},
inserted(el){ // 父元素已经存在
    console.log(el.parentNode)
},
update(el){ // 组件更新前
    console.log(el.innerHTML)
},
componentUpdated(el){ // 组件更新后
    console.log(el.innerHTML)
},
unbind(el){ // 可用于解除事件绑定
    console.log(el)
}

Vue 组件通信

  1. props$emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过 $emit 触发事件来做到的
  2. $attrs$listeners A->B->CVue 2.4 开始提供了 $attrs$listeners 来解决这个问题
  3. $parent,$children
  4. $refs 获取实例
  5. 父组件中通过 provider 来提供变量,然后在子组件中通过 inject 来注入变量
  6. envetBus 平级组件数据传递 这种情况下可以使用中央事件总线的方式
  7. vuex 状态管理

掌握 Vue 重点

一、插槽

二、Transition 组件

三、Keep-Alive 组件

一、Keep-alive 是什么

keep-alive 是 vue 中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染 DOM

keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

keep-alive 可以设置以下 props 属性:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例

keep-alive 的基本用法:

<keep-alive>
  <component :is="view"></component>
</keep-alive>

使用 includes 和 exclude:

<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated 与 deactivated):

  • 首次进入组件时:beforeRouteEnter > beforeCreate > created> mounted > activated > ... ... > beforeRouteLeave > deactivated
  • 再次进入组件时:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated

二、使用场景

使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用 keep-alive

举个栗子: 当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要 keep-alive

在路由中设置 keepAlive 属性判断是否需要缓存

{
  path: 'list',
  name: 'itemList',
  component: itemList,
  meta: {
    keepAlive: true,
    title: '列表页'
  }
}

使用 <keep-alive>

<div id="app">
  <keep-alive>
    <!-- 需要缓存的视图组件 -->
    <router-view v-if="$route.meta.keepAlive"></router-view>
  </keep-alive>
  <!-- 不需要缓存的视图组件 -->
  <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>

三、原理分析

keep-alive 是 vue 中内置的一个组件; 源码位置:src/core/components/keep-alive.js

export default {
  name: 'keep-alive',
  abstract: true,

  props: {
    include: [String, RegExp, Array],
    exclude: [String, RegExp, Array],
    max: [String, Number],
  },

  created() {
    this.cache = Object.create(null)
    this.keys = []
  },

  destroyed() {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted() {
    this.$watch('include', (val) => {
      pruneCache(this, (name) => matches(val, name))
    })
    this.$watch('exclude', (val) => {
      pruneCache(this, (name) => !matches(val, name))
    })
  },

  render() {
    /* 获取默认插槽中的第一个组件节点 */
    const slot = this.$slots.default
    const vnode = getFirstComponentChild(slot)
    /* 获取该组件节点的componentOptions */
    const componentOptions = vnode && vnode.componentOptions

    if (componentOptions) {
      /* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */
      const name = getComponentName(componentOptions)

      const { include, exclude } = this
      /* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */
      if (
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      /* 获取组件的key值 */
      const key =
        vnode.key == null
          ? componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : '')
          : vnode.key
      /*  拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        remove(keys, key)
        keys.push(key)
      } else {
        /* 如果没有命中缓存,则将其设置进缓存 */
        cache[key] = vnode
        keys.push(key)
        // prune oldest entry
        /* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
      }

      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  },
}
function pruneCacheEntry(
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key]
  /* 判断当前没有处于被渲染状态的组件,将其销毁*/
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}

keep-alive 的最强大缓存功能是在 render 函数中实现;

首先获取组件的 key 值:

const key =
  vnode.key == null
    ? componentOptions.Ctor.cid +
      (componentOptions.tag ? `::${componentOptions.tag}` : '')
    : vnode.key

拿到 key 值后去 this.cache 对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存,如下:

/* 如果命中缓存,则直接从缓存中拿 vnode 的组件实例 */
if (cache[key]) {
  vnode.componentInstance = cache[key].componentInstance
  /* 调整该组件key的顺序,将其从原来的地方删掉并重新放在最后一个 */
  remove(keys, key)
  keys.push(key)
}

直接从缓存中拿 vnode 的组件实例,此时重新调整该组件 key 的顺序,将其从原来的地方删掉并重新放在 this.keys 中最后一个

this.cache 对象中没有该 key 值的情况,如下:

/* 如果没有命中缓存,则将其设置进缓存 */
else {
    cache[key] = vnode
    keys.push(key)
    /* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */
    if (this.max && keys.length > parseInt(this.max)) {
        pruneCacheEntry(cache, keys[0], keys, this._vnode)
    }
}

表明该组件还没有被缓存过,则以该组件的 key 为键,组件 vnode 为值,将其存入 this.cache 中,并且把 key 存入 this.keys 中

此时再判断 this.keys 中缓存组件的数量是否超过了设置的最大缓存数量值 this.max,如果超过了,则把第一个缓存组件删掉

四、思考题:缓存后如何获取数据

解决方案可以有以下两种:

  • beforeRouteEnter
  • activated

每次组件渲染的时候,都会执行 beforeRouteEnter

beforeRouteEnter(to, from, next){
    next(vm=>{
        console.log(vm)
        // 每次进入路由执行
        vm.getData()  // 获取数据
    })
},

在 keep-alive 缓存的组件被激活的时候,都会执行 activated 钩子

activated(){
	this.getData() // 获取数据
},

四、$nextTick

五、生命周期

六、路由懒加载

七、Vuex 的用法

八、封装 Http 请求

九、按需加载 UI 组件

十、自定义指令 & 过滤器

十一、methods & computed & watch 的用法及区别

十二、组件间通信

十三、render 函数之 JSX 用法

十四、Vue 单元测试

💯 🚀

Vue 2.5.0+

Vuex beta

Vue-Resource废弃