查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选

vue的基本使用和高级特性,周边插件vuex和vue-router

  • 一、vue的使用
    • 1、vue-cli
    • 2、基本使用
      • (1)模板(插值,指令)
      • (2)computed和watch
      • (3)class和style
      • (4)条件
      • (5)循环(列表)渲染
      • (6)事件
      • (7)表单
    • 3、组件
      • (1)props和$emit(适合父子组件间的通信)
      • (2)组件间通信 - 自定义事件
      • (3)组件生命周期
  • 二、vue的高级特性
    • 1、自定义 v-model
    • 2、$nextTick
      • (1)知识点
      • (2)例子展示:chestnut:
    • 3、slot插槽
      • (1)插槽的作用
      • (2)三种插槽类型
        • 1)普通slot插槽
        • 2)作用域插槽slot-scope
        • 3)具名插槽
    • 4、动态、异步组件
      • (1)动态组件
        • 1)举个例子:chestnut:
        • 2)动态组件的用法和应用场景
        • 3)演示
      • (2)异步组件
    • 5、keep-alive
      • (1)定义和应用场景
      • (2)举例:chestnut:
    • 6、mixin
      • (1)mixin是什么
      • (2)mixin的问题
      • (3)举个栗子:chestnut:
  • 三、vue的周边插件:vuex和vue-router
    • 1、vuex
      • (1)vuex基本概念
      • (2)用于vue组件中的API
      • (3)图例
    • 2、vue-router
      • (1)路由模式
      • (2)路由配置
  • 四、结束语

以下文章将讲解对 vue 的基本使用以及各种高级特性还有周边插件 vuexvue-router ,融合大量案例和动图️进行展示。可以把它当成是 vue 的入门宝库,有不懂的语法知识点时或许在这里可以寻找到你的答案并且通过例子运用起来。

废话不多说,下面来开始探索 vue 的奥秘吧

一、vue的使用

1、vue-cli

vue 项目是基于 vue-cli 脚手架搭建的项目。当我们要创建一个项目时,首先要先全局安装 vue-cli 脚手架,命令行为:

npm i -g @vue/cli

在搭建完成项目以后,我们需要来了解 src 目录下各个文件夹和文件的用法。

├── assets 放置静态资源
├── components 放组件
├── router 定义路由的相关配置
├── views 视图
├── views 视图
├── app.vue 应用主组件
├── main.js 入口文件

2、基本使用

(1)模板(插值,指令)

1)插值、表达式

<template>
    <div>
        <p>文本插值 {
    {message}}p>
        <p>JS 表达式 {
    { flag ? 'yes' : 'no' }} (只能是表达式,不能是 js 语句)p>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            message: 'I am Monday.',
            flag: true
        }
    }
}
script>
//浏览器显示结果
// 文本插值 I am Monday.
// JS 表达式 yes

2)动态属性

<template>
    <div>
        <p :id="dynamicId">动态属性:{
    {dynamicId}}p>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            dynamicId: `id-${ Date.now()}`
        }
    }
}
script>

//浏览器显示结果
//动态属性:id-1622339576875

3)v-html指令

<template>
    <div>
        <p v-html="rawHtml">
            <span>有 xss 风险span>
            <span>【注意】使用 v-html 之后,将会覆盖子元素span>
        p>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            rawHtml: '指令 - 原始 html 加粗 斜体',
        }
    }
}
script>

//浏览器显示结果
//指令 - 原始 html 加粗 斜体

值得注意的是, v-html 指令会有 xss 风险,且会覆盖子组件,要谨慎使用!!

(2)computed和watch

1)computed

computed 有缓存data 不变则不会重新计算。如下代码所示:

<template>
    <div>
        <p>num {
    {num}}p>
        <p>double1 {
    {double1}}p>
        //用v-model一定要有get和set,否则会报错
        <input v-model="double2"/>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            num: 20
        }
    },
    computed: {
       
        double1() {
       
            return this.num * 2
        },
        double2: {
       
            // 获取值
            get() {
       
                return this.num * 2
            },
            // 设置值
            set(val) {
       
                this.num = val/2
            }
        }
    }
}
script>

此时浏览器的打印结果如图所示。

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第1张图片

大家可以看到,当 num 的值如果一直是 20 的值时,那么 double1double2get() 方法是不会重新计算的,它会被缓存下来,达到提高运算性能的效果。

2)watch

  • watch 监听基本数据类型时,可正常拿到 oldValval 的值。
  • watch 如果监听引用数据类型时,需要进行深度监听,且拿不到 oldVal 。因为指针相同,监听时,指针已经指向了新的 val

如下代码所示:

<template>
    <div>
        <input v-model="name"/>
        <input v-model="info.city"/>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            name: 'Monday',
            info: {
       
                city: 'FuZhou'
            }
        }
    },
    watch: {
       
        name(oldVal, val) {
       
            // eslint-disable-next-line
            console.log('watch name', oldVal, val) // 值类型,即基本数据类型,可正常拿到 oldVal 和 val
        },
        info: {
       
            handler(oldVal, val) {
       
                // eslint-disable-next-line
                console.log('watch info', oldVal, val) // 引用数据类型,拿不到 oldVal 。因为指针相同,此时已经指向了新的 val
            },
            deep: true // 深度监听
        }
    }
}
script>

(3)class和style

  • 使用动态属性,即 v-bind 绑定;

  • 使用驼峰式写法。

如下代码所示:

<template>
    <div>
        <p :class="{ black: isBlack, yellow: isYellow }">使用 classp>
        <p :class="[black, yellow]">使用 class (数组)p>
        <p :style="styleData">使用 stylep>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            isBlack: true,
            isYellow: true,

            black: 'black',
            yellow: 'yellow',

            styleData: {
       
                fontSize: '40px', // 转换为驼峰式
                color: 'red',
                backgroundColor: '#ccc' // 转换为驼峰式
            }
        }
    }
}
script>

<style scoped>
    .black {
       
        background-color: #999;
    }
    .yellow {
       
        color: yellow;
    }
style>

此时浏览器的显示效果如下。

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第2张图片

(4)条件

  • v-ifv-else 的用法:可使用变量,也可以使用 === 表达式。
  • v-ifv-show 的区别?
    • v-if 会根据条件对元素进行渲染的控制,此处的控制渲染指的是将元素添加到 dom 中或移除 dom,所以会存在dom的增删。假设页面初始化时,条件判断结果为 false ,则不会将该元素添加到 dom 当中。
    • v-showv-if 不一样的是,不管条件是 true 还是 false ,都会将对应的元素添加到 dom 中,在条件为 false 时将元素的 css 属性display设置为none,所以我们只是肉眼看到元素从页面中消失了,但是它还存在于 dom 当中。
  • v-ifv-show 的使用场景?
    • v-if 适用于条件变化频率不高的时候,这样不会频繁的去对 DOM 进行增删操作;
    • v-show 适用于需要做频繁切换的时候,比如说A和B两个元素,需要一会显示 A ,一会显示 B ,这样就算比较频繁。如果去操作 v-if ,让 DOM 疯狂的增添和销毁是会非常耗费性能的,所以这个时候就应该用 v-showdisplay 属性原本就已经存放在 DOM 当中,只是对 display 属性进行修改修做即可。

具体使用方式如下代码所示:

<template>
    <div>
        <p v-if="type === 'a'">Ap>
        <p v-else-if="type === 'b'">Bp>
        <p v-else>otherp>

        <p v-show="type === 'a'">A by v-showp>
        <p v-show="type === 'b'">B by v-showp>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            type: 'a'
        }
    }
}
script>

(5)循环(列表)渲染

  • vue 中如何遍历对象? —— 使用 v-for

  • v-forv-if 不能一起使用。

  • key 的重要性。在对数据进行 v-for 遍历时,需要加上 key 值来保证数据的唯一性。同时, key 不能乱写,一般不要使用 random 或者 index ,而是绑定一个与该绑定数据相关的唯一值来确定。如果用 random 或者 index 时,在对数据进行删除或增添操作时,有可能会出现数据错乱等问题出现。

  • key 的作用。在对节点进行 diff 的过程中,判断两个节点是否为相同节点的一个很重要的条件就是 key 值是否相等,如果 key 值相等,则说明两个节点是相同节点,所以会尽可能的复用原有的 DOM 节点,减少不必要的 DOM 操作,提升系统性能。

下面用一段代码演示 v-for 遍历数组和遍历对象时的效果。

<template>
    <div>
        <p>遍历数组p>
        <ul>
            <li v-for="(item, index) in listArr" :key="item.id">
                {
    {index}} - {
    {item.id}} - {
    {item.title}}
            li>
        ul>

        <p>遍历对象p>
        <ul >
            <li v-for="(val, key, index) in listObj" :key="key">
                {
    {index}} - {
    {key}} -  {
    {val.title}}
            li>
        ul>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            listArr: [
                {
        id: 'a', title: '今天周一' }, // 在数据结构中,最好有 id ,目的是为了方便使用 key
                {
        id: 'b', title: '今天周二' },
                {
        id: 'c', title: '今天周三' }
            ],
            listObj: {
       
                a: {
        title: '今天周一' },
                b: {
        title: '今天周二' },
                c: {
        title: '今天周三' },
            }
        }
    }
}
script>

此时浏览器的显示效果如下所示:
查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第3张图片

(6)事件

1)event参数,自定义参数

下面通过绑定一个两个按钮事件,来观察 vue 中的事件,参数时怎么进行传递的,事件又会被绑定到哪里去?

<template>
    <div>
        
        
        <button @click="increment1">+1button>
        
        <button @click="increment2(2, $event)">+2button>
        <div>递增值:{
    {num}}div>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            num: 0
        }
    },
    methods: {
       
        increment1(event) {
       
            console.log('event', event, event.__proto__.constructor) // 是原生的 event 对象
            console.log(event.target) //vue中的event,放在什么元素下,就会被挂载在什么元素下
            console.log(event.currentTarget) // 注意,事件是被注册到当前元素的,和 React 不一样
            this.num++

            // 1. event 是原生的
            // 2. 事件被挂载到当前元素
            // 和 DOM 事件一样
        },
        increment2(val, event) {
       
            console.log(event.target)
            this.num = this.num + val
        }
    }
}
script>

大家可以看到,上面的 increment1 方法,没有传递参数,则它可以直接使用 event 来触发当前对象;而 increment2 方法中,通过传递了一个 2 的参数,我们可以通过 $event 的方式,把 2 传递进来,之后同样用 event 触发原生对象。

同时,当触发到当前元素时,事件会被挂载到当前元素,和 DOM 事件一样。

此时浏览器的打印效果如下:

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第4张图片

2)自定义事件

上面的演示中, vue 事件是直接挂载到 vue 上面进行监听的。而下面的代码中,我们可以看到,如果我们需要自定义一个事件,而不进行直接挂载,则可以通过 addEventListener 来进行注册。

但值得注意的是,用 vue 绑定的事件,组件销毁时会自动进行解绑。但是呢,如果是自己定义的事件需要自己再进行手动销毁!!这一点需要特别注意,不然写代码过程很容易因为没有销毁热导致后面引发的一系列 bug 出现。

<template>
    <div>
        
    </div>
</template>

<script>
export default {
     
    data() {
     
        return {
     
            num: 0
        }
    },
    methods: {
     
        loadHandler() {
     
            // do some thing
        }
    },
    mounted() {
     
        window.addEventListener('load', this.loadHandler)
    },
    beforeDestroy() {
     
        //【注意】用 vue 绑定的事件,组建销毁时会自动被解绑
        // 但如果是,自己绑定的事件,需要自己销毁!!!
        window.removeEventListener('load', this.loadHandler)
    }
}
</script>

3)事件修饰符

对于 vue 来说,经常用到的修饰符有下面6种事件修饰符3种按键修饰符




<a v-on:stop="doThis">a>


<form v-on:submit.prevent="onSubmit">form>


<a v-on:click.stop.prevent="doThat">a>


<form v-on:submit.prevent>form>



<div v-on:click.capture="doThis">...div>



<div v-on:click.self="doThat">...div>



<button @click.ctrl="onClick">Abutton>


<button @click.ctrl.exact="onCtrlClick">Abutton>


<button @click,exact="onClick">Abutton>

(7)表单

在vue项目中,主要使用 v-model 指令在表单 inputtextareacheckboxradioselect 等元素上创建双向数据绑定

对于 v-model 来说,常见的修饰符有 trimlazynumbertrim 表示去除输入内容前后的空格, lazy 表示需要按回车键数据才会进行创建, number 表示把用户输入的值转换为 number 类型,如果先输入字符,则整串数据都会被当成字符串 string 类型看待,如果先输入数字,后输入字母,则后面的字母无法输入到输入框当中。

接下来我们用代码演示一遍常见的表单。

<template>
    <div>
        <p>输入框: {
    {name}} {
    {age}}p>
        
        <input type="text" v-model.trim="name"/>
        
        <input type="text" v-model.lazy="name"/>
        
        <input type="text" v-model.number="age"/>
        <hr>
        <p>多行文本: {
    {intro}}p>
        <textarea v-model="intro">textarea>
        
        <hr>
        <p>复选框 {
    {checked}}p>
        <input type="checkbox" v-model="checked"/>
        <hr>
        <p>多个复选框 {
    {checkedNames}}p>
        <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
        <label for="jack">Jacklabel>
        <input type="checkbox" id="john" value="John" v-model="checkedNames">
        <label for="john">Johnlabel>
        <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
        <label for="mike">Mikelabel>
        <hr>
        <p>单选 {
    {gender}}p>
        <input type="radio" id="male" value="male" v-model="gender"/>
        <label for="male">label>
        <input type="radio" id="female" value="female" v-model="gender"/>
        <label for="female">label>
        <hr>
        <p>下拉列表选择 {
    {selected}}p>
        <select v-model="selected">
            <option disabled value="">请选择option>
            <option>Aoption>
            <option>Boption>
            <option>Coption>
        select>
        <hr>
        <p>下拉列表选择(多选) {
    {selectedList}}p>
        <select v-model="selectedList" multiple>
            <option disabled value="">请选择option>
            <option>Aoption>
            <option>Boption>
            <option>Coption>
        select>
    div>
template>

<script>
export default {
       
    data() {
       
        return {
       
            name: 'Monday',
            age: 18,
            intro: '自我介绍',

            checked: true,
            checkedNames: [],

            gender: 'female',

            selected: '',
            selectedList: []
        }
    }
}
script>

此时浏览器的显示效果如下:

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第5张图片

3、组件

(1)props和$emit(适合父子组件间的通信)

  • $emit 子组件像父组件传递数据
  • props 父组件的数据需要通过 props 把数据传给子组件
传递形式 通信方式
$emit 子组件向父组件传递数据
props 父组件的数据需要通过props把数据传给子组件,props的取值可以是数组也可以是对象

(2)组件间通信 - 自定义事件

除了父子组件通信外,一般还有兄弟间的组件通信。对于兄弟间的组件通信来说, 兄弟1 可以通过 event.$on 来绑定自定义事件,绑定完成之后,需要再 beforeDestroy 的生命周期里面用 event.$off及时销毁自定义事件, 防止内存泄漏发生。之后 兄弟2 通过 event.$emit 来调用 兄弟1 的自定义事件。

下面我们结合父子组件通信和兄弟组件通信的知识点,来实现一个添加和删除元素的功能。具体代码如下:

父组件index.vue:

<template>
    <div>
        
        <Input @add="addHandler"/>
        <List :list="list" @delete="deleteHandler"/>
    div>
template>

<script>
import Input from './Input'
import List from './List'

export default {
       
    components: {
       
        Input,
        List
    },
    data() {
       
        return {
       
            list: [
                {
       
                    id: 'id-1',
                    title: '标题1'
                },
                {
       
                    id: 'id-2',
                    title: '标题2'
                }
            ]
        }
    },
    methods: {
       
        addHandler(title) {
       
            this.list.push({
       
                id: `id-${ Date.now()}`,
                title
            })
        },
        deleteHandler(id) {
       
            // 过滤出不等于当前删除内容的元素
            this.list = this.list.filter(item => item.id !== id)
        }
    }
}
script>

子组件List.vue:

<template>
    <div>
        <ul>
            <li v-for="item in list" :key="item.id">
                {
    {item.title}}

                <button @click="deleteItem(item.id)">删除button>
            li>
        ul>
    div>
template>

<script>
import event from './event'

export default {
       
    // props: ['list']
    props: {
       
        // prop 类型和默认值
        list: {
       
            type: Array,
            default() {
       
                return []
            }
        }
    },
    data() {
       
        return {
       

        }
    },
    methods: {
       
        deleteItem(id) {
       
            this.$emit('delete', id)
        },
        addTitleHandler(title) {
       
            console.log('on add title', title)
        }
    },
    created() {
       
    },
    mounted() {
       
        // 绑定自定义事件
        //event是通过实例化一个vue对象,来调用vue中本身具有的自定义事件能力
        event.$on('onAddTitle', this.addTitleHandler)
    }
    beforeDestroy() {
       
        // 及时销毁,否则可能造成内存泄露
        event.$off('onAddTitle', this.addTitleHandler)
    }
}
script>

子组件Input.vue:

<template>
    <div>
        <input type="text" v-model="title"/>
        <button @click="addTitle">addbutton>
    div>
template>

<script>
import event from './event'

export default {
       
    data() {
       
        return {
       
            title: ''
        }
    },
    methods: {
       
        addTitle() {
       
            // 调用父组件的事件
            this.$emit('add', this.title)

            // 调用自定义事件,调用List.vue中自定义的事件
            event.$emit('onAddTitle', this.title)

            this.title = ''
        }
    }
}
script>

event.vue:

import Vue from 'vue'

export default new Vue()

此时浏览器的显示效果如下:

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第6张图片

(3)组件生命周期

1)组件生命周期(单个组件)

一般来说,组件生命周期的执行顺序为:挂载阶段 → 更新阶段 → 销毁阶段。下面给出常用组件生命周期的解析。

生命周期钩子 介绍
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
created 页面还没有渲染,但是vue的实例已经初始化结束。
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted 页面已经渲染完毕。
beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。
activated keep-alive 组件激活时调用。
deactivated keep-alive 组件停用时调用。
beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。常用场景有: 自定义事件的绑定要解除、setTimeout等定时任务需要销毁、自己绑定的window或者document事件需要销毁。
destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

2)组件生命周期(父子组件)

加载渲染过程

  父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

子组件更新过程

  父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程

  父beforeUpdate->父updated

销毁过程

  父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

二、vue的高级特性

vue的高级特性主要有以下6种:

  • 自定义 v-model
  • $nextTick
  • slot
  • 动态、异步组件
  • keep-alive
  • mixin

下面对这6种高级特性进行一一讲解。

1、自定义 v-model

在文章的前半部分我们有讲到了 v-model 在表单中的应用,那么接下来我们将动手来实现一个 v-model

第一步,我们先定义一个子组件,名字叫 CustomVModel.vue ,具体代码如下:

<template>
    
    <input type="text"
        :value="text1"
        @input="$emit('change1', $event.target.value)"
    >
    
template>

<script>
export default {
       
    model: {
       
        prop: 'text1', // 对应下面 props 的 text1
        event: 'change1'
    },
    props: {
       
        text1: String,
        default() {
       
            return ''
        }
    }
}
script>

第二步,我们在父组件中使用上面的这个子组件:

<template>
    <div>
        <p>vue 高级特性p>
        <hr>
        
        
        <p>{
    {name}}p>
        <CustomVModel v-model="name"/>
    div>
template>

<script>
import CustomVModel from './CustomVModel'

export default {
       
    components: {
       
        CustomVModel
    },
    data() {
       
        return {
       
            name: 'Monday'
        }
    }
}
script>

通过上面的代码我们可以发现,通过绑定 value 属性和 input 事件这两个语法糖,最终实现数据的双向绑定。

此时我们看下浏览器的显示效果。

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第7张图片

通过上图我们自己发现,结果跟实际的 v-model 结果是一样的。至此,我们就实现了自定义的 v-model ,以此来操作数据的双向绑定

2、$nextTick

(1)知识点

先来了解三个知识点:

  • Vue异步渲染
  • data 发生改变之后, DOM 不会立刻进行渲染;
  • $nextTick 会在 DOM 渲染之后被触发,以获取最新 DOM 节点。

(2)例子展示

假设我们现在要实现一个功能,当我们点击按钮时,打印出列表的项数。这个时候我们大多人可能会这么操作。

<template>
  <div id="app">
      
    <ul ref="ul1">
        <li v-for="(item, index) in list" :key="index">
            {
    {item}}
        li>
    ul>
    <button @click="addItem">添加一项button>
  div>
template>

<script>
export default {
       
  name: 'app',
  data() {
       
      return {
       
        list: ['a', 'b', 'c']
      }
  },
  methods: {
       
    addItem() {
       
        this.list.push(`${ Date.now()}`)
        this.list.push(`${ Date.now()}`)
        this.list.push(`${ Date.now()}`)
        
          // 获取 DOM 元素
          const ulElem = this.$refs.ul1
          console.log( ulElem.childNodes.length )
    }
  }
}
script>

此时浏览器的显示效果如下。

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第8张图片

细心的小伙伴已经发现,浏览器并没有按照我们所想的打印。当页面上的列表显示 6项 内容时,此时控制台只打印 3项 ;当显示 9项 时,此时空直接只打印 6项

那这究竟时为什么呢?

其实,当我们点击的那一刻, data 发生变化,但是 DOM 并不会立刻进行渲染。所以等到我们点击完成的时候,获取的元素还是原来触发的内容,而不会增添上新的内容。

那我们所期望的是,当点击之后立刻触发 DOM 渲染并拿到最新的值。这个时候就需要用到 nextTick 。具体代码如下:

<script>
export default {
       
  name: 'app',
  data() {
       
      return {
       
        list: ['a', 'b', 'c']
      }
  },
  methods: {
       
    addItem() {
       
        this.list.push(`${ Date.now()}`)
        this.list.push(`${ Date.now()}`)
        this.list.push(`${ Date.now()}`)

        // 1. 异步渲染,$nextTick 待 DOM 渲染完再回调,
        //    即NextTick函数会在多次data修改完并且全部DOM渲染完再触发,仅在最后触发一次
        // 2. 页面渲染时会将 data 的修改做整合
        this.$nextTick(() => {
       
          // 获取 DOM 元素
          const ulElem = this.$refs.ul1
          console.log( ulElem.childNodes.length )
        })
    }
  }
}
script>

我们通过给获取DOM元素的代码外面再嵌套一层 $nextTick 函数,来达到我们想要的效果。在此过程中,当我们点击结束后,data的值发生变化,此时 $nextTick 会等待DOM全部渲染完成之后再进行回调。

最终浏览器的打印效果如下:

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第9张图片

3、slot插槽

(1)插槽的作用

让用户可以拓展组件,去更好地复用组件和对其做定制化处理

(2)三种插槽类型

1)普通slot插槽

slot的基本使用方法是,父组件往子组件中插入一段内容。

我们来演示一下。

假设我们现在有一个子组件,名字叫SlotDemo.vue,它的代码如下。

<template>
    <a :href="url">
        <slot>slot>
    a>
template>

<script>
export default {
       
    props: ['url'], 
    data() {
       
        return {
       }
    }
}
script>

我们可能希望这个 标签内绝大多数情况下都渲染文本 Sunday ,但是有时候却希望渲染文本不是 Sunday ,而是其他内容,那该怎么实现呢?

我们就可以将 Sunday 作为后备内容,这个时候把它放在 标签内:

<a :href="url">
        <slot>Sundayslot>
    a>

现在,我定义一个父级组件,名字叫 index.vue ,并且在父级组件中引用上面的 SlotDemo 组件,具体代码如下:

<template>
    <div>
        <p>vue 高级特性p>
        <hr>
        
        <SlotDemo :url="website.url">SlotDemo>
    div>
template>

<script>
import SlotDemo from './SlotDemo'

export default {
       
    components: {
       
        SlotDemo
    },
    data() {
       
        return {
       
            name: 'Monday',
            website: {
       
                url: 'https://blog.csdn.net/weixin_44803753',
                title: 'Monday',
                subTitle: '穿梭于前端开发的学习永动机'
            }
        }
    }
}
script>

这个时候后备内容 Sunday 就会被渲染出来。此时浏览器显示效果如下:

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第10张图片

那么如果想要把这个后备内容渲染成我们想要的内容,而不是 Sunday 呢,这个时候我们应该这么做:

<SlotDemo :url="website.url">
	{
    {website.title}}
SlotDemo>

我们可以通过在父组件 index.vue 中引用的子组件 SlotDemo.vue 里面,插入我们想要的内容。这个时候子组件 SlotDemo.vue后备内容 Sunday 就会渲染成我们提供的新内容,以此来达到我们想要的目的。

此时浏览器的打印效果如下:

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第11张图片

2)作用域插槽slot-scope

vue官方的说法给到的解释是:

  • 父组件模板的所有东西都会在父级作用域内编译;
  • 子组件模板的所有东西都会在子级作用域内编译。

这样看起来好像有点难懂,接下来我们用一个例子来了解作用域插槽想要解决的问题究竟是什么!

首先,我们先定义一个子组件,名字叫 ScopedSlotDemo.vue具体代码如下所示:

<template>
    <a>
        <slot>
            {
    {website.subTitle}} 
        slot>
    a>
template>

<script>
export default {
       
    props: ['url'],
    data() {
       
        return {
       
            website: {
       
                url: 'http://tinymce.ax-z.cn/',
                title: 'tinymce',
                subTitle: '一款易用、且功能强大的富文本编辑器'
            }
        }
    }
}
script>

通过以上代码可以分析,这个时候我们想要把 slot 里面想要呈现的内容绑定下方的data数据,于是通过 { {website.subTitle}} 引用。


接下来我们定义一个父组件,名字叫 index.vue ,父组件代码如下:

<template>
    <div>
        <p>vue 高级特性p>
        <hr>
        
        <ScopedSlotDemo>
			{
    {website.title}}
        ScopedSlotDemo>
   div>
template>
<script>
import ScopedSlotDemo from './ScopedSlotDemo'

export default {
       
    components: {
       
        ScopedSlotDemo
    },
    data() {
       
        return {
       
            name: 'Monday'
        }
    }
}
script>

我们希望父组件不显示子组件的后备内容 subTitle ,而是显示子组件的另外一个 data 属性 ➡ title ,于是我们按照默认绑定的操作,直接在父组件进行 { {website.title}} 的绑定。此时我们运行一下:

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第12张图片

阿欧!这是什么东西呢?为什么会报错呢?

这就谈到了我们开头说的定义了。在vue当中,父组件模板的所有东西只会在父级作用域内编译;子组件模板的所有东西只会在子级作用域内编译。

所以,我们在父组件当中引用子组件的内容,当然是要报错的。这就相当于,一个父亲称呼他的儿子为爸爸一样,怎么可能嘛对吧!

在vue中,父级组件只能访问父级的数据,而子级组件也只能访问子级的数据,不能跃级访问。

因此,有了这个问题的出现,我们引用了作用域插槽来解决这个问题。怎么做呢?来看下面内容。


我们需要在父级作用域 index.vue 中为我们要绑定的元素设置一个 v-slot ,这个 v-slot 用来定义我们提供的插槽 prop 的名字。

index.vue:

<ScopedSlotDemo>    
	<template v-slot="slotProps">		
		{
    {slotProps.slotData.title}}    
	template>
ScopedSlotDemo>

那么父级定义完只是第一步,接下来我们要让子级 ScopedSlotDemo.vue 的插槽内容在父级当中可用,于是我们需要在子级的插槽中绑定一个自定义事件,并且指向我们需要的数据,如下代码所示:

ScopedSlotDemo.vue:

<slot v-bind:slotData="website">	
	{
    {website.subTitle}} 
slot>

从上面我们可以看到,我们通过绑定一个自定义事件 v-bind:slotData ,并把它指向我们想要的数据 website 。指向之后,我们在父级的插槽当中,通过 slopProps.slotData ,就可以访问到自己子级 website 的数据,这样,就达到最终我们想要的效果。

最终浏览器访问结果:

查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选_第13张图片


最后贴上完整的父子级代码,大家可以根据以上讲解进行演示。

子级 ScopedSlotDemo.vue:

<template>
    <a>
        <slot>
            {
    {website.subTitle}} 
        slot>
    a>
template>

<script>
export default {
       
    props: ['url'],
    data() {
       
        return {
       
            website: {
       
                url: 'http://tinymce.ax-z.cn/',
                title: 'tinymce',
                subTitle: '一款易用、且功能强大的富文本编辑器'
            }
        }
    }
}
script>

父级 index.vue:

<template>
    <div>
        <p>vue 高级特性p>
        <hr>
        
        <ScopedSlotDemo>
            <template v-slot="slotProps">
                {
    {slotProps.slotData.title}}
            template>
        ScopedSlotDemo>
 	div>
template>

<script>
import ScopedSlotDemo from './ScopedSlotDemo'

export default {
       
    components: {
       
        ScopedSlotDemo
    },
    data() {
       
        return {
       
            name: 'Monday',
            website: {
       
                url: 'https://blog.csdn.net/weixin_44803753',
                title: 'Monday',
                subTitle: '穿梭于前端开发的学习永动机'
            }
        }
    }
}
script>

3)具名插槽

具名插槽的定义: 将父组件中的内容插入指定的子组件位置中。

接下来来看一下具名插槽是怎么使用的?

有时候我们一个组件里需要多个插槽。比如,现在我们有一个子组件,名字叫 named.vue ,具体代码如下:

<template>
	<div>
      <header>
        
      header>

      <main>
        
      main>

      <footer>
        
      footer>
    div>
template>

大家可以看到,上面的代码中,我们希望在 headermainfooter 当中放入对应模块的内容进去,这个时候就需要使用多个插槽来解决。


那怎么处理呢? 对于这样的情况, 元素有一个特殊的属性: name ,这个属性可以用来定义额外的插槽。我们通过使用 name 属性来对子组件 named.vue 的插槽加上属性名。具体做法如下:

<template>
	<div>
      <header>
           <slot name="header">slot>
      header>

      <main>
          
        <slot>slot>
      main>

      <footer>
        <slot name="footer">slot>
      footer>
    div>
template>

值得注意的是,如果一个 不带 name 属性的话,那么它的 name 默认为 default


子级组件插入完插槽之后,接下来我们来定义一个父组件 index.vue 。在向子级组件的具名插槽提供内容的时候,我们可以在父级组件的