1、Vue 简介

1.1、什么是 Vue

官方给出的概念:Vue (读音/vjuː/,类似于 view) 是一套用户构建用户界面的前端框架

1.2、Vue 的特性

数据驱动视图

  • 当页面数据发生变化时,页面会自动重新渲染
  • 数据驱动视图是单向的数据绑定

双向数据绑定

  • 在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源
1.3、MVVM

MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel,它们把每个页面都拆分成了这三个部分

  • Model 表示当前页面渲染时所依赖的数据源
  • View 表示当前页面所渲染的 DOM 结构
  • ViewModel 表示 vue 的实例,它是 MVVM 的核心
1.4、MVVM 的工作原理
  • ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起
  • 当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构
  • 当表单元素的值发生变化时,也会被 ViewModel 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中

2、Vue 的基本使用

2.1、基本使用步骤
  1. 导入 vue.js 的 script 脚本文件
  2. 在页面中声明一个将要被 vue 所控制的 DOM 区域
  3. 创建 vue 实例对象
<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">{{ message }}</div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      // 指定 Model 数据源
      data: {
        message: "Hello World",
      },
    });
  </script>
</body>

3、Vue 的指令

3.1、指令的概念

指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构

vue 中的指令按照不同的用途可以分为六大类:

  1. 内容渲染指令
  2. 属性绑定指令
  3. 事件绑定指令
  4. 双向绑定指令
  5. 条件渲染指令
  6. 列表渲染指令
3.2、内容渲染指令

内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有三个,分别是:

  • v-text
  • {{}}
  • v-html
3.2.1 v-text
<!-- 把 message 对应的值,渲染到 div 标签中 -->
<div v-text="message"></div>

<!-- v-text指令会覆盖元素内默认的值,李广会被username的值覆盖掉 -->
<div v-text="username">李广</div>
3.2.2 插值表达式 {{}}

vue 提供的 {{}}语法,专门用来解决 v-text 会覆盖默认文本内容的问题。这种 {{}}语法的专业名称是插值表达式(Mustache)

<!-- 使用 {{}} 插值表达式,将对应的值渲染到元素的内容节点中 -->
<div>姓名:{{ username }}</div>
<div>年龄:{{ age }}</div>
3.2.3 v-html

v-text 指令和插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素,则需要用到 v-html 指令

<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app" v-html="infomation"></div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      // 指定 Model 数据源
      data: {
        infomation: "<p>我是一个前端菜鸟!</p>",
      },
    });
  </script>
</body>
3.3、属性绑定指令

如果需要为元素的属性动态绑定属性值,需要用到 v-bind 属性绑定指令。用法示例如下:

<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">
    <!-- 使用 v-bind 指令,为 input 的 placeholder 动态绑定属性值 -->
    <input type="text" v-bind:placeholder="placeholder"/>
    <!-- v-bind指令的简写形式,俗称语法糖 -->
    <input type="text" :placeholder="placeholder"/>
  </div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      // 指定 Model 数据源
      data: {
        placeholder: "请输入",
      },
    });
  </script>
</body>
3.4、使用 JavaScript 表达式

在 vue 中提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 JavaScript 表达式的运算

<!-- 加法运算 -->
{{ number + 1}}
<!-- 三元表达式 -->
{{ ok ? 'YES' : 'NO' }}
<!-- 字符串取反 -->
{{ message.split('').reverse().join('') }}
<!-- 属性绑定 -->
<div v-bind:id="'list-' + id"></div>
3.5、事件绑定指令

vue 提供了 v-on 事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听,事件处理函数需要在 methods 节点中声明

<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">
    <!-- 完整事件绑定写法 -->
    <button v-on:click="handleClick">点击</button>
    <!-- 事件绑定简写形式 把 v-on: 简写为 @ 符号 -->
    <button @click="handleClick">点击</button>
  </div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      methods: {
        handleClick() {
          alert("我被点击了!");
        },
      },
    });
  </script>
</body>
3.5.1 事件参数对象

在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件参数对象 event。同理,在 v-on 指令所绑定的事件处理函数中,同样可以接收到事件参数对象 event

<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">
    <!-- 完整事件绑定写法 -->
    <button v-on:click="handleClick">点击</button>
    <!-- 事件绑定简写形式 把 v-on: 简写为 @ 符号 -->
    <button @click="handleClick">点击</button>
  </div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      methods: {
        handleClick(e) {
          console.log(e.target.innerText);
        },
      },
    });
  </script>
</body>
3.5.2 绑定事件并传参

在使用 v-on 指令绑定事件时,可以使用 () 进行传参

<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">
    <!-- 完整事件绑定写法 -->
    <button v-on:click="handleClick('参数')">点击</button>
  </div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      methods: {
        handleClick(args) {
          console.log(args);
        },
      },
    });
  </script>
</body>
3.5.3 $event

$event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event。$event 可以解决事件参数对象 event 被覆盖的问题

<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">
    <!-- 完整事件绑定写法 -->
    <button v-on:click="handleClick('参数', $event)">点击</button>
  </div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      methods: {
        handleClick(args, e) {
          console.log(args, e.target.innerText);
        },
      },
    });
  </script>
</body>
3.5.4 事件修饰符

在事件处理函数中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。因此,vue 提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。

事件修饰符说明
.prevent阻止默认行为(阻止 a 链接的跳转、阻止表单的提交)
.stop阻止事件冒泡
.capture以捕获模式触发当前的事件处理函数
.once绑定事件只触发一次
.self只有在 event.target 是当前元素自身时触发事件处理函数
3.5.5 按键修饰符

在监听键盘事件时,需要判断详细的按键。此时可以为键盘相关的事件添加案件修饰符

<!-- 只有在 key 是 enter 时调用 -->
<input @keyup.enter="submit" />

<!-- 只有在 key 是 esc 时调用 -->
<input @keyup.esc="close" />
3.6、双向绑定指令 v-model

vue 提供了 v-model 双向数据绑定指令,用来辅助开发者不操作 DOM 的前提下,快速获取表单的数据

<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">
    <p>{{ username }}</p>
    <input type="text" v-model="username"/>
  </div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      data: {
        username: '李斯'
      }
    });
  </script>
</body>

v-model 指令的修饰符

修饰符作用示例
.number自动将用户的输入值转为数值类型<input v-model.number="age"/>
.trim自动过滤用户输入的首尾空白字符<input v-model.trim="msg"/>
.lazychange 时更新而非 input时更新<input v-model.lazy="msg"/>
3.7、条件渲染指令

条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有两个分别是 v-if 和 v-show

<div id="app">
  <p v-if="Math.random() > 0.5">v-if指令</p>
  <p v-show="Math.random() > 0.5">v-show指令</p>
</div>

v-if 和 v-show 的区别

  • v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上地显示与隐藏
  • v-show 指令会动态为元素添加或移除 style="display: none;"样式,从而控制元素地显示与隐藏
  • v-if 有更高地切换开销,而 v-show 有更高的初始渲染开销
  • 如果需要非常频繁地切换,则使用 v-show 较好
  • 如果在运行时条件很少改变,则使用 v-if 较好

v-else

v-if 可以单独使用,或配合 v-else 指令一起使用。v-else 指令必须配合 v-if 指令一起使用,否则它将不会被识别

<div v-if="Math.random() > 0.5">
  随机数大于 0.5
</div>
<div v-else>
  随机数小于或等于 0.5
</div>

v-else-if

v-else-if 指令,充当 v-if 的 else-if 块,可以连续使用。v-else-if 指令必须配合 v-if 指令一起使用,否则它将不会被识别!

3.8、列表渲染指令

vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。v-for 指令需要使用 item in items 形式的特殊语法,其中:

  • items 是待循环的数组
  • item 是被循环的每一项
<body>
  <!-- 在页面中声明一个将要被 vue 所控制的 DOM 区域 -->
  <div id="app">
    <ul>
      <li v-for="(item, index) in list" :key="item.id">
        索引是:{{ index }},姓名是:{{ item.name }}
      </li>
    </ul>
  </div>
  <!-- 导入 vue.js 的脚本文件 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script>
  <script>
    // 创建 vm 实例对象
    const vm = new Vue({
      // 指定当前 vm 实例要控制页面的哪个区域
      el: "#app",
      data: {
        list: [
          { id: 1, name: "张三" },
          { id: 2, name: "李四" },
        ],
      },
    });
  </script>
</body>

注意

  • v-for 指令中的 item 项和 index 索引都是形参,可以根据需要进行重命名
  • 当列表的数据变化时,默认情况下,vue 会尽可能地复用已存在地 DOM 元素,从而提升渲染地性能。但这种默认地性能优化策略,会导致有状态的列表无法被正确地更新
  • 为了给 vue 一个提示,以便它能跟踪每一个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲染的性能,此时,需要为每一项提供一个唯一的 key 属性

使用 key 的注意事项

  1. key 的值只能是字符串或数组类型
  2. key 的值必须具有唯一性
  3. 建议把数据项 id 属性的值作为 key 的值
  4. 使用 index 的值作为 key 的值没有任何意义
  5. 建议使用 v-for 指令时一定要指定 key 的值

4、Vue 的过滤器

4.1、过滤器

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定
过滤器应该被添加在 JavaScript 表达式的尾部,由管道符进行调用

<!-- 在插值表达式中通过管道符调用 capitalize 过滤器,对 message 的值进行格式化 -->
<p>{{ message | capitalize }}</p>

<!-- 在 v-bind 中通过管道符调用 formatId 过滤器,对 rawId 的值进行格式化 -->
<div v-bind:id="rawId | formatId"></div>
4.2、定义过滤器

在创建 vue 实例期间,可以在 filters 节点中定义过滤器

const vm = new Vue({
  el: "#app",
  filters: {
    <!-- 把首字母转为大写的过滤器 -->
    capitalize(str) {
      return str.charAt(0).toUpperCase() + str.slice(1)
    }
  }
});
4.3、私有过滤器和全局过滤器

在 filters 节点下定义的过滤器,称为私有过滤器,因为它只能在当前 vm 实例所控制的 el 区域内使用。如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器:

<!-- 全局过滤器 - 独立于每个 vm 实例之外 -->
Vue.filter('capitalize', (str) => {
  return str.charAt(0).toUpperCase() + str.slice(1);
});
4.4、连续调用多个过滤器
<!-- 把 message 的值,交给 filterA 进行处理 -->
<!-- 把 filterA 处理的结果,再交给 filterB 进行处理 -->
<!-- 最终把 filterB 处理的结果,作为最终的值渲染到页面上 -->
{{ message | filterA | filterB }}

示例代码

<!-- 串联调用多个过滤器 -->
<p>{{ text | capitalize | maxLength }}</p>

<!-- 定义全局过滤器 - 首字母大写 -->
Vue.filter('capitalize', (str) => {
  return str.charAt(0).toUpperCase() + str.slice(1);
});

<!-- 定义全局过滤器 - 控制文本的最大长度 -->
Vue.filter('maxLength', (str) => {
  if(str.length <= 10) return str;
  return str.slice(0, 11) + '...';
});
4.5、过滤器传参

过滤器的本质是 JavaScript 函数,因此可以接收参数

<!-- arg1 和 arg2 是传递给 filterA 的参数 -->
<p>{{ message | filterA(arg1, arg2) }}</p>

<!-- 第一个参数永远是管道符前面待处理的值,第二个参数开始才是传递过来的值 -->
Vue.filter('filterA', (msg, arg1, arg2) => {
  <!-- 过滤器代码处理逻辑 -->
});

示例代码

<!-- 调用 maxLength 过滤器时传参 -->
<p>{{ text | capitalize | maxLength(5) }}</p>

<!-- 定义全局过滤器 - 首字母大写 -->
Vue.filter('capitalize', (str) => {
  return str.charAt(0).toUpperCase() + str.slice(1);
});

<!-- 定义全局过滤器 - 控制文本的最大长度 -->
Vue.filter('maxLength', (str, len = 10) => {
  if(str.length <= len)return str;
  return str.slice(0, len) + '...';
});

5、组件的生命周期

5.1、生命周期 & 生命周期函数

生命周期(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
生命周期函数是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行

生命周期示意图
在这里插入图片描述

5.2、组件之间的数据共享
5.2.1 组件之间的关系
  • 父子关系
  • 兄弟关系
5.2.2 父组件向子组件共享数据

父组件

<template>
  <child :msg="message"></child>
</template>

export default {
  data() {
    return {
      message: 'Hello World'
    }
  }
}

子组件

<template>
  <div>父组件传递过来的 msg 值:{{ msg }}</div>
</template>

export default {
  props: ['msg']
}
5.2.3 子组件向父组件共享数据

子组件

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    add() {
      this.count ++;
      <!-- 修改数据时,通过 $emit() 触发自定义事件 -->
      this.$emit('change-count', this.count);
    }
  }
}

父组件

<template>
  <child @change-count="changeCount"></child>
</template>

export default {
  data() {
    return {
      parentCount: 0,
    }
  },
  methods: {
    changeCount(count) {
      this.parentCount = count;
    }
  }
}
5.2.4 兄弟组件之间的数据共享
  1. 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
  2. 在数据发送方,调用 bus.$emit(‘事件名称’, 要发送的数据)方法触发自定义事件
  3. 在数据接收方,调用 bus.$on(‘事件名称’, 事件处理函数)方法注册一个自定义事件

eventBus 模块

import Vue from 'vue';

<!-- 向外共享 Vue 的实例对象 -->
export default new Vue();

数据发送方

import bus from './eventBus.js';

export default {
  data() {
    return {
      msg: 'hello vue.js'
    }
  },
  methods: {
    sendMsg() {
      bus.$emit('share', this.msg);
    }
  }
}

数据接收方

import bus from './eventBus.js';

export default {
  data() {
    return {
      newMsg: ''
    }
  },
  created() {
    bus.$on('share', msg => {
      this.newMsg = msg;
    });
  }
}

6、ref 引用

6.1、什么是 ref 引用

ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向的一个空对象

6.2、使用 ref 引用 DOM 元素
<template>
  <!-- 使用 ref 属性,为对应的 DOM 添加引用名称 -->
  <div ref="myRef">ref 组件</div>
  <button @click="getRef">获取 $refs 引用</button>
</template>

export default {
  methods: {
    getRef() {
      <!-- 通过 this.$refs 引用的名称,可以获取到 DOM 元素的引用 -->
      console.log(this.$refs.myRef);
      <!-- 操作 DOM 元素,把文本颜色改为红色 -->
      this.$refs.myRef.style.color = 'red';
    }
  }
}
6.3、使用 ref 引用组件实例
<template>
  <!-- 使用 ref 属性,为对应的组件添加引用名称 -->
  <child ref="childRef"></child>
  <button @click="getRef">获取 $refs 引用</button>
</template>

export default {
  methods: {
    getRef() {
      <!-- 通过 this.$refs 引用的名称 可以引用组件的实例 -->
      console.log(this.$refs.childRef);
      <!-- 调用组件上的 methods 方法 -->
      this.$refs.childRef.getList();
    }
  }
}
6.4、让文本自动获取焦点
<template>
  <input v-if="inputVisible" ref="iptRef" type="text" />
  <button v-else @click="showInput">展示 input 输入框</button>
</template>

export default {
  data() {
    return {
      inputVisible: false
    }
  },
  methods: {
    showInput() {
      this.inputVisible = true;
      <!-- 获取文本框的 DOM 引用,并调用 .focus() 使其自动获取焦点 -->
      this.$nextTick(() => {
        this.$refs.iptRef.focus();
      });
    }
  }
}

7、动态组件

7.1、什么是动态组件

动态组件指的是动态切换组件的显示与隐藏

7.2、如何实现动态组件渲染
# vue 提供了一个内置的<component>组件,专门用来实现动态组件的渲染
data() {
  return {
    <!-- 当前要渲染的组件名称 -->
    componentName: 'left'
  }
}
<!-- 通过 is 属性,动态指定要渲染的组件 -->
<component :is="componentName"></component>

<!-- 点击按钮,动态切换组件的名称 -->
<button @click="componentName = 'left'">展示 left 组件</button>
<button @click="componentName = 'right'">展示 right 组件</button>
7.3、使用 keep-alive 保持状态

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 <keep-alive>组件保持动态组件的状态

<keep-alive>
  <component :is="componentName"></component>
</keep-alive>
7.4、keep-alive 对应的生命周期
  • 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数
  • 当组件被激活时,会自动触发组件的 activated 生命周期函数
export default {
  created() {
    console.log('组件被创建了');
  },
  activated() {
    console.log('组件被激活了');
  },
  deactivated() {
    console.log('组件被缓存了');
  }
}
7.5、keep-alive 的 include 属性
# include 属性用来指定只有名称匹配的组件会被缓存,多个组件名之间使用英文的逗号分隔
<keep-alive include="left, right">
  <component :is="componentName"></component>
</keep-alive>

8、插槽

8.1、什么是插槽
  • 插槽是 vue 为组件的封装者提供的能力,允许开发者在封装组件时,把不确定的、希望由用户指定的部分定义为插槽
  • 可以把插槽认为是组件封装期间,为用户预留的内容占位符
8.2、插槽的基本用法
# 在封装组件时,可以通过 <slot></slot>元素定义插槽,从而为用户预留内容占位符
<!-- 定义插槽 -->
<template>
  <div>第一个 div 标签</div>
  <!-- 通过 slot 标签,为用户预留插槽 -->
  <slot></slot>
  <div>这是最后一个div标签</div>
</template>

<!-- 使用插槽 -->
<my-component>
  <!-- 在使用组件时,为插槽指定具体的内容 -->
  <div>插槽的内容</div>
</my-component>
8.3、插槽的后备内容
  • 封装组件时,可以为预留的 <slot>插槽提供后备内容(默认内容)。
  • 如果组件的使用者没有为插槽提供任何内容,则后备内容会生效
<template>
  <slot>我是后备(默认)内容</slot>
</template>
8.4、具名插槽

如果在封装组件时需要预留多个插槽节点,则需要为每个<slot>插槽指定具体的 name 名称。这种带有具体名称的插槽叫做具名插槽

<div class="container">
  <header>
    <!-- 把页头放在这里 -->
    <slot name="header"></slot>
  </header>
  <main>
    <!-- 把主要内容放在这里 -->
    <slot></slot>
  </main>
  <footer>
    <!-- 把页脚放在这里 -->
    <slot name="footer"></slot>
  </footer>
</div>
8.5、为具名插槽提供内容

在向具名插槽提供内容的时候,可以在一个<template>元素上使用 v-slot 指令,并以 v-slot 参数的形式提供其名称

<my-component>
  <template v-slot:header>
    <h1>静夜思</h1>
  </template>
  <template v-slot:default>
    <p>床前明月光,疑是地上霜。</p>
    <p>举头望明月,低头思故乡。</p>
  </template>
  <template v-slot:footer>
    <p>作者:李白</p>
  </template>
</my-component>
8.6、具名插槽的简写形式

跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之前的所有内容(v-slot:)替换为字符 #

<my-component>
  <template #header>
    <h1>静夜思</h1>
  </template>
  <template #default>
    <p>床前明月光,疑是地上霜。</p>
    <p>举头望明月,低头思故乡。</p>
  </template>
  <template #footer>
    <p>作者:李白</p>
  </template>
</my-component>
8.7、作用域插槽

在封装组件的过程中,可以为预留的<slot>插槽绑定 props 数据,这种带有 props 数据的 <slot>叫做作用域插槽

<!-- 定义一个作用域插槽 -->
<slot v-for="item in items" :user="item"></slot>
8.8、使用作用域插槽
# 使用 v-slot: 的形式,接收作用域插槽对外提供的数据
<my-component>
  <!-- 接收作用域插槽对外提供的数据 -->
  <template v-slot:default="scope">
    <!-- 使用作用域插槽的数据 -->
    {{ scope.user }}
  </template>
</my-component>
8.9、解构插槽 props
# 作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程
<my-component>
  <!-- 接收作用域插槽对外提供的数据,v-slot: 可以简写成 # -->
  <template #:default="{ user }">
    <!-- 使用作用域插槽的数据 -->
    {{ user.name }}
  </template>
</my-component>

9、自定义指令

9.1、什么是自定义指令

vue 官方提供了 v-text、v-for、v-if 等常用的指令,还允许开发者自定义指令

9.2、自定义指令分类
  • 私有自定义指令
  • 全局自定义指令
9.2、私有自定义指令
# 在每个 vue 组件中,可以在 Directives 节点下声明私有自定义指令
directives: {
  color: {
    <!-- 为绑定到的 HTML 元素设置红色的字体 -->
    bind(el) {
      <!-- 形参中的 el 是绑定了此指令的、原生的 DOM 对象 -->
      el.style.color = 'red';
    }
  }
}
9.3、使用自定义指令
# 在使用自定义指令时,需要加上 v- 前缀
<h1 v-color>App</h1>
9.4、使用自定义指令动态绑定参数值

在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值

data() {
  return {
    <!-- 定义 color 颜色值 -->
    color: 'red',
  }
}

<!-- 在使用指令时,动态为当前指令绑定参数值 color -->
<h1 v-color="color">App</h1>
9.5、通过 binding 获取指令的参数值
# 在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值
directives: {
  color: {
    bind(el, binding) {
      <!-- 通过 binding 对象的 .value 属性,获取动态的参数值 -->
      el.style.color = binding.value;
    }
  }
}
9.6、update 函数

bind 函数只调用 1 次,当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。update 函数会在每次 DOM 更新时被调用

directives: {
  color: {
    <!-- 当指令第一次被绑定到元素时被调用 -->
    bind(el, binding) {
      el.style.color = binding.value;
    },
    <!-- 每次 DOM 更新时被调用 -->
    update(el, binding) {
      el.style.color = binding.value;
    }
  }
}
9.7、函数简写形式
# 如果 bind 和 update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式
directives: {
  <!-- 在 bind 和 update 时,会触发相同的业务逻辑 -->
  color(el, binding) {
    el.style.color = binding.value;
  }
}
9.8、全局自定义指令
# 全局共享的自定义指令需要通过 Vue.directives() 进行声明
# 参数一:字符串,表示全局自定义指令的名字
# 参数二:对象,用来接收指令的参数值
Vue.directives('color', function(el, binding) {
  el.style.color = binding.value;
});

10、Vue 路由

10.1、什么是路由

路由(英文:router)就是对应关系

10.2、SPA 与前端路由

SPA 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。此时,不同组件之间的切换需要通过前端路由来实现

10.3、什么是前端路由

Hash 地址与组件之间的对应关系

10.4、前端路由的工作方式
  1. 用户点击了页面上的路由链接
  2. 导致了 URL 地址栏中的 Hash 值发生了变化
  3. 前端路由监听到了 Hash 地址的变化
  4. 前端路由把当前 Hash 地址对应的组件渲染到浏览器中
10.5、vue-router 的基本使用
10.5.1 什么是 vue-router

vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换

10.5.2 vue-router 安装和配置
  1. 安装 vue-router 包
  2. 创建路由模块
  3. 导入并挂载路由模块
  4. 声明路由链接和占位符
10.5.3 在项目中安装 vue-router
npm i vue-router -S
10.5.4 创建路由模块
# 在 src 源代码目录下,新建router/index.js路由模块
# 1. 导入 Vue 和 VueRouter 的包
import Vue from 'vue';
import VueRouter from 'vue-router';

2. 调用 Vue.use() 函数,把 VueRouter 安装为 Vue 的插件
Vue.use(VueRouter);

# 3. 创建路由的实例对象
const router = new VueRouter();

# 4. 向外共享路由的实例对象
export default router;
10.5.5 导入并挂载路由模块
# 在 src/main.js 入口文件中,导入并挂载路由模块
import Vue from 'vue';
import App from './App.vue';

<!-- 导入路由模块 -->
import router from '@/router';

new Vue({
  render: h => h(App),
  <!-- 挂载路由模块 -->
  router: router
}).$mount('#app');
10.5.6 声明路由链接和占位符
# 在 src/App.vue 组件中,使用 vue-router 提供的 <router-link> 和 <router-view>声明路由链接和占位符
<template>
  <div class="app-container">
    <h1>App 组件</h1>
    <!-- 定义路由链接 -->
    <router-link to="/home">首页</router-link>
    <router-link to="/movie">电影</router-link>
    <router-link to="/about">关于</router-link>
    <!-- 定义路由的占位符 -->
    <router-view></router-view>
  </div>
</template>
10.5.7 声明路由的匹配规则
# 在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则
# 导入需要使用路由切换展示的组件
import Home from '@/components/Home.vue';
import Movie from '@/components/Movie.vue';
import About from '@/components/About.vue';

# 创建路由的实例对象
const router = new VueRouter({
  <!-- 在 routes 数组中,声明路由的匹配规则 -->
  <!-- path 表示要匹配的 hash 地址,component 表示要展示的路由组件 -->
  routes: [
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About },
  ]
})
11、vue-router 常见用法
11.1、路由重定向

路由重定向值得是用户在访问地址 A 的时候,强制用户跳转到地址 C,从而展示特定的组件页面。通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由地重定向

const router = new VueRouter({
  <!-- 在 routes 数组中,声明路由地匹配规则-->
  routes: [
    <!-- 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则 -->
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About },
  ]
})
11.2、嵌套路由
11.2.1 声明子路由链接和子路由占位符
# 在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符
<template>
  <div class="about-container">
    <h3>About 组件</h3>
    <!-- 在关于页面中,声明两个子路由链接 -->
    <router-link to="/about/tab1">tab1</router-link>
    <router-link to="/about/tab2">tab2</router-link>

    <!-- 在关于页面中,声明子路由的占位符 -->
    <router-view></router-view>
  </div>
</template>
11.2.2 通过 children 属性声明子路由规则声明
# 在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则
import Tab1 from '@/components/tabs/Tab1.vue';
import Tab2 from '@/components/tabs/Tab2.vue';

const router = new VueRouter({
  routes: [
    {
      <!-- about 页面的路由规则 -->
      path: '/about',
      component: About,
      children: [
        <!-- 通过 children 属性,嵌套声明子级别路由规则 -->
        { path: 'tab1', component: Tab1 }, // 访问 /about/tab1 时,展示 Tab1 组件
        { path: 'tab2', component: Tab2 }, // 访问 /about/tab2 时,展示 Tab2 组件
      ]
    }
  ]
})
11.3、动态路由

动态路由指的时:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。在 vue-router 中使用英文的冒号(:)来定义路由的参数项

<!-- 路由的从参数以 : 进行声明,冒号后面的是动态参数的名称 -->
{ path: '/movie/:id', component: Moive }
11.4、$route.params 参数对象
# 在动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问动态匹配的参数值
<template>
  <div class="moive-container">
    <!-- this.$route 是路由的参数对象 -->
    <h3>Movie 组件 -- {{ this.$route.params.id }}</h3>
  </div>
</template>
11.5、使用 props 接收路由参数

为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参

# 在定义路由规则时,声明 props: true 选项
{ path: '/movie/:id', component: Movie, props: true }

<template>
  <!-- 直接使用 props 中接收的路由参数 -->
  <div>movie组件 --- {{ id }}</div>
</template>

<script>
export default {
  <!-- 使用 props 接收路由规则中匹配到的参数项目 -->
  props: ['id']
}
</script>
11.6、声明式导航和编程式导航

在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:

  • 普通网页中点击 <a>链接、vue 项目中点击 <router-link>都属于声明式导航

在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。例如:

  • 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
11.7、vue-router 中的编程式导航 API

vue-router 提供了许多编程式导航 API,其中最常用的导航 API 分别是:

  1. this.$router.push(‘hash 地址’)
  • 跳转到指定 hash 地址,并增加一条历史记录
  1. this.$router.replace(‘hash 地址’)
  • 跳转到指定 hash 地址,并替换掉当前的历史记录
  1. this.$router.go(数值 n)
  • 实现导航历史前进、后退
11.7.1 $router.push
# 调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面
<template>
  <div class="home-container">
    <h3>Home 组件</h3>
    <button @click="goMovie">跳转到 Movie 页面</button>
  </div>
</template>

<script>
export default {
  methods: {
    goMovie() {
      this.$router.push('/movie/1');
    }
  }
}
</script>
11.7.2 $router.replace

调用 this.$router.replace() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面

push 和 replace 的区别:

  • push 会增加一条历史记录
  • replace 不会增加历史记录,而是替换掉当前的历史记录
11.7.3 $router.go
# 调用 this.$router.go() 方法,可以在浏览器历史中前进和后退
<template>
  <div class="movie-container">
    <h3>Movie 组件</h3>
    <button @click="goBack">后退</button>
  </div>
</template>

<script>
export default {
  methods: {
    goBack() {
      this.$router.go(-1);
    }
  }
}
</script>

$router.go 的简化用法:

  • $router.back():在历史记录中,后退到上一个页面
  • $router.forward():在历史记录中,前进到下一个页面
11.8、路由导航守卫
11.8.1 全局前置守卫

每次发生路由的导航跳转时,都会触发全局前置守卫。因此在全局前置守卫中,可以对每个路由进行访问权限的控制

# 创建路由的实例对象
const router = new VueRouter();

# 调用路由实例对象的 boforeEach() 方法,即可声明全局前置守卫
router.boforeEach((to, from, next) => {
  # to 是将要访问的路由信息对象
  # from 是将要离开的路由信息对象
  # next 是一个函数,调用 next() 表示放行,允许这次路由导航
});
11.8.2 next 函数的 3 种调用方式
  • 当用户拥有后台主页的访问权限,直接放行:next()
  • 当用户没有后台主页的访问权限,强制其跳转到登录页面:next(‘/login’)
  • 当用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)
11.8.3 控制后台主页的访问权限
router.boforeEach((to, from, next) => {
  if(to.path === '/main') {
    const token = localStorage.getItem('token');
    if(token) {
      <!-- 访问的是后台主页,且有 token 的值 -->
      next();
    } else {
      <!-- 访问的是后台主页,但是没有 toekn 值 -->
      next('/login');
    }
  } else {
    next(); // 访问的不是主页,直接放行
  }
});
Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐