# vue 的指令与过滤器

# 1. 指令的概念

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

# 2. 指令的分类

  • 内容渲染指令
  • 属性绑定指令
  • 事件绑定指令
  • 双向绑定指令
  • 条件渲染指令
  • 列表渲染指令

# 2.1 内容渲染指令

# 1. 概念:

内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容

# 2. 常用的指令:
  • v-text

    该指令会覆盖标签中原来的内容

  • 插值表达式(mustache),专门用来解决 v-text 会替换标签中默认值的方法,该方法在实际开发中用的最多,只是内容的占位符不会覆盖原来的内容

    !Warning:插值表达式只能用在内容节点,不能用在属性节点,即只能替换标签的内容区域的内容,不可以替换标签属性的值 

  • v-html

    可以把带有标签的字符串渲染成真正的 html 内容

# 2.2 属性绑定指令

# 1. 概念:

为元素的属性动态绑定属性值

# 2. 常用的指令:
  • v-bind
使用 data.tips 的值动态绑定 placeholder 属性的值  
   
```html
   <input type="text" v-bind:placeholder="tips">
```
   
**简写** " v-bind: " 为 “ : "    
   
```html
   <input type="text" :placeholder="tips">
```
# 3. 使用 JavaScript 表达式:

在使用属性动态绑定的时候,可以 中对引入的动态值做一些运算

<div>{{ divContent.split('').reverse().join('') }}</div> // 翻转字符串
<div :title="divTitle + 100"></div>
<div :title="'ID: ' + divId"></div>
1
2
3

# 2.3 事件绑定指令

# 1. 概念:

为 DOM 元素绑定事件监听器

# 2. 常用的指令:
  • v-on

    v-on:事件名称="事件的处理函数"

    简写形式为 @事件名称="事件的处理函数"

    其中,事件的处理函数要定义在 methods 配置选项中

    若写为 v-on:click="func-name" 的形式则不可以传入参数,写为 v-on:click="func-name(xx, yy, ...)" 的形式可以传入参数

    <div id="app">
        <p>count 的值是:{{ count }}</p>
        <button v-on:click="countAdd">count 值加一</button>
    </div>
    
    1
    2
    3
    4
    const vm = new Vue({
        el: '#app',
        data: ...,
    
        // 定义 DOM 元素监听事件的处理函数
        methods: {
            // countAdd: function () {
            //    ...
            // }
            
            // 简写形式
            countAdd() {
                // console.log(vm === this) // true
                // vm.count++;
                this.count++;
            }
        }
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
# 3. 事件对象
  • 事件对象是什么:

    vue 提供了内置变量 $event,它就是原生 DOM 的事件对象 e

  • 什么时候用:

    在事件绑定时,若我们传入了参数,那么就会覆盖掉无参时自动传入的事件对象,如果我们在事件处理程序中需要使用到事件对象,那么就需要在事件绑定的时候手动传入事件对象 $event。

e.g.

<button @click="countAddThreeEvenShowRed(3, $event)">count 值每次加3,奇数原色显示,偶数红色显示</button>
1
countAddThreeEvenShowRed(n, e) {
    this.count += n;

    if (this.count % 2 === 0) {
        e.target.style.backgroundColor = 'red';
    } else {
        e.target.style.backgroundColor = '';
    }
}
1
2
3
4
5
6
7
8
9
# 4. 事件修饰符
  • 事件修饰符是什么:

    在事件处理函数中调用 e.preventDefult() e.stopPropagation() 是常见的需求。因此 vue 提出了事件修饰符,使用事件修饰符能够更方便地对事件的触发进行控制。

    在绑定事件的时候进行修饰

  • 常见的事件修饰符:

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

    e.g.

    <div @click="clickDiv" style="line-height: 50px; background-color: lightblue;">
        <a @click.prevent.stop="anchorPrevent" href="http://www.lib.hdu.edu.cn/">杭州电子科技大学图书馆</a>
    </div>
    
    1
    2
    3
# 5. 键盘修饰符
  • 概念:

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

    键盘事件:@keyup @keydown

  • 常见的按键修饰符:

    .enter .esc .left .right .up .down .alt .shift ... ...

# 2.4 双向绑定指令

# 1. 概念:

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

# 2. 谁可以使用:

v-model 只能对表单元素进行使用。

常见的表单元素:

  • input,不限定 type
  • textarea
  • select
<p>用户名是:{{ username }}</p>
<input type="text" v-model="username">
1
2

使用 v-model 就可以不用使用 input 的 value 属性了,会自动监听 value 的变化

!Warning:v-model 只能对表单元素进行使用,对普通的元素(如 div)使用会报错,显示不支持 

# 3. v-model 的修饰符:
  • 是什么:

    vue 为 v-model 提供了 3 个修饰符,以方便对用户输入的内容进行处理

  • 有哪些:

    修饰符 作用 示例
    .number 自动将用户输入的值转换为数值类型 < input v-model.number='age' >
    .trim 自动去除用户输入内容的首尾空白字符,中间的不管 < input v-model.trim='msg' >
    .lazy 不是实时进行数据修改,而是在用户修改完了之后统一更新 < input v-model.lazy='msg' >

# 2.5 条件渲染指令

# 1. 概念:

条件渲染指令辅助开发者按需控制 DOM 元素的显示与隐藏

# 2. 常用的指令:
  • v-if:

    动态为元素添加或者删除行内样式 display: none 以实现元素的隐藏和显示。

    如果元素初始默认不展示,后期大概率也不会被展示的话,用 v-show 性能会更好一些

  • v-show:

    每次都是动态的移除和创建元素以实现元素的隐藏和显示

    如果需要频繁地切换元素的显示状态,那么用 v-if 性能会更好一些

在实际开发中,绝大多数情况使用 v-if,很少考虑性能的影响

# 3. v-if 的配套指令:
  • v-else-if
  • v-else

!Warning:这两个配套指令必须配合 v-if 使用,若单独使用会报错 

实际开发中,v-if 配合 v-else 就可以满足大多数需求了

e.g.

<div v-if="score === 'A'">优秀</div>
<div v-else-if="score === 'B'">良好</div>
<div v-else></div>
1
2
3

# 2.6 列表渲染指令

# 1. 概念:

列表渲染指令(v-for)用来辅助开发者基于一个数组来循环渲染一个列表的结构。其语法结构为 v-for="item in list" / v-for="(item, index) in list",其中 item 是循环得到的每一项,index 为 item 的循环索引,list 为待循环的数组。

要循环生成哪个结构就在哪个结构上添加 v-for 指令

v-for 中的 item 和 index 是形参,可以为其他的变量名

# 2. 用法示例:
<!--要循环添加每一行,因此在 tr 上添加 v-for-->
<tr v-for="(item, index) in list" :key="item.id">
	<!--注意这里要用插值表达式括起来,否则不能正确填充想要的内容-->
    <td>{{ index }}</td>
    <td>{{ item.id }}</td>
    <td>{{ item.name }}</td>
</tr>
1
2
3
4
5
6
7

!Warning:官方建议,只要用到了 v-for 就要绑定一个 :key 属性,而且尽量把 id 作为 key 的值。key 的值类型必须是 string 或 number 类型

key 值必须满足的条件:

  • key 值类型只能是 string 或 number 类型

  • key 值必须是唯一的,这种唯一性不仅要求 key 值不重复,而且每个 key 要与其对应项严格一一对应


  • 建议把数据项的 id 属性值作为 key 值(因为 id 具有唯一性,体现在 id 不重复,且与数据项严格绑定)
  • 使用 index 的值当作 key 没有任何意义,因为 index 值不具有唯一性,不与数据项严格绑定
  • 使用 v-for 绑定 key 值既可以提升性能,又可以防止列表混乱

# 3. 过滤器(vue2 可用)

# 3.1 过滤器的基本使用

# 1. 概念:

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

过滤器本质就是一个函数

过滤器函数一定要有返回值

# 2. 用法示例:
<div id="app">
    <!-- 过滤器 -->
	<!-- info 是待过滤的内容,该内容经过过滤器后被插值表达式显示 -->
	<!-- | 是管道符 -->
	<!-- capitalize 是过滤器函数,该函数的默认实参就是管道符前边表达式的值 -->
    <div>{{ info | capitalize }}</div>
</div>
1
2
3
4
5
6
7
const vm = new Vue({
    el: '#app',
    data: {
        info: 'hello my filter...'
    },

    // 定义过滤器
    filters: {
        capitalize(value) {
            let first = value.charAt(0).toUpperCase();
            let others = value.slice(1);
            return first + others;
        }
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3. 注意点:
  • 过滤器本质是一个函数,要定义在 filters 节点中

  • 过滤器函数一定要写返回值,否则什么都不返回了,就没有显示了

  • 使用过滤器可以传递参数,不过管道符前面表达式的值默认为第一个参数,我们可以认为存在以下形式

    • 实际相当于
    • 实际相当于

# 3.2 私有过滤器和全局过滤器

# 1. 概念:
  • 私有过滤器:

    定义在 filters 节点之下的过滤器称为“私有过滤器”,它只能在当前 vm 实例所控制的 el 元素范围之内使用。

  • 全局过滤器:

    使用以下语法定义的过滤器称为“全局过滤器”,它可以在多个 vm 实例之间共享过滤器。

    Vue.filter('过滤器的名称', (value) => {
    	// code...
        return xxx;
    })
    
    1
    2
    3
    4

若全局过滤器和私有过滤器重名,那么优先调用 vm 实例自己的私有过滤器

实际开发中很少用到过滤器,即使用到也是使用全局过滤器,很少使用私有过滤器

!Warning:先于全局过滤器定义的 vue 实例无法使用在这个实例之后定义的全局过滤器

# 2. 连续调用多个过滤器

具体语法如下:

{{ param | filter1 | ... | filtern }}
1

# vue 的侦听器、计算属性、vue-cli 和 vue组件

# 1. 侦听器

# 1.1 侦听器的基本使用

# 1. 概念:

侦听器(watch)允许开发者监视数据的变化,从而针对数据的变化采取特定的操作。

# 2. 语法格式:
  • 方法格式的侦听器:

    侦听器本质上是一个函数,要侦听哪个数据的变化,就用哪个数据作为方法名。

    新的值在前,旧的值在后。

    缺点:

    • 无法在刚进入页面的时候就自动触发侦听器
    • 如果侦听的是一个对象(userInfo),那么对象属性(userInfo.username)发生变化,不会触发侦听器
const vm = new Vue({
	...
	data: {
        name: 'xxx',
        userInfo: {
            username: 'yyy',
            age: 18
        }
    }
    
	watch: {
		// 侦听 name 的变化,newVal 是变化之后的值,oldVal 是变化之前的值
        name(newVal, oldVal) {
            // code...
        }
	},
	
	...
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 对象格式的侦听器:

    优点:

    • 通过设置 immediate 选项,可以在一进入页面的时候就自动触发侦听器
    • 可以通过 deep 选项,深度侦听对象中属性的变化

    immediate: true 代表一进入页面就可以触发一次侦听器

    immediate: false 代表一进入页面不会触发侦听器,默认该选项

    deep: true 代表可以侦听对象中属性的变化

const vm = new Vue({
	...
	
	watch: {
		// 定义对象格式的侦听器
        name: {
        	// handler 是相应的处理函数
        	// handler: function() { ... };
        	
        	// 简写
        	handler(newVal, oldVal) {
        		// code...
        	},
            // 设置一进入页面自动触发
            immediate: true,
            // 只要对象中任一属性发生变化,就会触发侦听器
            deep: true
        }
	},
	
	...
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

如果真的需要侦听对象中属性的变化,推荐使用如下方法,注意方法名要使用单引号

const vm = new Vue({
	watch: {
		// 侦听对象中属性的变化
        'userInfo.username'() {
            // code...
        }
	}
})
1
2
3
4
5
6
7
8

# 2. 计算属性

# 2.1 计算属性的基本使用

# 1. 概念:

计算属性是经过一系列运算之后得到的一个属性值。该计算好的属性是 vm 实例的属性,可以像使用 data 里边普通的属性一样使用这个计算好的属性。

# 2. 语法格式:
  • 在 computed 选项中使用方法格式定义计算属性
  • 定义完成后相当于 vm 实例多了一个属性值,当作普通属性使用即可
const vm = new Vue({
	el: '#app',
	data: {r: 0, g: 0, b: 0},
	
	// 定义计算属性,要使用方法定义格式,相当于 data 中多了一个 rgb 属性
	computed: {
		rgb() {
			return `rgb(${this.r}, ${this.g}, ${this.b})`;
		}
	}
})
1
2
3
4
5
6
7
8
9
10
11
# 3. 计算属性的优点:
  • 实现了代码的复用
  • 当计算属性依赖的数据源发生变化,计算属性会重新进行求值

# 3. vue-cli

# 3.1 单页面应用程序

# 1. 概念:

单页面应用程序(Single Page Application,SPA)指一个 web 网站中只有唯一的一个html 页面,所有的功能与交互都在这唯一的一个页面内完成。

# 3.2 vue-cli

# 1. 概念:

vue-cli 是 Vue.js 的标准开发工具,它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。

# 2. 安装与使用:
  • vue-cli 是 npm 上的一个全局包,使用 npm install @vue/cli -g 进行全局安装
  • 基于 vue-cli 快速生成工程化的 Vue 项目: vue create <项目名称>
# 3. 项目的目录结构:

|项目目录

|--------node_modules

​ |--------存放所有的第三方库

|--------public

​ |--------favicon.ico:页面小图标

​ |--------index.html:首页首页

|--------src:源代码目录

​ |--------assets:存放项目中用到的静态资源文件,如 css、图片等

​ |--------component:存放开发者封装的可复用的组件

​ |--------main.js:项目的入口文件,整个项目的运行,要先执行 main.js

​ |--------App.vue:项目的根组件

# 4. vue 项目的运行流程

在工程化的项目中,vue 要做的事情很简单,就是通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。

vue 实例的 .$mount('#app')el: '#app' 作用相同。

e.g.

main.js:

// 导入 vue 这个包,得到 Vue 构造函数
import Vue from 'vue'
// 导入 App.vue 这个根组件,将来要把 根组件中的模板结构渲染到 index.html 页面中
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  // 把 render 函数指定的组件渲染到 index.html 中去
  render: h => h(App),
}).$mount('#app')
1
2
3
4
5
6
7
8
9
10
11

App.vue:

<template>
  <h1>Lorem ipsum dolor sit amet, consectetur.</h1>
</template>
1
2
3

index.html:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

通过上面的代码,main.js 会将 App.vue 中的内容渲染到 index.html 中。也就是<h1> Lorem ipsum dolor sit amet, consectetur. </h1>替换掉 index.html 中的 <div id="app"> </div>这一部分内容。

# 4. vue 组件

# 4.1 组件化开发

# 1. 概念:

组件化开发指的是根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护。

什么是组件?

组件就是对 UI 结构的封装和复用

# 2. vue 组件的三个组成部分:

vue 组件以 .vue 为扩展名,每个 vue 组件由以下三部分组成:

  • template:组件的模板结构
  • script:组件的 JavaScript 行为
  • style:组件的样式

!Warning:vue 组件中的 data 不可以指向对象,要用方法格式定义,可以返回一个对象,别的选项依旧定义

!Warning:vue 组件中 结构内部只能包含一个根节点,不可以有平行的根节点 

!Warning:若不指定 lang="xxx" 即默认为 css 文件

<template>
  <!-- 这里只有一个根节点,没有平行的根节点 -->
  <div id="test-component">
    <h1>这是一个测试组件的模板</h1>
    <h1>用户名是:{{ username }}</h1>
    <button @click="showUsername">点击按钮,控制台打印username</button>
  </div>
</template>

<script>
// 默认输出,这是固定结构
export default {
  name: 'TestComponent',
  // vue 组件中的 data 不可以指向对象,要用方法格式定义,可以返回一个对象
  // data 需要定义为方法格式,别的选项依旧
  data() {
    return {
      username: 'lucien'
    }
  },

  methods: {
    showUsername() {
      console.log(this.username);
    }
  },

  filters: {},
  computed: {},
  watch: {}
}
</script>

<!-- 若不指定 lang="xxx" 即默认为 css 文件-->
<style lang="less">
  #test-component {
    background-color: aliceblue;
    h1 {
      color: lightpink;
    }
  }
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 5. 组件之间的父子关系:

组件在被封装好之后,彼此之间是独立的,不存在父子关系。

使用组件的时候,根据组件彼此之间的嵌套关系,形成了父子关系兄弟关系

# 6. 使用组件的三个步骤:
  1. 导入:使用 import 语法导入需要的组件
  2. 注册:使用 component 节点注册组件,注意这里注册的是私有子组件
  3. 使用:以标签的形式使用组件
<script>
// 1. 使用 import 语法导入需要的组件
import Left from './components/Left.vue'

export default {
  name: 'App',

  // 2. 使用 component 节点注册组件
  components: {
    // 简写为以下形式
    Left
  }
}
</script>

<template>
  <div id="app">
    <!-- 3. 以标签的形式使用组件 -->
    <Left></Left>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 7. 注册全局组件

在每一个 Xxx.vue 组件的 components 节点中注册的组件都是私有子组件,在其他的组件中不能使用该组件中注册的组件。

  • 注册全局组件:

    在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法注册全局组件。

!Warning:全局组件要在所有用到该全局组件的组件之前注册才可以

!Warning:反复使用的组件适合全局注册

// 1. 导入需要注册的全局组件
import Count from "@/components/Count";
// 2. 使用 Vue.component('注册的名称', 要被注册的组件) 注册全局组件
Vue.component('MyCount', Count);

// code... 这里包含用到上述全局组件 MyCount 的组件
1
2
3
4
5
6

# 4.2 组件的 props 属性

# 1. 概念:

props 是组件的自定义属性,在封装组件的时候,合理利用 props 可以极大提高组件的复用性。

为什么叫自定义属性呢?

因为我们在使用自己封装的组件的时候希望通过属性值传入一些参数(例如标签 < img src='xxxx' > 中的 src 可以传入图片路径一样),而这些属性只有在我们封装组件时自己预先定义好,所以叫做”自定义属性“

定义好的自定义属性可以像 data 中的属性一样使用,但是建议只以“只读”的方式访问自定义属性的值,不推荐修改自定义属性的值

# 2. 语法:
  • 数组形式定义:

    <script>
    export default {
      // 定义自定义属性,允许使用者通过自定义属性为当前组件指定初始值
      props: ['attr1', 'attr2', ..., 'attrn'],
    }
    </script>
    
    1
    2
    3
    4
    5
    6
  • 对象形式定义:

    <script>
    export default {
      // 定义自定义属性,允许使用者通过自定义属性为当前组件指定初始值
      props: {
        attr1: {
          // 如果外界使用该组件时没有传入 prop property -- attr1 的值,则默认值生效
          default: 0,
          // 若传入的 zttr1 值类型不是 Number 则会报错
          type: Number,
          // 设置为必填项
          required: true
        },
        attr2: String,
        attr3: Array,
        attr4: Object,
        ....
      }
    }
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
# 3. 使用 v-bind 传入真正的数值

假设已经封装好了一个名为 MyCount 的组件,其有一个名为 initNum 的自定义属性

  • 若以 < my-count init-num="6" > 这样的形式,initNum 接收到的是字符串类型的 6,即 "6"

  • 若使用 v-bind 修饰 initNum,即以 < my-count :init-num="6" > 这样的形式,initNum 接收到的是字数值型的 6,因为加了 v-bind 后,引号中的内容是 JavaScript 表达式了。

props property 的大小写问题:

HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名

# 4.3 解决组件之间的样式冲突

# 1. 样式冲突的原因:

默认情况下,写在 .vue 组件中的样式会全局生效,因此容易造成多个组件之间的样式产生冲突。

导致组件之间样式冲突的根本原因

  1. 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现
  2. 每个组件中的样式,都会影响到整个 index.html 页面中的 DOM 元素
# 2. 解决方法及其原理:
  • 方法:

    可以对 < style > 标签添加 scoped 属性,即 < style scoped >

  • 原理:

    如果添加了 scoped 属性,vue 会默认对该组件中的 template 中所有的元素添加一个 data-v-随机序号 的属性,相应的 style 样式也会应用上 属性选择器。

    即 .mydiv**[data-v-3c83f0b7]** { color: red } 这样形式。

# 3. 父组件修改子组件的样式:
  • 什么时候会父组件中修改子组件的样式:

    当使用第三方库的时候,如果有修改第三方组件样式的需求,需要用到 /deep/

  • 做法:

    在父组件中写子组件的选择器之前加上 /deep/

    /deep/ 子组件的选择器 {
    	color: red,
    	font-size: 20px,
    	...
    }
    
    1
    2
    3
    4
    5

    这样子组件选择器前就加上了父组件的 data-v-xxxxxx,例如:

    [data-v-3c83f0b7] h3 {
    	background-color: pink
    }
    
    1
    2
    3

    表示将父元素包含 [data-v-3c83f0b7] 属性的 < h3 > 标签的背景颜色修改为粉色。至此就实现了在父组件中修改子组件的样式。

# 生命周期和数据共享

# 1. 生命周期

# 1.1 组件的生命周期

# 1. 概念:
  • 生命周期:是一个组件从创建、运行到销毁的整个阶段,强调的是一个时间段。
  • 生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行
生命周期第一阶段——组件创建阶段 生命周期第二阶段——组件运行阶段 生命周期第三阶段——组件销毁阶段
beforeCreate() => created() => beforeMount() => mounted() beforeUpdate() => updated() beforeDestroy() => destroyed()

created() : 常用该生命周期函数调用 methods 中的方法发送 Ajax 请求,将请求到的数据转存到 data property 中共 template 渲染使用。

mounted() : 到这里已经渲染好了 DOM 结构,可以进行 DOM 操作了。

lifecycle

# 2. 组件之间的数据共享

# 2.1 父向子传值

# 1. 如何实现:

父组件设置子组件的自定义属性实现父组件向子组件传值

<son-component :son-name="'andy'" :son-id="10001"></son-component>
1

# 2.2 子向父传值

# 1. 如何实现:

子向父传值使用子组件的自定义事件实现

  • 在子组件中定义自定义事件
<template>
  <div id="son">
    <p>子组件</p>
    <button @click="buyBook">购买新书</button>
  </div>
</template>

<script>
export default {
  name: "SonComponent",
  ...,    
  methods: {
    buyBook() {
      this.booksnum++;
      // 定义自定义事件,实现从子组件向父组件传值
      // 自定义事件名为 ”booksChange“,当触发该事件后父组件就会监听到该事件,
      // this.booksnum 作为实参传递给父组件监听此自定义事件之后的回调函数
      this.$emit('booksChange', this.booksnum);
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 在父组件中监听自定义事件,指定回调函数,接收由子组件传来的实参 this.booksnum
<template>
<div id="father">
  <!-- @booksChange 用来监听自定义事件,出发后调用回调函数 -->
  <son-component :son-name="'andy'" :son-id="10001" son-book-num="27" @booksChange="getNewBooksNum"></son-component>
</div>
</template>

<script>
import SonComponent from "@/components/SonComponent";

export default {
  name: "FatherComponent",
  data() {
    return {
      booksNumFromSonComponent: -1
    }
  },

  methods: {
    getNewBooksNum(val) {
      // val 是子组件 this.emit() 传递的第二个参数
      this.booksNumFromSonComponent = val;
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 2.3 兄弟组件之间传值

# 1. 如何实现:

使用 Eventbus 实现

  • 先在一个 eventbus.js 文件中暴露一个公共的 vue 实例
import Vue from 'vue'

export default new Vue();
1
2
3
  • 在发送方引入 eventbus,在此 bus 上使用 $emit() 定义自定义事件 share
<script>
// 兄弟组件之间共享数据采用 EventBus
import bus from './eventBus.js';

export default {
  ...,
  methods: {
    dataChange() {
      this.dataToSend++;
      // 发送方定义自定义事件
      bus.$emit('share', this.dataToSend);
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 在数据接收方引入 eventbus,接收方一旦 created 就在此 bus 上使用 $on() 监听自定义事件 share 并定义回调函数接收处理发送方发来的数据
<script>
// 使用 EventBus 接收兄弟组件 SisterComponent 传来的数据
import bus from './eventBus.js';

export default {
  // 作为接收方,在 vue 属性实例化好之后就随即定义 EventBus 的监听函数
  // share 事件是发送方定义好的事件,只要发送方发送数据就会触发这个事件
  created() {
    bus.$on('share',(val) => {
      this.dataFromSibling = val;
    });
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 动态组件、插槽、自定义指令

# 1. 动态组件

# 1.1 什么是动态组件

可以动态切换组件的显示与隐藏的组件

# 1.2 如何实现动态组件渲染

# 1. 动态组件:

vue 提供了 < component > 组件,专门用来实现动态组件的渲染

# 2. 示例:
  • < component > 只是组件的占位符
  • 使用 is 属性指定需要渲染的组件的名字
<template>
  <div id="app">
    <h1>根组件</h1>
    <component is="Left"></component>
  </div>
</template>

<script>
import Left from "@/components/Left";
import Right from "@/components/Right";

export default {
  name: 'App',
  components: {
    Left,
    Right,
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3. 如何防止动态组件切换时会被创建与销毁:
  • 解决方法:

    使用 vue 内置的 keep-alive 标签把需要保持状态的组件包裹起来

<keep-alive>
  <component :is="comName"></component>
</keep-alive>
1
2
3

隐藏的组件被没有被销毁,而是被缓存起来了,后面添加了 inactive

image-20220308210401263

# 4. keep-alive 对应的生命周期函数:
  • 当组件被缓存时,会自动触发组件的deactivated生命周期函数
  • 当组件被激活时,会自动触发组件的activated生命周期函数

通过以上两个生命周期函数,我们可以在组件被缓存和被激活的时候做一些事情。

当组件第一次被创建显示的时候,会触发 created 和 activated 两个函数

# 5. 指定 keep-alive 缓存或者不缓存哪些组件:

指定 < keep-alive > 的 includeexclude 属性

  • include 属性:

    只有名称匹配的组件才会被缓存,多个组件名之间使用逗号分隔

  • exclude 属性:

    被指定的组件不会被缓存

!Warning:include 和 exclude 属性不可以同时使用

示例:

<keep-alive include="Left, Other">
  <component :is="comName"></component>
</keep-alive>
1
2
3

# 1.3 了解组件的注册名称和声明名称的区别

# 1. 什么是注册名称:
import Left from "@/components/Left";
1

这里的 Left 就是注册名称

# 2. 什么是声明名称:
<template>
<div id="left-container">
  <h3>left 组件</h3>
</div>
</template>

<script>
export default {
  name: "Left",
  ...
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12

这里的 Left 就是声明名称

# 3. 相关联系:

如果没有指定组件的声明名称,即没有写 export default {name: xxx} 中的 name,那么组件的声明名称默认就是组件的注册名称

# 4. 各自用途:
  • 声明名称:

    • 结合 keep-alive 的 include 属性和 exclude 属性实现组件的缓存

    • 在 vue 调试工具中看到的名称也是声明名称

  • 注册名称:

    • 作为标签名把注册好的组件渲染到页面结构之中

# 2. 插槽

# 2.1 插槽基础

# 1. 概念:

插槽(slot)是 vue 为组件的封装者提供的能力,允许开发者在封装组件时把不确定的、希望由用户指定的部分定义为插槽。

# 2.2 插槽的使用

# 1. 插槽的后备内容:

如果用户没有指定插槽的具体内容是什么,那么就会显示插槽的后备内容

# 2. 具名插槽:
  • 封装组件时

    使用 < slot > 标签定义插槽,可以指定 slot name,即 < slot name="xxx" >,若未指定 name 则默认为 default

<template>
  <div id="left-container">
    <h3>left 组件</h3>

    <!-- 定义一个具名插槽,若未指定具体名字,名字为 default -->
    <slot name="myslot"></slot>
    <slot></slot>
  </div>
</template>
1
2
3
4
5
6
7
8
9
  • 使用组件时

    在组件的注册名称标签内传入需要插入的内容,若要指定将内容插入到哪一个插槽中,则可以借助 v-slot: name 指令

<template>
  <div id="app">
    <h1>根组件</h1>
      
    <Left>
      我被插入到 < slot name="default" > 中了
      <template v-slot:myslot>
        <p>我被插入到 < slot name="myslot" > 中了</p>
      </template>
    </Left>

    <Left v-slot:myslot>
      Lorem ipsum dolor sit amet, consectetur.
    </Left>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

!Warning:v-slot 指令只能应用在 < template > 或者组件上,不可以用在 html 元素身上

v-slot 是一个指令,不是属性,所以 v-slot 后面用的是冒号,不是等号!

< template > 标签只起到包裹性的作用,不会渲染为任何真正的元素

v-slot 指令的简写形式是 #

# 3 作用域插槽:

简单的说,就是在声明插槽的时候指定了一些属性相应的值

  • 定义作用域插槽时,指定需要传递给父组件的值(以属性和属性值的形式)
<template>
  <div id="left-container">
    <h3>left 组件</h3>

    <!-- 定义一个作用域插槽 -->
    <slot name="myscopedslot" msg="我是一个作用域插槽"></slot>
  </div>
</template>
1
2
3
4
5
6
7
8
  • 在父组件中使用子组件(Left),并使用子组件的作用域插槽(即指定 Left 中 自定义插槽的内容)
<template>
  <div id="app">
    <h1>根组件</h1>

    <!-- 使用作用域插槽 -->
    <Left #myscopedslot="scope">
      <p>{{ scope }}</p>
      <p>{{ scope.msg }}</p>
    </Left>
      
    <!-- 使用作用域插槽,对接收到的对象进行解构赋值 -->
    <Left #myscopedslot="{ msg }">
      <p>{{ msg }}</p>
    </Left>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

这里的 scope 可以接收从子组件中传递来的以对象形式保存的

即 scope = { "msg": "我是一个作用域插槽" }

# 3. 自定义指令

# 3.1 自定义指令及其分类

自定义指令就是除官方提供的指令之外,用户自己定义的指令。

  • 私有自定义指令
  • 全局自定义指令

# 3.2 私有自定义指令

# 1. 基本语法格式:

在每个 vue 组件中,directives 选项内部声明的指令是私有自定义指令

<script>
export default {
  ...
  // 定义私有自定义指令
  directives: {
    自定义指令名称: { // 指向一个配置对象
      bind(el) { // bind() 方法,在该指令绑定到相应的 DOM 元素上时会自动触发,el 为被绑定元素的原生 DOM 对象
		// code...
      }
    },
    ... // 其他自定义指令
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在定义自定义指令时不用添加 v- 前缀,但是在使用时需要添加,以 v-xxx 的形式使用

示例:

<template>
<div id="private-directives-container">
  <h3>私有自定义指令组件</h3>
  <p v-pink>我是一段测试私有自定义指令 v-pink 的文字</p>
  <p v-aliceblue>我是一段测试私有自定义指令 v-aliceblue 的文字</p>
</div>
</template>

<script>
export default {
  name: "PrivateDirectives",

  // 定义私有自定义指令
  directives: {
    pink: {
      bind(el) {
        console.log('v-pink 私有自定义指令的 bind() 方法被触发了~');
        el.style.backgroundColor = 'pink';
      }
    },
    aliceblue: {
      bind(el) {
        console.log('v-pink 私有自定义指令的 bind() 方法被触发了~');
        el.style.backgroundColor = 'aliceblue';
      }
    },

  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 2. 使用 binding.value 获取用户传入的值:
<template>
<div id="private-directives-container">
  <p v-color="'red'">我是一段测试私有自定义指令 v-color 的文字</p>
</div>
</template>

<script>
export default {
  name: "PrivateDirectives",

  // 定义私有自定义指令
  directives: {
    color: {
      bind(el, binding) {
        console.log(binding);
        console.log(binding.value);
        el.style.backgroundColor = binding.value;
      }
    },

  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

image-20220309151523273

# 3. update() 函数:

如下,当点击了按钮之后,传入 v-color 的值会发生改变,所以会触发 v-color 指令的 update() 函数

<template>
<div id="private-directives-container">
  <p v-color="'red'">我是一段测试私有自定义指令 v-color 的文字</p>
  <button @click="color = 'green'">更新下面的背景颜色为绿色</button>
  <p v-color="color">我会改变背景颜色</p>
</div>
</template>

<script>
export default {
  name: "PrivateDirectives",

  data() {
    return {
      color: 'mediumpurple',
    }
  },

  // 定义私有自定义指令
  directives: {
    color: {
      bind(el, binding) { // 只是在第一次被绑定到元素上的时候会触发 bind() 函数
        console.log(binding);
        console.log(binding.value);
        el.style.backgroundColor = binding.value;
      },
      update(el, binding) { // 在 DOM 更新的时候会触发 update() 函数
        console.log(binding);
        console.log(binding.value);
        el.style.backgroundColor = 'green';
      }
    },

  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 4. 定义自定义指令时的函数简写:

如果自定义指令的 bind() 和 updata() 内部的代码完全相同(比如上面的案例),那么我们可以采取以下的定义方式

<script>
export default {
  ...
  // 定义私有自定义指令
  directives: {
    color(el, binding) { // 联合定义 bind() 函数和 update() 函数
        el.style.backgroundColor = binding.value;
    },
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11

# 3.3 全局自定义指令

# 1. 基本语法:

全局声明的自定义指令类似于全局声明的过滤器一样,需要在 main.js 中进行声明

  • bind() 和 update() 联合声明形式
Vue.directive('color-one', function (el, binding) {
  el.style.backgroundColor = binding.value;
});
1
2
3
  • 分别定义 bind() 和 update() 函数
Vue.directive('color-two', {
  bind(el, binding) {
    el.style.backgroundColor = binding.value;
  },
  update(el, binding) {
    el.style.backgroundColor = binding.value;
  }
});
1
2
3
4
5
6
7
8

# 3.4 将 axios 挂载到 vue

在 main.js 文件中添加如下代码:

import axios from "axios";

axios.defaults.baseURL = 'http://localhost:8080';
Vue.prototype.$http = axios;
1
2
3
4

这样在以后使用 axios 时直接使用 this.$http 就可以了

# 路由

# 1. 基本概念

# 1.1 介绍前端路由

路由(router)就是对应关系。通俗的讲,就是浏览器地址栏的 hash 地址和页面组件之间的对应关系。

# 1.2 前端路由的工作方式

  1. 用户点击页面上的路由链接,导致地址栏的 hash 地址发生变化
  2. 前端路由监听到 hash 地址的变化
  3. 前端路由会把当前 hash 地址对应的组件渲染到页面之中

# 2. vue-router 的基本使用

# 2.1 介绍

# 1. 什么是 vue-router:

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

# 2. 安装配置:
  • 安装 vue-router 包:

    npm install vue-router

  • 创建路由模块(模块就是 .js 文件):

    新建 src/router/index.js 路由模块,初始化如下代码

    // 1. 导入 vue 和 vue-router 包
    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
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  • 导入并挂载路由模块:

    在 vue 项目中,使用路由必须要在 main.js 文件中挂载路由的实例对象

    import Vue from 'vue'
    import App from './App.vue'
    import router from '@/router/index.js'
    
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
      // 挂载路由的实例对象
      router: router
    }).$mount('#app')
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    在模块化(ES、CommonJs)导入的时候,如果给定的是一个文件夹路径,则默认导入该文件夹下的 index.js

  • 声明路由链接占位符

    只要在项目中安装配置了 vue-router,就可以使用 < router-view > 这个组件了,其作用是一个占位符

    • 在路由模块中使用 routes 定义路由规则

    注意:这里定义的时候要省略掉 # 

    const router = new VueRouter({
      // 定义 hash 地址和组件之间的对应关系,即路由规则
      // 要省略 #
      routes: [
        { path: '/home', component: HomeComp },
        { path: '/movie', component: MovieComp },
        { path: '/about', component: AboutComp }
      ]
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    • 在使用的时候使用 < router-view > 进行占位

    当点击首页的时候,根据上面定义的路由规则,会使用 HomeComp 组件替换掉 < router-view >

    <a href="#/home">首页</a>
    <a href="#/movie">电影</a>
    <a href="#/about">关于</a>
    <hr>
    
    <router-view></router-view>
    
    1
    2
    3
    4
    5
    6

    当安装配置了 vue-router 之后就可以使用 < router-link > 来代替 < a > 标签了

# 3. 路由重定向:

{ path: '/xxx', redirect: '/yyy' }

const router = new VueRouter({
  // 定义 hash 地址和组件之间的对应关系,即路由规则
  // 要省略 #
  routes: [
    // 重定向路由规则
    { path: '/', redirect: '/home' },
    // 定义路由规则
    { path: '/home', component: HomeComp },
    { path: '/movie', component: MovieComp },
    { path: '/about', component: AboutComp }
  ]
})
1
2
3
4
5
6
7
8
9
10
11
12
# 4. 嵌套路由:
<template>
  <div id="about-container">
    <h1>我是 about 组件</h1>
    <router-link to="/about/left">左子组件</router-link>
    <router-link to="/about/right">右子组件</router-link>
    <hr>
    <div id="content-cotainer">
      <router-view></router-view>
    </div>
  </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
// index.js 路由模块中定义的嵌套路由规则
{
  path: '/about',
  component: AboutComp,
  // 使用 children 定义子级路由,注意前面不要加 /
  children: [
    { path: 'left', component: LeftComp },
    { path: 'right', component: RightComp }
  ]
}
1
2
3
4
5
6
7
8
9
10
  • 定义默认子路由

    • 方法一:

    使用重定向的方法,重定向到某一个子路由

    {
      path: '/about',
      component: AboutComp,
      redirect: '/about/left', // 使用重定向的方法定义默认子路由
      children: [  // 使用 children 定义子级路由,注意前面不要加 /
        { path: 'left', component: LeftComp },
        { path: 'right', component: RightComp }
      ]
    },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    • 方法二:

    对于 path 是空的子路由,是默认的子路由

    {
      path: '/about',
      component: AboutComp,
      children: [  // 使用 children 定义子级路由,注意前面不要加 /    
        { path: '', redirect: 'left' },  // 该数组中,path 为空的路由对应默认子路由
        { path: 'left', component: LeftComp },
        { path: 'right', component: RightComp }
      ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
# 5. 动态路由:

解决一条路由规则只能匹配一个路由的局限

{ path: '/xxx/:id', component: MyComp }
1

这条路由规则可以匹配 /xxx/1 /xxx/2 ... /xxx/n 所有的路由

  • 在 MyComp 组件中如何获取到 path 中的 id 值:

    • 方法一:

      在 MyComp 组件中,可以使用 this.$route.params.id 获取到对应的 id 值

      $route 路由的参数对象

      $router 路由的导航对象

    • 方法二:

      为当前的路由规则开启 props 传参

      { path: 'mitem/:id', component: MovieItem, props: true }
      
      1

      在 MyComp 组件中,props 中定义 'id' 获取到对应的 id 值

    现有路径 /mitem/123?name='aaa'&age=24,则有如下定义

    • / 后面的参数项为“路径参数”,需用 $route.params 访问
    • ? 后面的参数项为”查询参数“,需用 $route.query 访问
    • $route.fullPath = " /mitem/123?name='aaa'&age=24" 是完整路径
    • $route.path = " /mitem/123" 只是路径部分,不包含查询参数
# 6. 声明式导航和编程式导航:
  • 在浏览器中,通过点击链接实现导航的方式,叫做声明式导航

    普通网页中点击 < a >,vue 项目中点击 < router-link >

  • 在浏览器中,通过 API 方法实现导航的方式,叫做编程式导航

    普通网页中使用 location.href 跳转页面的方式

    vue 项目中,

    使用 $router.push('/xxx') 这种方法会记录浏览历史

    使用 $router.replace('/xxx') 这种方法不会记录浏览历史,是直接替换掉当前页面的内容

    使用 $router.go(1)/go(-1) 这种方法可以实现页面的前进和后退

# 7. 全局前置导航守卫:

to: 是要访问的页面的路由

from:是该请求的来源路由

next: 直接调用此函数表示放行

​ next() 放行

​ next('/xxx') 强制跳转到 '/xxx'

​ next(false) 不允许访问,强制停留在当前页面

// 设置全局前置导航守卫
router.beforeEach(function (to, from, next) {
  if (to.path === '/main') {
    // 如果想要访问 '/main' 就要检测有没有登录(检测有无 token)
    if (localStorage.getItem('token')) {
      // 如果已经登录了,那么就放行
      next()
    } else {
      // 如果没有登录,那么久强制跳转到登录页面
      next('/login')
    }
  } else {
    // 如果不是要访问 '/main' 就不需要登录权限,直接放行访问
    next()
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16