Vue2 基础知识(二)

Posted by violetks on January 2, 2023

Vue2 于 2023年12月31日停止维护,官网:https://v2.cn.vuejs.org/

一、computed 和 watch 有什么区别?应用场景?侦听的是 data 里面的值吗?(★)

1、功能上computed是计算属性,依赖其他的属性计算而得出最后的值,是用于定义基于数据之上的数据watch是侦听一个值的变化并做对应的操作,允许数据变化时执行异步操作。
2、缓存computed结果会被缓存,只有依赖的响应式属性变化才会重新计算。而watch在每次侦听的值发生变化时都会执行回调。
3、是否调用returncomputed中的函数必须要用return返回,watchreturn不是必须的。
4、应用场景:如果一个值依赖多个属性(多对一),用computed更好,如购物车结算,比如查询表单配置数组。如果一个值变化后会引起一系列操作或者多个值变化(一对多),用watch更好,如搜索框。
5、在computed中不能对 data 中的属性赋值,会导致死循环。watch侦听复杂数据类型时需用深度侦听(在被侦听对象中使用handler)。特殊情况下(更改数组中的数据时,数组已经更改,但是视图没有更新),watch无法侦听到数组的变化。更改数组必须要用 splice() 或 $set。
6、计算属性默认只有getter,需要时也可提供一个setter
7、watch可以侦听 data 中的数据,还可以侦听 props、$route、$emit、computed 里的数据。
8、侦听 API 用法:vm.$watch
9、watch的两个属性:immediate(初始化时调用)和deep(深度侦听对象属性值的变化)。
10、在一个组件中使用多个watch侦听多个值时,它们的执行顺序是随机不受控制的,尽量少用watch

二、Vue 监测数据的原理

1、Vue 会监视 data 中所有层次的数据。
2、如何监测对象中的数据?
通过setter实现监视,且要在 new Vue 创建实例对象时就传入要监测的数据。
(1)对象中后追加的属性,Vue 默认不做响应式处理。
(2)如需给后添加的属性做响应式,请使用如下 API:Vue.set(target,propertyName/index,value)vm.$set()
3、如何监测数组中的数据?
通过包裹数组更新元素的方式实现,本质就是做了两件事:
(1)调用原生对应的方法对数组进行更新。
(2)重新解析模板,进而更新页面。
4、在 Vue 修改数组中的某个元素一定要用如下方法:
(1)使用这些 API:push()pop()shift()unshift()splice()sort()reverse()
(2)使用Vue.set()vm.$set(),注意Vue.set()vm.$set()不能给 vm 或 vm 的根数据对象添加属性。

三、$set 的作用

由于 Javascript 的限制,Vue 中并不是任何时候数据都是双向绑定的,这时可以使用$set解决双向绑定失效的问题。
出现双向绑定失效的情况:
(1)利用索引直接设置一个数组项,例如:vm.items[index] = newValue。
(2)修改数组的长度,例如:vm.items.length = newLength。
(3)对象属性的添加或删除操作。

var vm = new Vue({
  data: {
    a: 1
  }
})

// `vm.a` 是响应式的

vm.b = 2
// `vm.b` 是非响应式的

// 添加响应式属性
Object.assign(this.someObject, { a: 1, b: 2 }) // 不会响应
// 需要用原对象与要混合进去的对象一起创建一个新的对象
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

四、Vue 的生命周期有哪些?它们有什么不同?

Vue 生命周期是指 Vue 实例对象从创建之初到销毁的过程。Vue 的生命周期总共分为 8 个阶段:创建前后,载入前后,更新前后,销毁前后。beforeCreatecreatedbeforeMountmountedbeforeUpdateupdatebeforeDestroydestroyed第一次加载页面会触发前四个钩子

生命周期钩子 组件状态 响应类型 最佳实践
beforeCreate 实例初始化之后,this 指向创建的实例,不能访问到 data、computed、watch、methods 上的方法和数据 拿不到任何信息,无法篡改数据,一般做 loading,此时的 Vue 实例还什么都没有,但是$route对象是存在的,可以根据路由信息进行重定向之类的操作。 常用于初始化非响应式变量
created 实例创建完成,可以访问方法和数据,还未挂载到 DOM,不能访问到$el属性,$ref属性内容为空数组 $el,没有初始化,数据已加载完成,可以篡改数据,并更新,不会触发beforeUpdateupdated,在这结束 loading,还做一些初始化,实现函数自执行,$ref属性内容为空数组。定义 getter、setter 存储器属性,在实例创建之后被调用,该阶段可以访问 data,可以使用 this。该阶段允许执行 HTTP 请求操作。 常用于简单的 Ajax 请求,页面的初始化
beforeMount 在挂载开始之前被调用,beforeMount 之前,会找到对应的 template,并编译成 render 函数 $el 已被初始化,数据已加载完成,可以篡改数据,并更新,不会触发 beforeUpdate,updated。将 HTML 解析生成 AST 节点,再根据 AST 节点动态生成渲染函数。相关 render 函数首次被调用 /
mounted 实例挂载到 DOM 上,此时可以通过 DOM API 获取到 DOM 节点,$ref 属性可以访问 $el 已被初始化,数据已加载完成,可以篡改数据,并更新,并且触发 beforeUpdate,updated,在这发起后端请求,拿回数据,配合路由钩子做一些事情。在挂载完成之后被调用,执行 render 函数生成虚拟 DOM,创建真实 DOM 替换虚拟 DOM,并挂载到实例。可以操作 DOM,比如事件监听 常用于获取信息和操作,Ajax请求
beforeUpdate 响应式数据更新时调用,发生在虚拟 DOM 打补丁之前 $vm.data 更新之后,虚拟 DOM 重新渲染之前被调用。在这个钩子可以修改 $vm.data,并不会触发附加的重渲染过程。 适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器
updated 虚拟 DOM 重新渲染和打补丁之后调用,组件 DOM 已经更新,可执行依赖于 DOM 的操作 虚拟 DOM 重新渲染后调用,若再次修改 $vm.data,会再次触发 beforeUpdate、updated,进入死循环 避免在这个钩子函数中操作数据,可能陷入死循环
beforeDestroy 实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例 / 常用于销毁定时器、解绑全局事件、销毁插件对象等操作
destroyed 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 / /

lbhGrR.jpg

五、数据请求在生命周期哪一个阶段?created 和 mounted 之间的区别?

一般在created(或 beforeRouter)里面就可以,如果涉及到需要页面加载完成之后的话就用mounted。主要区别在于是否需要操作 DOM 结构
1、在created的时候,视图中的 HTML 并没有渲染出来,所以此时如果直接去操作 DOM 节点,一定找不到相关元素。
2、在mounted中,HTML 已经渲染出来了,可以直接操作 DOM 节点,document.getelementById生效。

六、Vue 组件基础

Vue 组件是可复用的 Vue 实例,它们与new Vue接收相同的选项,例如datacomputedwatchmethods以及生命周期钩子等。注意每个组件都会各自独立维护它的数据,每用一次组件,就会有一个它的新实例被创建。
Vue 中使用组件的三大步骤:1、创建组件,使用Vue.extend(options)。2、注册组件:全局注册使用 Vue.component(“组件名”, 组件),局部注册使用 new Vue 时传入 components 选项。3、使用组件:写组件标签。

// 创建并注册一个名为 button-counter 的全局组件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
// 注册一个局部组件
components: {...}
<!-- 使用组件 -->
<button-counter></button-counter>

com-tree.png

七、Vue 中,data 属性为什么必须声明为一个返回初始数据的函数?

data 是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,互不影响。如果不用 function 返回,每个组件的 data 都是内存的同一个地址,一个实例中数据改变了其他实例的也跟着改变。data 函数中的 this 是 Vue 实例对象。

//如果直接赋值,两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改
var MyComponent = function() {}
MyComponent.prototype.data = {
  a: 1,
  b: 2
}

//两个组件实例,通过<my-component>调用,创建的两个实例
var component1 = new MyComponent()
var component2 = new MyComponent()

component1.data.a === component2.data.a;  //true
component1.data.b = 5;
component2.data.b;  //5
Vue.component('my-component', {
  template: '<div>OK</div>',
  data () {
    return {} //返回一个唯一的对象,不要和其他组件共用一个对象进行返回
  }
})

八、Props 配置项

1、功能:让组件接收外部传过来的数据。
2、Prop 类型:
(1)以字符串数组形式列出 prop:

props: ['title', 'likes', 'isPublished', 'commentIds', 'author']

(2)限制类型,每个 prop 都有指定的值类型,以对象形式列出 prop:

props: {
  title: String,
  likes: Number,
  isPublished: Boolean,
  commentIds: Array,
  author: Object,
  callback: Function,
  contactsPromise: Promise // or any other constructor
}

(3)Prop 验证,为 props 中的值提供一个带有验证需求的对象:

// 类型检查:String、Number、Boolean、Array、Object、Date、Function、Symbol、一个自定义的构造函数

// 构造函数
function Person (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

props: {
  // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
  propA: Number,
  // 多个可能的类型
  propB: [String, Number],
  // 必填的字符串
  propC: {
    type: String,
    required: true
  },
  // 带有默认值的数字
  propD: {
    type: Number,
    default: 100
  },
  // 带有默认值的对象
  propE: {
    type: Object,
    // 对象或数组默认值必须从一个工厂函数获取
    default: function () {
      return { message: 'hello' }
    }
  },
  // 自定义验证函数
  propF: {
    validator: function (value) {
      // 这个值必须匹配下列字符串中的一个
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // 构造函数:来验证是否是通过 new Person 创建的。
  author: Person
}

3、单向数据流:所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行,子组件不能修改从 props 获取到的数据。

九、组件的自定义事件(★)

1、一种组件间通讯的方式。适用于:子组件给父组件传值。
2、使用场景:子组件想给父组件传数据,那么就要在父组件中给子组件绑定自定义事件(事件的回调在父组件中)。
3、在父组件中给子组件绑定自定义事件的两种方式:

<!-- 方法一 -->
<Child @custom="test" />
<!-- 方法二:用 $on,this.test 是在父组件中定义的回调 -->
<Child ref="child" />

mounted () {
  this.$refs.child.$on("custom", this.test)
}

若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
4、触发自定义事件:在子组件中,this.$emit("custom", 数据)
5、解绑自定义事件:this.$off("custom")
6、组件上也可以绑定原生 DOM 事件,需要使用native修饰符。
7、注意:通过this.$refs.xxx.$on("custom", 回调)绑定自定义事件时,回调要么配置在 methods 中,要么用箭头函数,否则 this 指向会出问题。
8、自定义组件的v-model:一个组件上的v-model默认会利用名为value的 prop 和名为input的事件,但是像单选框、复选框等类型的输入控件金可能会将value用于不同的目的。model选项可以用来避免这种冲突:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})
<base-checkbox v-model="lovingVue"></base-checkbox>

在这个组件上使用v-model的时候:这里的lovingVue的值将会传入这个名为checked的 prop。同时当<base-checkbox>触发一个change事件并附带一个新的值的时候,这个lovingVue的 property 将会被更新。注意你仍然需要在组件的props选项里声明checked这个 prop。
9、.sync修饰符:可以实现子组件与父组件的双向绑定,并且可以实现子组件同步修改父组件的值。
具体解释:一般情况下,想要实现父子组件间值的传递,通常使用的是 props 和自定义事件 $emit。
其中,父组件通过 props 将值传给子组件,子组件再通过 $emit 将值传给父组件,父组件通过事件监听获取子组件传过来的值。
如果想要简化这里的代码,可以使用.sync修饰符,实际上就是一个语法糖。

<!-- Father.vue -->
<template>
  <div class="app">
    {{ dataApp }}
    <hr>
    <!-- 两句作用等同 -->
    <Child :money.sync="dataApp"></Child>
    <Child :money="dataApp" v-on:update:money="dataApp = $event"></Child>
  </div>
</template>

<script>
import Child from "./Child.vue";

export default {
  data () {
    return { dataApp: 10000 };
  },
  components: { Child }
};
</script>
<!-- Child.vue -->
<template>
  <div class="child">
    {{ money }}
    <button @click="$emit('update:money', money-100)"></button>
  </div>
</template>

<script>
export default {
  props: ["money"]
};
</script>

上面例子中,父组件传了一个参数 money 给子组件,子组件通过 $emit 修改 money 后,将值同步到父组件。

十、Vue 实现双向绑定的两种方法(★)

1、v-model:常用于表单与组件之间的数据双向绑定。
2、表单实现双向绑定
(1)原理:v-bind 绑定一个 value 属性,v-on 指令给当前元素绑定 input 事件 可看出v-model绑定在表单上时,v-model其实就是 v-bind 绑定 value 和 v-on 监听 input 事件的结合体。

v-model = v-bind:value + v-on:input

2、实现:用 v-bind:value + v-on:input 来模拟实现 v-model。

<input type="text" :value="username" @input="username = $event.target.value" />

例子解释:通过 v-bind:value 绑定 username 变量,每次输入内容的时候触发 input 事件,通过事件对象参数 $event.target.value 获得输入的内容,并且把这个内容赋值给 username。此时更改 username 时 input 输入框会变化,更改 input 输入框时 username 变量会变,从而实现了 v-model 的双向绑定功能。
3、组件上的双向绑定
(1)原理:v-model 绑定在组件上做了以下步骤:在父组件内给子组件标签添加 v-model,其实就是给子组件绑定了 value 属性。子组件内使用 prop 创建 value 属性可以拿到父组件传递下来的值,名字必须是 value。子组件内部更改 value 的时候,必须通过 $emit 派发一个 input 事件,并携最新的值。v-model 会自动监听 input 事件,把接收到的最新的值同步赋值到 v-model 绑定的变量上。
2、实现:

<!-- 父组件中 -->
<子组件标签 v-model="父组件定义的变量值" />
<!-- 子组件中 -->
<template>
  <p>子组件库存:{{ value }}</p>
  <button @click="addFun">增加1</button>
</template>
<script>
  export default {
    props: {
      value: {
        type: Number,
        default: 0
      }
    },
    methods: {
      addFun() {
        this.$emit('input', this.value + 1)
      }
    }
  }
</script>

4、.sync修饰符实现双向绑定,详见:九、组件的自定义事件。

十一、Vue 组件之间的传值(★)

1、父传子
子组件通过props 属性接收从父组件传过来的值,父组件传值的时候使用 v-bind 将子组件中预留的变量名绑定为 data 里面的数据。

<!-- 在 .vue 文件中的写法 -->
<!-- 子组件 -->
<template>
  <h3>{{msg}}</h3>
</template>
<script>
export default {
  props: {
    msg: String
  }
  // 或者 props: ['msg']
};
</script>

<!-- 父组件 -->
<template>
  <div>
    <input type="text" v-model="message" @change="dataChange">
    <Child :msg="message"></Child>
  </div>
</template>
<script>
import Child from "@/components/Child";
export default {
  data () {
    return {
      message: "父组件的值"
    };
  },
  methods: {
    dataChange (data) {
      this.msg = data;
    }
  },
  components: { Child }
}
</script>
<!-- 在 .html 文件中的写法 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>组件之间通信</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<!-- 父组件里调用子组件 -->
<div id="parent">
    <Child message="第一次调用" imgsrc="images/ad1.png"></Child>
    <Child message="第二次" imgsrc="images/ad2.png"></Child>
</div>

<!-- 定义一个子组件 -->
<template id="child">
    <div>
        <h1>{{message}}</h1>
        <img :src="imgsrc" width="300">
    </div>
</template>

<script>
    Vue.component('Child', {
        template: '#child',
        props: ['message', 'imgsrc']
    });

    new Vue({
        el: '#parent'
    })
</script>
</body>
</html>

2、子传父
首先子组件中用按钮来触发setData事件,在setData中用this.$emit来遍历父组件中getData事件,最后返回this.msg

<!-- 子组件 -->
<template>
  <input type="text" v-model="msg">
  <button @click="setData">传递到父组件</button>
</template>
<script>
export default {
  data () {
    return {
      msg: "传递给父组件的值"
    };
  },
  methods: {
    setData () {
      this.$emit("getData", this.msg);
    }
  }
};
</script>

<!-- 父组件 -->
<template>
  <div>
    <Child @getData="getData"></Child>
    <p>{{message}}</p>
  </div>
</template>
<script>
import Child from "@/components/Child";
export default {
  data () {
    return {
      message: "父组件默认值"
    };
  },
  methods: {
    getData (data) {
      this.msg = data;
    }
  },
  components: { Child }
}
</script>

3、兄弟组件互相传值
(1)通过事件总线(EventBus),即通过on监听,emit触发的方式。
①创建一个bus.js文件,里面定义一个新的 Vue 实例专门用于传递数据,并导出。

// bus.js
import Vue from "vue";
export default new Vue;

②在子组件 A 里定义传递的方法名和传输内容,点击事件或钩子函数触发bus.$emit事件。

<!-- 子组件 A -->
<template>
  <button @click="sendToB">点击传参给B组件</button>
</template>

<script>
import bus from "./bus.js";
export default {
  data () {
    return {
      msg: "这是A要传给B的数据"
    }
  },
  methods: {
    sendToB () {
      bus.$emit("getData", this.msg);
    }
  }
}
</script>

③在子组件 B 接收传递过来的数据,通过bus.$on监听。

<!-- 子组件 B -->
<template>
  <p>组件A传过来的数据:{{ msg }}</p>
</template>

<script>
import bus from "./bus.js";
export default {
  data () {
    return {
      msg: ""
    }
  },
  mounted () {
    bus.$on("getData", (params) => {
      this.msg = params;
    })
  }
}
</script>

④记得解绑eventBus

bus.$off("getData");

(2)全局事件总线(GlobalEventBus),适用于任意组件间通信。
①安装全局事件总线:

new Vue({
  ...
  beforeCreate () {
    Vue.prototype.$bus = this; // 安装全局事件总线,$bus 就是当前应用的 vm
  }
})

②使用事件总线:
接收数据:A 组件想接收数据,则在 A 组件中给$bus绑定自定义事件。事件的回调留在 A 组件自身。

methods: {
  demo (data) { ... }
}
...
mounted () {
  this.$bus.$on("xxx", this.demo)
}

提供数据:this.$bus.$emit("xxx", 数据);
③最好在beforeDestroy中用$off去解绑当前组件所用到的事件。
(3)通过 Vuex 状态管理传值:先通过 npm 下载 vuex,创建 store.js 文件。

//store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const state = { name: 'Violetks' };
const mutations = {
  newName (state, message) {
    state.name = message;
  }
}
export default new Vuex.Store({ state, mutations })

(4)使用$root

//一个子组件触发
this.$root.$emit('触发的方法名'需要传递的值);
//一个子组件监听
this.$root.$off("方法名");//每次进入先关闭一下
this.$root.$on("方法名", (传递的值) => { })

十二、$nexTick

1、$nextTick所指定的回调会在浏览器更新 DOM 完毕之后再执行。
2、例子:从后端请求到一个列表数据后,前端直接重新赋值时页面不更新。因为 Vue 更新 DOM 是异步更新,无法通过同步代码赋值后马上去更新页面。
3、$nextTick的原理:
当调用$nextTick方法时会传入两个参数,回调函数和执行回调函数的上下文环境,如果没有提供回调函数,那么将返回promise对象。首先将拿到的回调函数存放到数组中,判断是否正在执行回调函数,如果当前没有在 pending 的时候,就会执行 timeFunc,多次执行 nextTick 只会执行一次 timerFunc,timeFunc 其实就是执行异步的方法,在 timeFunc 方法中选择一个异步方法(首先判断是否支持 promise,如果支持就将 flushCallbacks 放在 promise 中异步执行,并且标记使用微任务。如果不支持 promise 就看是否支持 MutationObserver 方法,如果支持就 new 了一个 MutationObserver 类,创建一个文本节点进行监听,当数据发生变化了就会异步执行 flushCallbacks 方法。如果以上两个都不支持就看是否支持 setImmediate 方法,如果支持 setImmediate 就去异步执行 flushCallbacks 方法。如果以上三种方法都不支持,就使用 setTimeout),然后异步去执行 flushCallbacks 方法,flushCallbacks 中就是将传递的函数依次执行。
nextTick 多次调用会维持一个数组,之后会异步的把数组中的方法以此执行,这样的话用户就会在视图更新之后再获取到真实的 DOM 元素。
原理简述: nextTick 是 Vue 提供的一个全局的 API ,由于 Vue 的异步更新策略导致我们对数据的修改不会立马体现到都没变化上,此时如果想要立即获取更新后的 DOM 的状态,就需要使用这个方法。
Vue 在更新 DOM 时是异步执行的。只要监听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓存时去重对于避免不必要的计算和 DOM 操作是非常重要的。nextTick 方法会在队列中加入一个回调函数,确保该函数在前面的 DOM 操作完成后才调用。

十三、Vue 的 slot 插槽(匿名&具名&作用域)

Vue 实现了一套内容分发的 API,将<slot></slot>元素作为承载分发内容的出口,没有插槽的情况下在组件标签内一些内容是不起任何作用的。
1、默认插槽:适用于子组件中只定义一个插槽的情况,在子组件中使用<slot></slot>添加默认插槽,可以设置插槽默认内容。

<!-- 父组件中调用子组件 Child -->
<Child>
  <div>父组件想在插槽中展示的内容</div>
</Child>

<!-- 子组件中定义插槽 -->
<template>
  <div>
    <slot>插槽默认内容</slot>
  </div>
</template>

2、具名插槽:当需要在子组件中使用多个插槽时,可以使用<slot>的属性:name添加插槽名用于区分,在父组件中使用插槽时有以下两种方式。
注意v-slot只能添加在<template>上。

<!-- 父组件中 -->
<Child>
  <!-- 方式一 -->
  <template slot="center">
    <div>父组件想在center插槽中展示的内容</div>
  </template>
  <!-- 方式二 -->
  <template v-slot:footer>
    <div>父组件想在footer插槽中展示的内容</div>
  </template>
</Child>

<!-- 子组件中 -->
<template>
  <div>
    <slot name="center">插槽默认内容</slot>
    <slot name="footer"></slot>
  </div>
</template>

3、作用域插槽:作用域插槽就是父组件在调用子组件的时候给子组件传了一个插槽,这个插槽为作用域插槽,该插槽必须放在<template>标签里面,同时声明从子组件接收的数据放在一个自定义属性内,并定义该数据的渲染方式。
在 2.6.0 中,为具名插槽和作用域插槽引入了一个v-slot指令,它取代了slotslot-scope

<!-- 父组件中 -->
<Child>
  <template v-slot:default="scopeData">
    <ul>
      <li v-for="item in scopeData.list" :key="item">{{ item }}</li>
    </ul>
  </template>
</Child>

<Child>
  <template slot-scope="scopeData">
    <h4 v-for="item in scopeData.list" :key="item">{{ item }}</h4>
  </template>
</Child>

<!-- 子组件中 -->
<template>
  <div>
    <slot :list="list"></slot>
  </div>
</template>

<script>
export default {
  name: "Child",
  props: ["title"],
  data () {
    return {
      list: [a, b, c, d]
    }
  }
}
</script>

4、动态插槽名。

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

5、具名插槽的缩写:v-slot:header可以简写为#header

十四、异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

如你所见,这个工厂函数会收到一个resolve回调,这个回调函数会在你从服务器得到组件定义的时候被调用。你也可以调用reject(reason)来表示加载失败。这里的setTimeout是为了演示用的,如何获取组件取决于你自己。一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 `require` 语法将会告诉 webpack
  // 自动将你的构建代码切割成多个包,这些包
  // 会通过 Ajax 请求加载
  require(['./my-async-component'], resolve)
})

你也可以在工厂函数中返回一个Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以这样使用动态导入:

Vue.component(
  'async-webpack-example',
  // 这个动态导入会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

当使用局部注册的时候,你也可以直接提供一个返回Promise的函数:

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})

处理加载状态:这里的异步组件工厂函数也可以返回一个如下格式的对象:

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})