Vue2.x全家桶学习笔记

一、概述

1.1、什么是Vue

Vue 是一个 自底向上 逐层应用、注重 视图模块 、快捷构建 前端应用渐进式框架

1.2、为什么使用Vue

​ 随着项目规模的增大、业务流程的复杂度提升,现在主流的项目开发方式主要是前后端分离模式

  1. 灵活的前端应用:可以随时在不影响后端服务的情况下进行维护
  2. 便捷的扩展维护:可以在独立的前端项目中不影响原有功能的情况下实现高效的功能扩展和维护

​ 鉴于上面的开发要求,使用 Vue 框架的 优势

  • 快速、高效的项目开发
  • 功能、模块的高度复用
  • 大中小型各种项目场景
  • 市场主流的框架技术

1.3、快速入门

​ 最基础的单个 .html 网页作为一个独立的应用:

  • demo01quickstart.html
  • 优势1:使用 Vue 减少了页面中的 DOM 操作,提高了网页渲染效率优势
  • 优势2:让开发人员重点关注数据如何有效的渲染,而不是渲染过程/DOM过程,提高开发效率
DOCTYPE html>
<html lang="zh-CN">
<head>

    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initialscale=1.0">
    <title>第一个Vue应用title>
head>
<body>
    <div id="app">
        
        <h2>msg1: {{ message }} h2>
    div>
    <div>
        
        <h2>msg2: {{ message }}h2>
    div>
    
    <script src="./vue.js">script>
    <script>
        // 1.创建一个Vue实例
        const app = new Vue({
            // 2.el(element缩写):将实例挂载到id=app的div上,表示将实例关键到那个标签上
            el: "#app",
            // 3.data:声明使用的数据,表示实例上需要使用的数据
            data: {
                message: "hello vue!"
            }
        })
    script>
body>
html>

1.4、实例选项

Vue 项目中最核心的就是一个 和页面中某个标签节点挂载的 实例对象

  • 通过实例对象的选项,操作网页视图中的所有数据和数据的操作!

​ 学习之前,先了解 Vue 实例的常见选项:

  • let app = new Vue({…})
  • Vue2.x 主要通过实例选项,完成网页视图中数据操作,将这样的开发方式:选项式开发
选项 描述 备注
el 挂载页面节点的选项 必须要有的,不进行挂载的话数据就不会被渲染
data 声明实例中的数据的选项
methods 声明实例中的函数的选项
watch 声明实例中监听器/侦听器的选项,监控变量数据 在 watch 中添加一个与监听数据同名的函数
computed 声明实例中计算属性的选项,用于自动运算
filters 声明实例中过滤器的选项,用于条件过滤 {{需要过滤的数据:filters添加的过滤函数}}
components 声明实例中子组件的选项,用于视图模块复用 引入——>声明
created() 生命周期选项,表示实例创建
mounted() 生命周期选项,表示 DOM 挂载
distroyed() 生命周期选项,表示组件卸载
directives 注册自定义指令,同components一样,只不过一个是注册组件,一个是注册指令
mixins 组件中添加局部混入模块

二、vue基础语法

Vue 应用开发中,每个网页视图中都会使用到的基础操作语法

​ 基础语法:项目开发中使用非常频繁的语法,面试的时候需要有条理的、精确的进行描述的技术点

2.1、插值表达式

Vue 中通过固定的操作语法: Mustach 语法、插值表达式,用于渲染展示数据

​ 基础语法:

{{ 表达式 }}

​ 应用场景:

// 1、输出变量数据
{{ message }}

// 2、支持JS内置函数调用
{{ message.toUpperCase() }}

// 3、支持自定义函数的调用
{{ fn(message) }}

// 4、支持基础运算
{{ "消息:" + message}}

// 5、支持三元表达式
{{ editId ? '编辑数据' : '新增数据' }}
{{ age > 18 ? '成年': '未成年' }}

...

​ 推荐/适用场景:

// 1、变量数据渲染输出【频繁】
{{ message }}

// 2、执行自定义函数
{{ fn(message )}}

// 3、三元表达式
{{ editId ? '编辑数据': '新增数据' }}

// 4、其他函数调用执行、表达式运算:不推荐在插值表达式中执行
// Vue中推荐将数据的运算/操作过程,放在实例的函数中进行,而不是放在插值表达式进行
// 方便后期代码功能维护

2.2、常见指令

Vue 指令 directive ,描述了页面中的一些简单 DOM 操作的封装,通过指令语法完成基础 DOM 操作

  • Vue 指令:完成了基础 DOM 操作,让开发人员的精力集中到数据上而不是 DOM 操作代码上,提高开发效率
  • DOM 操作:网页视图中确实需要一些基础 DOM 操作,建议将操作方式封装成指令进行使用

(1)v-text

​ 作用:添加到标签属性中,以普通文本方式,渲染输出表达式结果数据

​ 语法:

​ 用途:

// 1、变量数据的输出[推荐]


// 2、执行基础运算

注意:

​ 输出的数据中如果包含标签字符,如

,输出时会将标签当成普通文本进行输出

(2)v-html

​ 作用:添加到标签属性中,以富文本的方式,解释并渲染输出富文本数据

​ 语法:

​ 用途:

// 1、普通数据输出[推荐]


// 2、支持基础运算

​ 注意:

  • 当前指令会将变量中标签字符,进行解释渲染输出,如

    输出的时候会将标签中的文本渲染成标题输出
  • 该指令输出的数据,一定要保障数据的安全性(不输出来历不明的数据,很容易造成代码注入漏洞)

(3)v-once

​ 作用:声明在标签属性中,用于一次性加载数据

​ 语法:

{{ message }}
// 使用v-once修饰之后,数据只被渲染一次,数据被修改后,显示页不变

​ 注意:

  • 网页视图中,希望某些数据只加载渲染一次,后续的更新不希望影响这个标签,选择使用这个指令
  • 如:用户登录之后进入管理平台,页头上显示的用户信息~只加载一次,使用 v-once 提高效率

(4)v-show

​ 作用:条件渲染指令,添加到标签的属性中,属性值是一个返回布尔值的表达式,用于条件判断是否显示指定的标签

​ 语法:

需要展示的数据

​ 注意:

  • v-show 指令主要是通过 display:none|block 控制标签的异常/显示,不占位置
  • 主要用在页面中需要频繁切换显示/异常状态的标签上,如选项卡

(5)v-if

​ 作用:条件渲染指令,添加到标签的属性上,通过属性值表达式返回的布尔值,判断是否加载指定的标签

​ 语法:

// 1.基础语法,条件判断是否加载指定标签
需要展示的数据
// 2.多条件语法,条件判断是否加载对应的标签 尊敬的用户您好,欢迎访问本系统 登录注册 // 3.多分支条件语法 角色:管理员 角色:会员 角色:游客

​ 注意:

v-if 条件渲染指令,是通过 DOM 加载方式显式或者隐藏指定的标签

​ 页面中需要频繁切换显示和隐藏,建议使用 v-show

​ 页面中不需要频繁切换显示/隐藏状态的,建议使用 v-if

​ 面试题:请描述 v-showv-if 的区别?

​ 一、应用场景

​ v-if:当组件中某块内容只会显示或者只会隐藏,不会因为页面上的操作再次改变显示状态时。

​ v-show:当组件中某块内容显示隐藏是可以变化时。

​ 二、两者实现显示隐藏的方式

​ v-if:动态的向dom树中添加或者删除dom元素

​ v-show:通过设置dom元素的display属性

​ 三、编译条件

​ v-if:惰性的,如果初始条件为假,则什么也不做,只有在条件第一次变为真时,才开始编译

​ v-show:在任何条件下都会被编译

​ 四、v-if解决问题的场景

​ 页签切换,返回等情况,出现表格或者图表渲染问题,可以考虑v-if去解决

(6)v-for

​ 作用:列表渲染指令,用于循环遍历序列数据

​ 语法:

  • {{ item }}
  • {{ index }} {{ item }}
  • {{ index }} {{ item }}

​ 注意:

  • v-for 指令:可以直接遍历数据,也可以遍历数据+索引;循环数据时添加 :key 状态属性
  • v-for 指令一般情况下 不建议v-if 指令一起使用
  • v-for 指令必须和 v-if 指令要一起使用时,建议 标签进行隔离

备注:为什么 v-forv-if 不建议同时使用?

​ 在处于同一节点的时候, v-for 优先级比 v-if 高。这意味着 v-if 将分别重复运行于每个 v-for 循环中。即先运行 v-for 的循环,然后在每一个 v-for 的循环中,再进行 v-if 的条件对比。所以,不推荐v-if和v-for同时使用

(7)v-bind

​ 作用:属性动态绑定指令,让标签的属性值可以是一个动态表达式的结果数据,增强页面功能

​ 语法:

// 1.基础语法


// 2.快捷语法:属性前面添加一个冒号,优化v-bind语法


// 3.优化语法:绑定多个数据



​ 注意:

v-bind 几种操作语法都需要熟练

(8)v-on

​ 作用:给标签添加事件的指令

​ 语法:

// 1. 事件绑定完整语法
添加了事件的标签
// 2. 快捷语法
添加了事件的标签

​ 注意:

  • v-on 指令绑定事件的标准语法、快捷语法都需要掌握

  • 标准事件操作中和其他系统修饰符、按键修饰符的混合使用语法

​ 面试题: v-on 指令是否可以绑定多个相同事件? v-on 指令是否支持未来元素?

// 绑定多个事件
盒子
//可以正常执行不同方法
盒子
//绑定多个相同事件,只执行最后一个方法
盒子
//这样绑定无效
// 快速给所在标签同时绑定多个事件和相应处理函数

不支持绑定未来元素

​ 修饰符:

  • .stop - 调用 event.stopPropagation()。
  • .prevent - 调用 event.preventDefault()。
  • .capture - 添加事件侦听器时使用 capture 模式。
  • .self - 只当事件是从侦听器绑定的元素本身触发时才触发回调。
  • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
  • .native - 监听组件根元素的原生事件。
  • .once - 只触发一次回调。
  • .left - (2.2.0) 只当点击鼠标左键时触发。
  • .right - (2.2.0) 只当点击鼠标右键时触发。
  • .middle - (2.2.0) 只当点击鼠标中键时触发。
  • .passive - (2.3.0) 以 { passive: true } 模式添加侦听器

(9)v-model

​ 作用:用于表单元素上,绑定操作数据

​ 语法:

// 1. username变量就可以和input输入框中的数据实现双向绑定

​ 注意事项:

  • v-model 指令可以实现变量和输入框中的数据双向绑定,是一个需要掌握的指令

  • 底层可以通过 @input 事件结合 :value 动态属性,实现 v-model

​ 面试题:请使用事件和属性赋值的方式,解释说明 v-model 执行原理?

(10)自定义指令

​ 项目实际开发中, Vue 内置的指令不一定满足项目功能的需要, Vue 单独预留了自定义指令开发 API ,可以让用户根据项目的实际需要定义自定义指令,完成定制化的 DOM 操作:

​ 语法:

// 全局指令(推荐、常用)
Vue.directive('指令名称', {
	bind: ()=> { // 最常用:只会执行一次,第一次绑定到标签/元素节点上时调用
		...
	},
	inserted: () => { // 绑定的节点,插入到父节点时调用
		...
	},
	update: () => { // 组件节点数据更新时调用
		...
	}
})

// 局部指令:只能被用于当前组件中
export default {
		directives: {
		// 声明局部指令
		指令名称: function() {
			bind: ()=> { // 最常用:只会执行一次,第一次绑定到标签/元素节点上时调用
				...
			},
			inserted: () => { // 绑定的节点,插入到父节点时调用
				...
			},
			update: () => { // 组件节点数据更新时调用
				...
			}
		}
	}
}

​ 注意:

​ 项目开发中,自定义指令的使用不可避免,标准项目结构中包含一个directive/ 文件夹用于存放 全局指令

Vue2.x全家桶学习笔记_第1张图片

2.3、样式操作

Vue 项目网页视图中的样式,可以通过常规 css 样式直接处理,项目中如果希望样式可以跟随数据状态的变化而产生对应的页面效果,就需要结合 Vue 特性完成样式动态操作

(1)class 样式处理

​ 样式会提前在 css 中声明完成,提供 class 名称给标签元素使用:

​ 对象操作语法(掌握):

// css class: box、active
// isBox变量true,给div设置box样式;否则不设置样式
// isActive变量true,给div设置active样式;否则不设置样式
需要操作动态样式的内容

​ 数组操作语法(了解):

// css class: box、active
data() {
	return {
		_box: 'box',
		_active: 'active'
	}
}

// 展示两个样式叠加的效果
需要操作动态样式的内容
需要操作动态样式的内容
-------- // 展示其中一个样式的效果
需要操作动态样式的内容

(2)style样式处理

​ 行内样式在网页开发中优先级较高,有些时候设置动态样式时需要行内样式支持

​ 基础语法:对象语法

// color: css样式名称
// fontColor:变量,存储具体样式值
// 通过控制fontColor变量中的数据,动态切换对应标签中字体颜色
需要动态管理样式的内容

​ 扩展语法:样式数据包含在 data 选项对象中处理

需要动态管理样式的内容
---- data选项 data() { return { divStyle: { color: 'blue', fontSize: '22px', backgrounColor: '#ef1e1f' } } }

三、高级语法

3.1、事件操作

(1)事件绑定

v-on 指令,进行单个或者多个事件绑定的操作方式

// 1.基础语法


// 2.快捷语法

​ 特殊事件绑定方式:

// 1. 同时绑定多个相同事件
// 单击事件同时触发多个处理函数


// 2. 同时绑定多个不同事件

(2)事件对象

Vue 中针对事件处理函数中,如果需要获取事件对象进行操作,包含两种操作方式:

  • 默认事件参数(不推荐)
  • e v e n t ∗ ∗ 内置事件对象(推荐):固定语法,如果函数中需要操作事件,调用函数式传递实际参数 ∗ ∗ event** 内置事件对象(推荐):固定语法,如果函数中需要操作事件,调用函数式传递实际参数 ** event内置事件对象(推荐):固定语法,如果函数中需要操作事件,调用函数式传递实际参数event


...
export default {
   methods: {
     fn(e) {
         // 如果事件函数没有传递参数,第一个形式参数就是事件对象
         var e = e ||window.event
     },
     fn2(e, id, name) {
         // e就是事件对象,可以直接使用($event)传递的数据
     }
   }
}

(3)事件修饰符

Vue 语法中提供了一套系统修饰符,用于增强事件函数的功能

事件修饰符 使用 功能描述
.stop 目标事件函数执行时,阻止事件冒泡
.prevent 目标事件函数执行时,阻止标签元素默认行为
.capture 发送事件操作,事件函数在捕获阶段执行
.self 只有发生在目标标签节点的事件上才能触发事件函数
事件函数只能由当前标签自身触发;不会捕获/冒泡触发
.once 一次性绑定事件,事件函数只会在事件第一次发生时触发

(4)系统修饰符

Vue 语法中为了让事件,可以结合键盘上功能键触发一些特殊操作,如:按下Ctrl 键的同时单击按钮。提供了一些列系统功能修饰符,增强事件操作

功能修饰符 使用 功能描述
.ctrl 用户按下 Ctrl(Win)|Control(Mac) 键同时触发事件操作
.alt 用户按下 Alt(Win)|Options(Mac) 键同时触发事件操作
.shift 用户按下 Shift(Win)|Shift(Mac) 键同时触发事件操作
.meta 用户按下 Win 徽标(Win)|command(Mac) 键同时触发事件操作
.left 事件处理函数响应鼠标左键
.right 事件处理函数响应鼠标右键
.middle 事件处理函数响应鼠标滚轮按下
.exact 精确事件控制修饰符 :按下 Ctrl 单击按钮就会触发事件,即使用户按下 Ctrl 键的同时按下了其它键也会触发事件**** :用户只有按下 Ctrl 功能键的同时点击才会触发事件,同时按下了其它键就不会触发事件****:用户没有按下任何按键的同时点击鼠标发生单击行为才会触发事件函数

(5)按键修饰符

Vue 除了系统功能键( Ctrl/Alt/Shift/Win )之外,也提供了普通按键配合操作事件的修饰符

按键修饰符 功能描述
.enter 用户按下回车键触发事件
.tab 用户按下制表键触发事件
.delete 用户按下删除键触发事件
.esc 用户按下取消键 Esc 触发事件
.space 用户按下空格键触发事件
.up 用户按下上方向键触发事件
.left 用户按下左方向键触发事件
.down 用户按下下方向键触发事件
.right 用户按下右方向键触发事件
自定义按键 用户可以定义键盘上的任意按键 配合触发对应事件
// 事件函数中可以使用 v-on:keyup.f1
Vue.config.keyCodes.f1 = 112

3.2、侦听器

Vue 提供了一种特殊的语法,可以通过侦听器函数完成对变量中数据监听,当变量中数据发生更新时自动触发事件处理函数完成一定的 功能

(1)监听普通变量

export default {
	data() {
		return {
			msg: "",
			title: ""
		}
	},
	watch: { // 当前实例中的侦听器选项,声明各种侦听函数
		// 1. 侦听器基础语法
		"msg": function(oldVal, newVal) {
			// msg中的数据一旦发生变化,这个函数就会立即执行
		},
		// 2. 侦听器完整语法
		"title": {
		handler(oldVal, newVal) {
			// title变量中的数据一旦发生变化,这个函数就会立即执行
			}
		}
	}
}

(2)监听对象属性

export default {
	data() {
    	return {
			loginForm: {
				username: "",
				password: ""
			}
		}
	},
	watch: {
    	// 监听对象中的某个属性
		"loginForm.username": function(oldVal, newVal) {
			// loginForm中的username数据一旦发生变化,这个函数就会立即执行
		}
	}
}

(3)监听整个对象

export default {
	data() {
		return {
			loginForm: {
				username: "",
				password: ""
				}
			}
		},
	watch: {
		// 监听对象
		"loginForm": {
			handler: function(oldVal, newVal) {
			// 对象中的数据一旦发生更新,就会立即执行的函数
				},
			deep: true,   // 开启对象深度监听
			immediate: true    // 开启首次渲染监视
		}
	}
}

3.3、计算属性

Vue 中提供了一个选项 computed ,用于完成一些自动运算,提供的好处

  • 避免了手工调用的过程
  • 缓存了上一次运算结果,提高了数据的复用,间接提升了页面效率

(1)基础语法

export default {
	data() {
		return {
			firstName:"",
			lastName: ""
		}
	},
	computed: {
	// 计算属性:本质上是一个函数
		fullName: function() {
			// 当firstName、lastName任意变量发生更新,这个函数内部代码重新执行得到结果
			return this.firstName +" " + this.lastName
		}
	}
}

计算属性的数据(当成属性变量直接访问):{{ fullName }}

(2)完整语法

​ 计算属性大部分情况下都是读取数据的,特殊情况下也可以通过计算属性进行数据赋值

export default {
	data() {
		return {
			firstName:"",
			lastName: ""
		}
	},
	computed: {
	// 计算属性:完整语法是一个对象
		fullName: {
			get() { // 1.读取数据
				// 当firstName、lastName任意变量发生更新,这个函数内部代码重新执行得到结果
				return this.firstName +" " + this.lastName
			},
			set(val) { // 2.设置数据/赋值
				let [fname, lname] = val.split(" ")
				this.firstName = fname
				this.lastName = lname
			}
		}
	}
}

计算属性的数据(当成属性变量直接访问):{{ fullName }}

3.4、过滤器

Vue 是一个注重视图的框架,数据可以在页面中进行完善的渲染展示,如果数据展示格式有特殊的要求,甚至提供了一个独立的专门用于数据格式化的选项:过滤器,用于按照要求转换数据

(1)全局过滤器

​ 基本语法:

// 基本语法:声明全局过滤器,可以在所有Vue实例中使用
Vue.filter("过滤器名称", value => {
	....
	return 用于展示的数据
})
 {{ message | 过滤器 }}

​ 解释:{{ message | 过滤器 }},message表示需要过滤的数据,过滤器表示一个函数名,这个函数名接受message作为参数进行数据处理,简称过滤

(2)局部过滤器

​ 基本语法:

export default {
	data() {},
	methods: {},
	filters: { // 声明局部过滤器
		// 整理整理的过滤器,只能用于当前组件(使用较少)
		"过滤器名称": value => {
			过滤数据处理
			return 用于展示的数据
		}
	}
}

(3)项目中的过滤器

​ 一般中大型项目开发时,需要将过滤器函数部分独立封装起来,方便后期进行功能升级和维护

  • 需要的过滤器,声明为全局过滤器
  • 统一封装到独立的文件夹中

Vue2.x全家桶学习笔记_第2张图片

// src/filters/An.js   单独文件封装过滤器,并导出
export default {
	bind (el, binding) {
		console.log(el, binding);
	}
}


备注:高级语法相关的一些面试题

  • 什么是侦听器?侦听器和普通函数的区别?
  • 什么是计算属性?计算属性和普通函数的区别?
  • 什么是过滤器?过滤器和普通函数的区别?
  • 侦听器和计算属性的区别?

四、组件化基础

4.1、声明组件

Vue 为了提高网页视图的复用性,提供了组件的概念:

​ 组件:包含了视图、样式、结构逻辑的完整视图

(1)全局组件

Vue 上直接声明全局组件,可以让这个组件在所有 Vue 实例中进行使用:

​ 基础语法:

Vue.component("组件名称", {
	template: "组件的页面模板",
		data() {
			return {
				// 组件中的数据
			}
		},
		methods: {
			// 组件中的函数
		}
})

​ 网页中使用


<组件名称>

(2)局部组件/子组件

​ 可以在组件内部声明独立的组件,通过 Vuecomponents 选项注册子组件,通过父子组件嵌套方式完成页面中视图的复用,提高开发效率

let 子组件名称 = {
	template: "",
	data() {
		return {}
	},
	methods: {},
		...
}

export default {
	components: { // 声明子组件
		"子组件名称"
	}
}

<子组件名称>

(3)组件复用

​ 常规模式下声明全局组件、局部组件的方式,不利于小型项目开发!

  • 常规组件声明方式,导致组件的语法比较繁琐,降低了开发效率
  • 项目中实际应用组件时,通过会结合 webpack加载器 对组件进行单文件封装 .vue ,实现组件化开发

4.2、vue-loader

​ 将常规的组件声明方式,转换成 .vue 单文件组件封装,实现组件化应用开发

  • webpack 支持
  • .vue 加载器支持

​ 主要依托于 webpack 构建的项目:

$ webpack init

​ 通过配置文件,加载项目中的特殊文件: webpack.config.js

// webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
    mode: 'development',
    module: {
        rules: [
            {
                test: /\.vue$/,
                loader: 'vue-loader'
            },
            // 它会应用到普通的 `.js` 文件
            // 以及 `.vue` 文件中的 `

​ 父组件 Parent.vue :引入并添加子组件



(2)自定义属性

​ 组件中添加自定义属性,包含多种操作语法,既可以接受数据,还可以对数据进行基础的校验

直接接收数据

export default {
	// 自定义属性:通过数组声明需要的属性名称
	props: ['title', 'msg'],
	...
}

校验数据类型 | 默认值

export default {
	// 自定义属性:添加类型校验,如果数据不合法不会报错~控制台提示警告
	props: {
		title: { // 基础数据
			type: string,
			default: '默认标题'
		},
		favs: { // 爱好,数组
			type: Array,
			default: () =>{
				return ["前端开发", "音乐", "游戏"]
			}
		},
		loginForm: { // 表单,对象
			type: Object,
			default: () => {
				return {
					username: "",
					password: ""
				}
			}
		}
	}
}

(3)注意事项

Vue 中通过自定义属性传递数据,执行的语法规则是:单向数据流

  • 父组件可以通过自定义属性传递数据给子组件
  • 子组件中不能直接修改父组件数据

​ 数据需要参与操作:赋值组件数据

export default {
	// 自定义属性:商品购买数量,当前页面组件订单页面可以修改购买数量
	props: ['buyCnt'],
	data() {
		return {
			// 页面中渲染可以修改的变量:buyCount
			buyCount: this.buyCnt
		}
	}
}

​ 数据需要参与运算:计算属性

export default {
	// 自定义属性:商品单价,只需要参与运算
	props: ['price'],
	computed: {
		// 当前页面中渲染展示数据:total进行处理
		total: function() {
			return this.price * 购买数量
		}
	}
}

5.2、子父组件传值

​ 嵌套关系的组件中,子组件通过自定义事件的方式传递数据给父组件使用:

Vue2.x全家桶学习笔记_第4张图片

(1)基础语法

​ 子组件:Child.vue



​ 父组件: Parent.vue



(2)自定义事件

​ 自定义事件:就是开发人员自定义名称的事件,通过 Vue 提供的事件接口函数声明

  • 事件接口函数: Vue.prototype.$emit(事件名称,事件参数)
  • 组件中触发自定义事件: this.$emit(“事件名称”,事件参数)

​ 子组件中触发自定义事件的名称、其他组件中监听自定义事件的名称:尽量保持一致

.sync语法糖

组件通信,子->父

以前技术:

(1):子组件内恰当时机, this.$emit(‘自定义事件名’,值)

(2):父组件内,给子组件标签@自定义事件名="事件处理函数"在事件处理函数中,接到子传出的值,赋予给父变量

.sync语法糖:本质上还是用的上面的技术,但是语法上呢有些简化

(1):子组件内恰当时机, this.$emit( ’ update:props变量名’,值)

(2):父组件内,给子组件对应props变量名.sync=“父范围变量名”

.sync作用:它会在运行时,给当前绑定绑定自定义事件名和事件处理函数

@update:visible=“val => dialogVisible = val”

:visible="dialogVisible”还在

.sync场景:子->父快速传值并赋予给父范围内的变量

如果子->父要执行逻辑代码很多代码,这个.sync不能用了,还用我们自己绑定的事件

// 1. 子组件事件:
this.$emit("child-event")

// 父组件监听


// 2. 子组件事件
this.$emit("childEvent")

// 父组件监听

(3)注意事项

​ 子父组件传值时,如果子组件中的数据是后续生成的、需要通知父组件进行数据接收

  • 自定义事件:通过子组件中发生的事件,将数据传递给父组件进行处理

5.3、组件之间传值

​ 一个网页视图中,可能会包含多个层级关系不同的组件,这样的组件之间实现数据传递就需要借助中间对象完成数据的交互:消息总线(本质上就是一个空的Vue

Vue2.x全家桶学习笔记_第5张图片

(1)消息总线

​ 编辑 src/main.js

...
// Vue原型上,挂载了一个空的Vue实例对象,作为消息传输对象
Vue.prototype.$bus = new Vue
...

(2)发送数据

​ 发送数据的组件 Comp1.vue



(3)接受数据

​ 接收数据的组件: Comp2.vue



5.4、组件边界传值

Vue 中除了自定义属性/自定义事件完成数据的传递方式,还提供了一种快捷访问数据的方式:

  • 边界传值:给父子组件提供了各自的组件对象,通过组件对象直接访问对方的数据
  • $parent :当前组件的父组件对象
  • ref :当前组件中包含的子标签/子组件对象

Vue2.x全家桶学习笔记_第6张图片

(1)组件声明

​ 子组件: Child.vue



​ 父组件: Parent.vue



(2)$parent

​ 子组件中访问父组件的数据: $parent

  • 注意1:子组件中可以直接通过 $parent 访问和修改父组件中的数据
  • 注意2:当 父组件只包含一个/唯一一个子组件的情况下 ,允许子组件直接修改父组件数据


(3)ref

​ 父组件可以通过 ref 属性获取到子组件,直接获取和修改子组件中的数据



5.5、代理注入传值

​ 了解-组件的嵌套关系中,一个上层组件需要将数据注入给下层组件(不一定是直接的嵌套关系),使用代理注入的方式传递组件数据非常方便

Vue2.x全家桶学习笔记_第7张图片

(1)上层组件

​ 提供共享数据的组件



(2)下层组件

​ 使用数据的后代组件:通过注入的方式,直接访问上层组件提供的数据



5.6、本地缓存传值

​ 项目开发过程中,数据如果只是保存在 .vue 组件中,随着组件的切换或者网页的刷新,很容易造成数据的丢失,可以借助本地缓存或者会话缓存,完成数据的持久化使用!

​ 数据在组件中操作时,可以借助原生 JS 中的缓存语法,完成数据的保存和读取:

(1)缓存存储数据

  • 注意1:缓存中存储的数据, key 值尽量使用字符串, value 值尽量使用JSON 格式的字符串
  • 注意2:发送数据的组件中,将数据存储到缓存中
// 会话(页面)缓存
sessionStorage.setItem(key, value)

// 本地缓存
localStorage.setItem(key, value)

(2)缓存读取数据

  • 注意:会话缓存的数据,在完全关闭浏览器窗口时会销毁,注意数据的有效时间
  • 注意2:缓存中存储数据,占用浏览器内存,需要注意存储数据的大小!
  • 注意3:接受数据的组件中,可以通过固定函数读取缓存中的数据
// 会话缓存
let value = sessionStorage.getItem(key)

// 本地缓存
let value = localStorage.getItem(key)

5.7、插槽传值

​ 项目开发中为了优化组件中的代码,可以将一些功能代码拆分到子组件中,可以通过插槽的方式传递数据:

  • 父组件,将部分视图,传递给子组件
  • 子组件,将操作数据,传递给父组件

(1)匿名插槽

使用情况:当父组件需要无定向传递数据或者渲染页面时使用

​ 子组件: Msg.vue


​ 父组件: Parent.vue


(2)具名插槽

使用情况:父组件需要向子组件有差别,有固定位置的传递数据或者渲染页面

​ 子组件中通过命名属性的方式,提供了多个数据占位位置:

​ 子组件: Msg.vue


​ 父组件: Parent.vue


(3)作用域插槽(可实现数据子传父)

使用情况:当父组件需要使用子组件传递过来的数据时

使用口诀:

​ (1):在组件内,用(table-column写好了内部叫row)

​ (2):在插槽内,用template v-slot="变量名”(变量会收集slot身上属性和值形成对象)

​ 子组件可以通过插槽的 作用域变量 ,将数据传递给父组件进行使用

​ 子组件: Goods.vue



​ 父组件:


备注:以后学习了更加高级的语法之后,还有其他的组件数据传值的方式:

  • vuex 实现组件数据传值
  • router 实现路由组件之间传值

六、组件化复用

​ 项目开发中,组件是 Vue 项目中 视图的最小单元 ,通过组件的复用可以提高代码/视图的开发效率;

​ 注意:针对组成组件的每个选项,都可以通过特定的语法实现二次复用,进一步规范和提高开发效率

6.1、生命周期

Vue 中视图是以组件为单元,每个组件都包含创建、加载、运行、渲染、销毁的过程, Vue 中提供了特定的语法监控每个执行过程:称为生命周期

  • 可能在生命周期中,完成数据初始化、数据渲染等工作
  • 掌握程度:掌握-> 熟练-> 熟悉-> 了解-> 知道
生命周期钩子 功能描述
beforeCreate() 目标组件实例准备创建
真实 DOM:index.html 可以访问
虚拟 DOM :不可以访问
实例数据:不可以访问
掌握程度:了解
created() 目标组件实例创建完成
真实 DOM :可以访问
虚拟DOM :不可以访问
实例数据:可以访问
掌握程度:掌握
beforeMount() 目标组件上 DOM 准备挂载
真实 DOM :可以访问
虚拟 DOM :不可以访问
实例数据:可以访问
掌握程度:了解
mounted() 目标组件上 DOM 结构挂载完
成真实 DOM :可以访问
虚拟 DOM :可以访问
实例数据:可以访问
掌握程度:掌握
beforeUpdate() 目标组件上发生任意数据更新之前执行
真实 DOM :可以访问
虚拟 DOM :可以访问
实例数据:可以访问
掌握程度:了解
updated() 目标组件上发生任意数据更新之后执行
真实 DOM :可以访问
虚拟 DOM :可以访问
实例数据:可以访问
掌握程度:熟悉
beforeDestroy() 目标组件准备销毁时执行
destroyed() 目标组件销毁后执行的钩子函数

​ 项目在进行组件化开发时,通用静态视图的组件不需要频繁的创建和销毁,可以将组件进行缓存



 

​ 被缓存的组件包含两个声明周期:

生命周期钩子 功能描述
activated() 缓存组件页面上切换显示时执行:主要用于更新数据
掌握程度:掌握
deactivated() 缓存组件页面上切换隐藏时执行
掌握程度:熟悉

备注:关于生命周期的面试题

1、请简要描述组件的生命周期钩子都有哪些?有什么特点?**

  • 扩展1:简述 created()mounted() 区别
  • 扩展2:组件初始化实例数据,可以在 created()/beforeMount() 钩子中执行,你认为应该在那个钩子函数中初始化数据,为什么?
  • 扩展3:正常情况下,组件切换过程中是否会发生组件的销毁和重新创建?
  • 扩展4:简述 destroyed() 生命周期钩子函数有什么用途?

2、请简要描述在父子组件嵌套的情况下,父子组件生命周期执行顺序?**

  • 扩展1:简述父子组件嵌套时,子组件 mounted() 生命周期钩子函数什么时候执行?
  • 扩展2:简述父子组件嵌套时,父组件 created() 和子组件created() 分别什么时候执行?
  • 扩展3:简述父子组件嵌套时,父组件 mounted() 和子组件mounted() 分别什么时候执行?

6.2、混入模块

​ 组件开发过程中,可能会出现多个组件/全部组件中都出现了重复代码,这些重复代码可能出现在任意的实例选项中,为了提高组件中代码的复用性, Vue 语法中提供了混入 Mixin 模块,可以抽取并复用组件中重复的代码

​ 基本语法:抽取公共混入代码,封装到 mixins/ 文件夹中

export default {
  data() {},   // 所有/多个组件共享数据
  methods: {},  // 所有/多个组件共享函数
  created(){}, // 所有/多个组件共享生命周期钩子
  mounted(){}, // 所有/多个组件共享生命周期钩子
  ....
}

​ 全局混入:所有组件生效,编辑 src/main.js

...
import globalMixin from "../mixins/global.js"
Vue.mixin(globalMixin)
...

​ 局部混入:需要使用混入模块的组件中,通过 mixins 选项添加混入功能

# index.vue
import partMixin from '../mixins/part.js'
export default {
  mixins: ['partMixin'], // 组件中添加局部混入模块
  data() {...},
  methods: {...},
    ...
}

​ 项目中的应用

​ 项目开发中通常会单独维护一个 mixins/ 文件夹,存放项目中需要的全局混入模块和局部混入模块,提高组件中重复代码的复用性!

Vue2.x全家桶学习笔记_第8张图片

6.3、自定义组件

​ 项目中可能会出现一些页面上经常用到的视图(包含自己的处理数据和逻辑功能),可以通过 Vue 提供的组件注册语法,专门将特定的视图、数据、业务处理逻辑封装在一起形成一个自定义组件,项目中再次使用自定义组件时就可以直接导入注册,页面中添加使用即可!类似 element-ui 中的各种标签都是自定义组件

​ 基础语法:

// 1、通过Vue.component()声明组件: 常规组件的封装
// 2、通过Vue.extend()声明组件: 通用组件的封装(更加底层)

​ 基于类型的组件封装:

import Vue from 'vue'
import Component from 'vue-class-component'
// @Component 修饰符注明了此类为一个 Vue 组件

@Component({
	// 所有的组件选项都可以放在这里
	template: ''
})

export default class MyComponent extends Vue {
	// 初始数据可以直接声明为实例的 property
	message: string = 'Hello!'
	// 组件方法也可以直接声明为实例的方法
	onClick (): void {
	window.alert(this.message)
	}
}

6.4、自定义插件

​ 插件一般都是用于扩展 Vue 功能的工具模块,如 vue-router 扩展了路由功能!

  • 插件的功能并没有进行严格的限制,也可以实现一些简单组件的功能

封装插件:就是一个小型组件

// 封装的.vue视图
import MyPlugin from "./myplugin.vue"
// 暴露install()接口函数
MyPlugin.install = function (Vue, options) {
    // 1. 添加全局方法或 property
    Vue.myGlobalMethod = function () {
    	// 逻辑...
    }
    // 2. 添加全局资源
    Vue.directive('my-directive', {
    	bind (el, binding, vnode, oldVnode) {
    	// 逻辑...
    	}
	...
	})
	// 3. 注入组件选项
	Vue.mixin({
		created: function () {
			// 逻辑...
		}
		...
	})
	// 4. 添加实例方法
	Vue.prototype.$myMethod = function (methodOptions) {
		// 逻辑...
	}
}

​ 使用插件: Vue.use() 接口函数可以注册插件

...
import MyPlugin from '../plugins/myplugin'
Vue.use(MyPlugin)
...

备注:关于自定义插件和自定义组件的封装

  • 项目中需要一个复用的功能视图组件,使用频率非常高的情况下,如果业务逻辑并不是十分复杂的情况下可以考虑封装成插件使用
  • 项目中需要一个复用的功能视图组件,使用频率非常高的情况下,如果业务逻辑比较复杂,操作过程中会出现各种使用差异,可以考虑封装自定义组件使用

6.5、过渡动画

Vue 针对页面组件进行动态切换,出现的页面切换过程,提供了 .css 语法可以添加自定义动画效果,避免了开发人员去添加大量样式代码的实现,提高了动画的复用!

Vue2.x全家桶学习笔记_第9张图片

(1)过渡动画

​ 通用动画效果:


	<需要动画的组件/>

.v-enter{}             /* 组件进入前样式 */
.v-enter-active{}      /* 组件进入过程样式:动画 */
.v-enter-to{}          /* 组件进入后样式 */
.v-leave{}             /* 组件离开前样式 */
.v-leave-active{}      /* 组件离开过程样式:动画 */
.v-leave-to{}          /* 组件离开后样式 */

​ 自定义动画效果:


	<需要动画的组件/>

.mycomp-enter{}                   /* 组件进入前样式 */
.mycomp-enter-active{}            /* 组件进入过程样式:动画 */
.mycomp-enter-to{}                /* 组件进入后样式 */
.mycomp-leave{}                   /* 组件离开前样式 */
.mycomp-leave-active{}            /* 组件离开过程样式:动画 */
.mycomp-leave-to{}                /* 组件离开后样式 */

(2)列表组动画


  • 需要动画的内容:{{ item }}
  • .list-enter{}             /* 组件进入前样式 */
    .list-enter-active{}      /* 组件进入过程样式:动画 */
    .list-enter-to{}          /* 组件进入后样式 */
    .list-leave{}             /* 组件离开前样式 */
    .list-leave-active{}      /* 组件离开过程样式:动画 */
    .list-leave-to{}          /* 组件离开后样式 */
    

    七、vue扩展

    7.1、双向绑定原理

    Vue 框架特性之一: 数据的双向绑定 ,添加了框架支持,数据可以在逻辑代码中和视图中实现双向绑定,逻辑中的数据更新同时触发页面视图的更新;页面视图中数据的更新同时触发逻辑代码中的数据更新

    • .vue 页面结构进行渲染,得到虚拟 DOM 结构
    • 虚拟 DOM 结构抽象成 ACT 抽象树,通过 Object.defineProperty() 数据劫持监听变量
    • 观察者模块收集页面变量,当发生数据更新就会触发通知机制, diff 算法完成更新数据的提取,触发页面中的重新渲染,结合 ACT 抽象树 diff 算法处理,将需要更新的视图进行重新渲染,提高页面处理效率

    Vue2.x全家桶学习笔记_第10张图片

    ​ 面试题解析:

    1、简述 Vue2.x 中数据双向绑定原理?

    Vue2.x中数据双向绑定底层是通过Object.defineProperty()数据劫持方式实现
    

    2、简述 Vue2.x 中页面数据是如何发生更新?

    Vue2.x中将虚拟DOM结构抽取出来形成抽象树,然后通过diff算法完成差异化数据比较,最后对需要更新的视图进行重新渲染
    

    3、简述 Vue2.x 中什么是虚拟 DOM

    框架中将组件页面结构进行抽取,将抽取的数据形成DOM树结构加载到内存中,加载在内存中的DOM结构就是描述的虚拟DOM结构
    

    4、简述 v-for 循环执行时,如果遍历了10个数据,只有一个数据发生了更新,页面中是否会对10个数据都进行重新渲染?

    框架进行虚拟DOM加载时,通过观察者模块收集页面数据
    当页面中数据发生了更新,结合虚拟DOM树和diff算法定位需要更新的数据解构进行渲染,属于差异化渲染,可以在数据更新时提高页面加载渲染效率
    

    5、简述 Vue2.x 中你所理解的 diff 算法?

    diff算法属于一种差异化比较算法
    针对两个需要比较的数据,通过patch进行节点抽取和比较
    执行比较先处理入口节点比较,如果相同继续比较下一个节点,如果不同就先标记节点,然后执行尾部节点比较
    执行尾部节点比较,如果节点相同继续比较上一个节点,如果不同就先标记节点,然后从上一次开始节点处重新比较前置节点
    最终将需要更新的所有节点进行收集,将不需要比较的节点直接过滤,更新需要比较的节点
    

    7.2、v-model绑定原理

    v-model 是一个作用在表单上的双向绑定指令,完成了表单中的数据和视图中的数据的双向同步,底层执行原理和数据劫持实现的双向绑定原理存在一定的差异!

    ​ 实现过程中,主要通过表单元素的 @input 事件,事件中收集视图表单的数据将数据同步给逻辑变量;逻辑变量中的数据更新通过表单的 v-bind:value 动态属性的方式完成逻辑变量的数据自动更新到页面视图的过程

    Vue2.x全家桶学习笔记_第11张图片

    7.3、强制更新

    Vue 提供了一种语法,不需要观察数据是否发生变化直接强制更新数据/渲染页面!

    • Vue.prototype.$forceUpdate()
    • 组件中调用强制更新: this.$forceUpdate()

    ​ 关于强制更新:官方的描述是这样的

    在这里插入图片描述

    ​ 项目开发中需要操作的数据包含普通变量、数组、对象等等各种复杂数据,需要应用到各种复杂场景中,实现数据在页面中的合理的渲染!

    • 偶尔遇到的问题:逻辑代码中更新了变量/数组/对象的数据,页面视图中没有发生变化
    • 需求紧急的情况下 :没有多余的时间处理遇到的问题;可以在需要更新的组件中,业务函数执行的最后一行代码上添加this.$forceUpdate() 强制页面数据更新,实现需求目标
    • 开发人员需要明确 :一旦需要强制更新,肯定是代码中出现了问题导致数据更新没有触发页面渲染/页面数据没有触发自动同步;如果需要强制更新~一定要复盘开发流程找到问题所在解决强制更新问题!

    7.4、DOM更新

    ​ 项目中经常遇到这样的情况:需要给一个组件赋值

    • 编辑数据时,需要编辑编辑按钮后,给弹窗赋值需要编辑的数据
    • 编辑数据时,弹窗中需要给第三方组件(树形控件 el-tree )赋值需要默认勾选的数据

    ​ 出现的问题如下:

    • 赋值的数据是存在的,控制台可以打印展示
    • 操作的标签也是存在的,控制台可以打印展示
    • 问题:标签上/组件上,赋值数据失败,数据没有成功添加上去!

    ​ 出现问题的原因:

    • 父子组件嵌套时,父组件中给子组件设置数据时,子组件 DOM 结构还没有加载完成!
    • 父组件给子组件 DOM 赋值数据,子组件还没有执行到 mounted() ,赋值失败!

    ​ 父子组件生命周期执行过程:

    父组件               子组件
    -------------------------------------
    beforeCreate()
    created()
    beforeMount()
                       beforeCreate()
                       created()
                       beforeMount()
                       mounted()
    mounted()
    -------------------------------------   父组件methods函数中,给子
    组件设置数据(失败!)
    beforeUpdate()
                       beforeCreate()
                       created()
                       beforeMount()
                       mounted()
    updated()
    -------------------------------------
    

    Vue 提供了一个辅助函数,专门用于等待 DOM 更新完成后再去执行相关代码:

    • Vue.prototype.$nextTick()
    • 组件中使用: this.$nextTick( () => {… 函数中执行DOM操作 })
    • 注意事项: $nextTick() 一定放在数据更新代码的 后面 执行

    7.5、组件递归

    Vue 中的组件,为了提高组件的复用性,可以在组件页面中访问调用自身!

    • 组件自己调用自己:组件递归

    ​ 组件递归的场景:

    • 循环展示树形菜单

    • 视图: 展示菜单、展示菜单中的子菜单

    • 封装菜单展示组件:

      • 展示菜单、展示菜单子菜单

      • 如果子菜单中继续包含子菜单,调用当前组件继续展示子菜单
        Vue2.x全家桶学习笔记_第12张图片

    ​ 使用注意事项:

    ​ 组件的递归导致组件常驻内存,如果没有设置好结束条件很容易导致无限循环,造成内存消耗完毕出现内存溢出的问题,内存溢出导致程序崩溃!

    7.6、组件样式

    SPA 应用中,主要使用的是 SFC(Singleton File Component) 单文件组件

    • 给所有组件中出现的标签设置样式
    • 给当前组件中出现的标签设置样式
    • 给当前组件中的子组件/第三方设置样式

    (1)所有组件设置样式

    ​ 一般会在入口模块中设置通用样式

    • 注意:一般如果是通用样式,建议使用外部 .css 文件进行处理
    • 注意2:少量特定样式可以封装在组件中的 标签中
    # App.vue
    
    
    
    

    (2)当前组件设置样式

    ​ 组件中的标签设置样式,不希望影响其他组件,可以给组件的

    ​ 底层实际的编辑过程:抽取虚拟 DOM 结构时

    # Other.vue
    
    
    

    (3)设置第三方组件样式

    ​ 第三方组件(如 element-ui )默认已经自带了样式,我们需要在自己的组件中修改第三方组件样式

    • 提升优先级
    • 样式穿透
    # Other.vue
    
    
    
    # Other.vue
    
    
    

    八、vue-router路由

    ​ 官方文档:https://v3.router.vuejs.org/zh/installation.html

    vue-router 属于 Vue 生态技术中的核心技术之一,主要用于服务项目的页面切换功能!

    注意: VueVueRouter 版本对应关系

    • Vue2.x 对应 VueRouter3.x
    • Vue3.x 对应 VueRouter4.x

    8.1、基础语法

    Vue 项目中,添加 VueRouter 支持:安装依赖模块

    $ npm install vue-router@3 -S
    

    ​ 创建路由规则配置: src/router/index.js

    // 导入模块
    import Vue from "vue"
    import VueRouter from "vue-router"
    
    Vue.use(VueRouter)
    
    // 配置路由规则
    import Index from '../views/index.vue'
    
    const routes = [{
    	path: "/",
    	component: Index
    }]
    
    // 创建路由对象
    const router = new VueRouter({
    	routes, // 路由映射规则
    	mode: "history", // 路由匹配模式
    	linkActiveClass: "active", // 导航高亮样式
    	scrollBehavior: () => { // 页面滚动行为
    		return {x: 0, y: 0}
    	}
    })
    // 导出模块
    export default router
    

    ​ 实例上添加路由配置:

    // src/main.js
    ...
    import router from './router' // 导入路由模块
    ...
    new Vue({
    	router, // 实例上挂载路由模块
    	render: h => h(App)
    }).$mount("#app")
    

    8.2、基本配置

    (1)路由重定向

    ​ 重定向:将用户的访问路径,自动切换到其他路径访问对应的资源

    ​ 如:用户访问 http://localhost:8080/ ,访问路径: / ,重定向: /main

    浏览器地址: **http://localhost:8080/main**
    

    ​ 基本配置:

    {
    	path: "/",        // 配置用户访问路径
    	redirect: "/main" // 配置重定向路径
    }
    

    (2)路由别名

    ​ 别名:用户访问某个页面时,除了本来的访问路径之外的另一个访问路径

    ​ 如:用户访问 http://localhost:8080/index ,路径 /index 可以访问系统首页

    ​ 别名访问 http://localhost:8080/main ,路径 /main 也可以访问系统首页

    ​ 基本配置

    {
    	path: "/index",    // 用户配置 访问路径
    	alias: "/main",    // 配置访问 别名路径
    	component: Index   // 对应的页面组件
    }
    

    (3)声明式导航

    ​ 用户可以通过网页中的导航链接,直接访问到对应的页面组件

    • 导航链接:通过路由组件提供的 导航组件 实现,称为声明式导航

    ​ 基本语法:

    • 声明式导航组件,会自动根据路由的匹配模式,将导航链接编译成超链接让用户可以点击跳转访问页面
    首页
    登录
    注册
    

    (4)编程式导航

    ​ 开发人员可以将用户在页面中发生的操作,执行对应的事件,在事件函数的代码中完成页面跳转

    • 编程式导航:通过代码函数的执行实现页面跳转的过程

    ​ 基本语法:

    导航函数 功能描述
    $router.push(path) 保留本次访问记录,跳转显示路径对应的页面组件
    $router.replace(path) 丢弃本地访问记录,跳转显示路径对应的页面组件
    $router.back() 访问上一次访问记录
    $router.forward() 访问下一次访问记录
    $router.go(idx) 访问索引对应的访问记录

    (5)路由匹配模式

    ​ 路由路径在页面中的切换方式,称为路由动态匹配模式,主要包含两种匹配方式:

    • hash 模式:使用 URLhash 模拟完整路径,页面切换过程中不会导致整个页面重新加载
    • history 模式:充分利用浏览器 history.pushState 实现页面切换不会导致整个页面重新加载

    ​ 基本配置:

    const router = new VueRouter({
    	routes,  // 路由规则
    	mode: "hash",   // 路由匹配模式
    })
    

    (6)命名路由

    ​ 路由规则进行声明时,可以给规则对象添加 name 属性实现命名路由,方便导航时通过动态属性控制导航效果

    ​ 基本配置:

    {
    	name: "login", // 命名路由
    	path: "/login",
    	Component: Login
    }
    
    
    登录
    
    // 编程式导航
    this.$router.push({name: 'login'})
    

    (7)命名视图

    ​ 路由组件在页面中主要是通过 路由视图组件 进行显式

    
    
    

    ​ 一个页面中如果要同时匹配和展示多个路由组件的情况下,就可以启用命名视图:

    
    
    
    
    
    
    
    {
    	name: "index",
    	path: "/index",
    	components: {
    		header: MyHeader,
    		aside: Aside,
    		default: Index
    	}
    }
    

    (8)嵌套路由

    ​ 项目中如果出现多级页面切换,需要嵌套路由的支持

    • 顶级路由:主页、登录、注册
    • 二级路由:主页【首页、列表页、购物车、用户中心】

    ​ 基本配置:主要通过路由规则的 children 选项实现

    {
    	path: "/main",
    	name: "main",
    	component: Main,
    	children: [        // 嵌套路由
    		{name: "home",
    		path: "home",   // /main/home
    		// path: "/home",   // /home
    		component: Home
    		},
    		....
    	]
    }
    
    
    
    
    

    (9)路由传参-动态路由

    ​ 除了 Vue 项目本身自带的多个组件之间的数据传递方式之外,路由规则也可以实现不同页面之间的参数传递

    ​ 基本语法:路由规则定义参数

    {
    	name: "goodsdetail",
    	path: "/goodsdetail/:id", // 动态路由参数,参数名称id
    	component: GoodsDetail
    }
    

    ​ 导航传参:

    
    商品:小米
    商品:小米
    
    // 编程式导航传参
    this.$router.push('/goodsdetail/12')
    this.$router.push({name: 'goodsdetail', params: {id: 12}})
    

    ​ 页面组件接收参数:

    
    
    

    (10)路由传参-查询字符串

    ​ 路由规则处理中,可以通过 ?key=value&key2=value2 的查询字符串方式传递参数

    ​ 基本语法:路由规则不需要多余配置

    {
    	name:"goodsdetail",
    	path: '/goodsdetail',
    	component: GoodsDetail
    }
    

    ​ 导航传参:

    
    商品:华为
    商品:华为
    
    // 编程式导航
    this.$router.push('/goodsdetail?id=12')
    this.$router.push({name: 'goodsdetail', query: {id: 12}})
    

    ​ 页面组件接收参数:

    
    
    

    (11)路由严格匹配

    ​ 如果页面声明式导航中出现了多个相同或者带有包含关系的导航链接,很容易出现多个导航同时高亮的情况

    VueRouter 中提供了一个关键属性: exact ,可以将路由路径模糊匹配方式转换成精确匹配方式,避免导航同时高亮显示的情况

    
    
    LOGO
    首页
    登录
    

    (12)404路由

    ​ 页面配置完成后,如果用户访问了不存在的路径,需要给用户一个友好的页面提示

    ​ 路由:通配符方式配置404路由实现

    const routes = [
    	{
    		...
    	},{
    		path: "*",
    		component: NotFount  // 配置显示404路由页面,给用户一个自定义提示页面
    	}
    ]
    

    8.3、高级配置

    (1)路由元数据

    ​ 路由规则对象配置过程中,可以添加一些基础数据增强路由功能:如路由访问权限、路由页面标题等等

    ​ 基本语法:

    {
    	name: "ucenter",
    	path: "ucenter",
    	component: Ucenter,
    	meta: { // 路由元数据
    		title: "用户中心",
    		roles: ['admin', 'manage', 'member']
    	}
    }
    

    ​ 页面组件获取数据

    
    

    (2)页面滚动行为

    VueRouter 路由对象中,默认可以记录当前页面滚动条位置,增强用户访问体验

    • 注意:大部分情况下,网站网页视图访问时都是从顶部直接访问,不需要额外记录位置

    ​ 基本配置:

    const router = new VueRouter({
    	routes,
    	mode: 'hash',
    	scrollBehavior: (to, from, savePosition) => {
    		// to: 即将跳转到的组件
    		// from: 跳转之前的组件
    		// savePosition: 跳转之前组件中滚动条位置
    		// 需求:需要保留位置,结合vuex/缓存记录某个页面中的位置,当跳转指定页面时回到历史访问位置
    		return {x: 0; y: 0}
    	}
    })
    

    (3)路由懒加载

    SPA 单页面应用,常规开发模式中最大的问题:用户访问第一个页面时,会触发项目中所有的路由规则中添加的组件全部加载,导致首页访问缓慢!

    • VueRouter 提供了一种路由懒加载方式,提高首页访问效率
    • 首页:第一个访问页面,正常页面组件,直接导入使用
    • 其他页面:懒加载方式导入,只有用户访问这个组件时才会加载这个组件

    ​ 基本语法:

    {
    	name: "about",
    	path: "/about",
    	component: () => import("../views/About/index.vue")    // 懒加载语法 | Vue中的异步组件
    }
    

    (4)导航守卫

    ​ 前端应用中,路由实现了页面和页面之间的切换,如果需要在页面切换时进行条件判断、权限验证等中间功能时,需要拦截用户路由请求, VueRouter 通过路由导航守卫实现:

    • 全局守卫
    • 路由独享守卫
    • 组件内守卫
    1. 全局守卫
    
    // 全局前置守卫:通过路由对象添加
    router.beforeEach( ( to, from, next ) => {
    	// 拦截用户页面导航请求,进行权限验证等操作
    
    	// 允许访问下一个导航页面
    	next()
    })
    
    // 全局解析守卫
    router.beforeResovle(( to, from, next) => {
    	// 拦截用户页面导航请求(可以访问路由元数据、导航组件基础数据..)
    
    	// 允许访问下一个导航页面
    	next()
    })
    
    // 全局后置钩子
    router.afterEach( ( to, from ) => {
    	// 处理导航完成后的资源数据
    })
    
    2. 路由独享守卫:配置在单个路由中,拦截指定路由的请求
    {
    	name: 'Ucenter',
    	path: 'Ucenter',
    	components: {
    		header: CommHeader,
    		default: Ucenter
    	},
    	beforeEnter(to, from, next) {
    		// 路由独享守卫,拦截访问这个路由的页面请求
    
    		// 允许访问当前路由
    		next()
    	}
    }
    
    3. 组件内守卫:配置在单个组件中,拦截组件的访问请求
    export default {
    	data() {
    		return {}
    	},
    	methods: {},
    	....
    	beforeRouteEnter(to, from, next) {},
    	beforeRouteUpdat(to, from, next) {},
    	beforeRouteLeave(to, from, next) {}
    }
    

    ​ 导航守卫的执行顺序:

    1. 导航被触发。

    2. 在失活的组件里调用 beforeRouteLeave 守卫。

    3. 调用全局的 beforeEach 守卫。

    4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。

    5. 在路由配置里调用 beforeEnter

    6. 解析异步路由组件。

    7. 在被激活的组件里调用 beforeRouteEnter

    8. 调用全局的 beforeResolve 守卫 (2.5+)。

    9. 导航被确认。

    10. 调用全局的 afterEach 钩子。

    11. 触发 DOM 更新。

    12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

    (5)数据获取方式[了解]

    ​ 数据获取方式描述了目标组件中数据的获取行为,通过页面组件切换的中间过度效果提高用户访问体验

    ​ 有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

    • 导航完成之后获取 :先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
    # Goods.vue
    export default {
    	data() {
    		return {
    			goodsList: []
    		}
    	},
    	created() { // 导航已经完成
    		// 发送请求获取数据
    		this.getGoodsList()
    	}
    }
    
    • 导航完成之前获取 :导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
    # Goods.vue
    export default {
    	data() {
    		return {
    			goodsList: []
    		}
    	},
    	beforeRouteEnter (to, from, next) { // 导航没有完成
    		// 获取数据
    		this.getGoodsList()
    		next()
    	},
    }
    

    ​ 从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。

    (6)过渡动效[了解]

    ​ 路由页面组件切换时添加过渡动画,结合 Vue 过渡动效实现:

    
    	
    
    
    .v-enter{}             /* 组件进入前样式 */
    .v-enter-active{}      /* 组件进入过程样式:动画 */
    .v-enter-to{}          /* 组件进入后样式 */
    .v-leave{}             /* 组件离开前样式 */
    .v-leave-active{}      /* 组件离开过程样式:动画 */
    .v-leave-to{}          /* 组件离开后样式 */
    

    (7)导航故障[了解]

    ​ 导航故障通常通过 404****路由 就可以实现处理方案:如下三种情况导致页面可能出现空白!

    • 用户已经位于他们正在尝试导航到的页面
    • 一个导航守卫通过调用 next(false) 中断了这次导航
    • 一个导航守卫抛出了一个错误,或者调用了 next(new Error())

    VueRouter 中提供了一中导航异常处理语法,可以给用户提供一个错误信息提示页面/ 500****错误

    import VueRouter from 'vue-router'
    const { isNavigationFailure, NavigationFailureType } = VueRouter
    // 正在尝试访问 admin 页面
    router.push('/admin').catch(failure => {
    	if (isNavigationFailure(failure,NavigationFailureType.redirected)) {
    		// 向用户显示一个小通知
    		showToast('Login in order to access the admin panel')
    	}
    })
    

    8.4、路由封装

    ​ 正常项目开发中,通过 src/router/index.js 就可以实现路由模块的封装但是随着项目规模增加,单个文件中封装路由模块导致代码量过多无法/很难维护出现!

    (1)中小型项目

    |-- project/
    	|-- node_modules/
    	|-- public/
    	|-- router/
    		|-- index.js   路由封装 
    	|-- utils/
    	|-- src/
    		|-- assets/
    		|-- ...
    		|-- App.vue
    		|-- main.js
    	|- ...
    

    (2)中大型项目

    |-- project/
    	|-- node_modules/
    	dsf public/
    	|-- router/
    		|-- dynamicRouter.js  动态路由规则封装
    		|-- permission.js  路由导航守卫封装
    		|-- routes.js   路由映射规则封装
    		|-- index.js    路由模块[导入动态路由、导航守卫、映射规则...]
    	|-- utils/
    	|-- src/
    		|-- assets/
    		|-- ...
    		|-- App.vue
    		|-- main.js
    	|- ...  
    

    九、vuex状态管理

    待补充

    9.1、基础配置

    (1)入口模块

    (2)数据模块

    9.2、核心单元

    (1)state

    (2)mutations

    (3)actions

    (4)getters

    (5)modules

    (6)namespaced

    (7)strict

    9.3、进阶配置(映射)

    ​ 映射是将函数配置中的数据和方法,映射到本组件,相当于在本组件中添加这些方法和属性

    (1)mapState

    (2)mapMutations

    (3) mapActions

    (4)mapGetters

    (5)表单数据处理

    十、axios异步模块

    10.1、概述

    (1)什么是 axios

    axios 是一个异步请求框架!

    官方网站: https://www.axios-http.cn/

    axios 是一个异步请求库/框架,可以用于浏览器和 nodejs 环境

    底层是通过 PromiseJS ajax 进行封装实现

    (2)为什么要使用 axios

    ​ 前端应用中异步请求方式的实现技术有很多

    • 原生 JS Ajax
    • jQuery Ajax
    • Axios ajax
    • Fetch ajax

    Vue 项目应用中,不推荐浏览器性能消耗巨大 DOM 操作,对于其他的 Ajax 技术的选型依据:

    • 原生 JS Ajax :底层实现过程复杂,不同浏览器兼容性较差,很容易出现编写 BUG
    • jQuery AjaxjQuery 本质上对于 JS 封装,核心就是 DOM 操作,Ajax 只是其中一个实现; Vue 项目中引入 jQuery 不仅引入了需要的Ajax****代码 ,同时引入了大量无用的 DOM 操作函数,消耗了资源利用!
    • Axios Ajax纯粹的、基于 Promise 实现的 Ajax 框架; Vue 引入****Axios 不仅可以使用 Ajax 技术、同时解决了异步回调地狱的问题,还不****用引入多余的其他的代码!
    • Fetch Ajax :底层是通过浏览器 API 实现的 Ajax ,并没有使用原生JS XMLHttpRequeset****异步对象 ,目前对于浏览器的兼容性还存在一些问题

    10.2、基础请求

    ​ 项目中添加 axios 支持,属于一个第三方模块,需要安装和引入才能正常使用

    Ⅰ. 安装

    $ npm install axios -S 
    

    Ⅱ. 引入

    ​ 需要使用 axios 的组件中,单独引入 axios

    ​ 如:编辑商品管理组件 Goods.vue

    
    
    

    Ⅲ. 请求

    ​ 直接调用 axios 中提供的请求函数,完成数据请求即可!

    • 发送 get 请求
    // 字符串中拼接参数
    axios.get("/goods?id=12").then(response => {
    	console.log("服务器响应数据:", response)
    }).catch(error=> {
    	console.log("请求发生了错误:", error)
    })
    
    // 发送参数对象(不需要手工拼接参数:常用)
    axios.get('/goods', { params: {id: 12} }).then(response => {
    	console.log("服务器响应数据:", response)
    }).catch(error => {
    	console.log("请求发生了错误:", error)
    })
    
    // async / await配合操作(直接通过同步方式编写异步请求,推荐)
    async function getGoods() {
    	const response = await axios.get("/goods", {params: {id:12}})
    	console.log("请求到的数据:", response)
    }
    
    • 发送 post 请求
    // 发送不带参数的post请求
    axios.post('/login').then(response => {
    	console.log("服务器返回数据:", response)
    })
    
    // 附带参数的post请求
    axios.post('/login', {data: {username:"admin", password:"123"}}).then(repsonse => {
    	console.log("服务器返回了数据:", response)
    })
    
    • 发送其他请求: axios 针对 HTTP1.1 规范中的请求方式,提供了各种请求函数

    Vue2.x全家桶学习笔记_第13张图片

    • 通用请求函数 (推荐使用)
      • 侧重点:更加方便在项目中进行封装使用,封装的时候需要考虑不同的请求方式差异
    axios({
    	url: 'http://localhost:3000/goods/list',
    	method: 'GET',   // GET POST PUT DELETE....
    	params: {page:1, size:10},   // get参数
    	// data: {id: 12}, // post参数
    	headers: {'X-Requested-With': 'XMLHttpRequest'},  // 请求头
    }).then(response => {
    	console.log("服务器返回的数据")
    })
    

    10.3、请求配置

    axios 除了提供的一些快捷函数 axios.get()/post()/put()… 这些操作之外,项目中推荐使用通用性更高的配置方式完成请求的发送

    axios({
    	// 请求配置
    }).then(response => {
    	console.log("获取服务器响应数据", response)
    })
    

    axios 中提供了常用的 请求配置

    • 参考官方文档: https://www.axios-http.cn/docs/req_config
    • url :请求地址
    • method :请求方式
    • paramsGET 参数
    • datapost 参数
    • headers :请求头
    • timeout :请求超时时间
    {
    	// `url` 是用于请求的服务器 URL  [掌握]
    	url: '/user',
    
    	// `method` 是创建请求时使用的方法  [掌握]
    	method: 'get', // 默认值
    
    	// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
    	// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对URL
    	baseURL: 'https://some-domain.com/api/',
    
    	// `transformRequest` 允许在向服务器发送前,修改请求数据
    	// 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
    	// 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
    	// 你可以修改请求头。
    	transformRequest: [function (data, headers) {
    		// 对发送的 data 进行任意转换处理
    		return data;
    	}],
    
    	// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
    	transformResponse: [function (data) {
    		// 对接收的 data 进行任意转换处理
    		return data;
    	}],
    
    	// 自定义请求头,可用于解决跨域问题  [掌握]
    	headers: {'X-Requested-With': 'XMLHttpRequest'},
    
    	// `params` 是与请求一起发送的 URL 参数;
    	// GET请求参数  [掌握]
    	params: {
    		ID: 12345
    	},
    
    	// `paramsSerializer`是可选方法,主要用于序列化`params`
    	// (e.g. https://www.npmjs.com/package/qs,http://api.jquery.com/jquery.param/)
    	paramsSerializer: function (params) {
    		return Qs.stringify(params, {arrayFormat: 'brackets'})
    	},
    
    	// `data` 是作为请求体被发送的数据
    	// 发送POST参数   [掌握]
    	data: {
    		firstName: 'Fred'
    	},
    
    	// 发送请求体数据的可选语法
    	// 请求方式 post
    	// 只有 value 会被发送,key 则不会
    	data: 'Country=Brasil&City=Belo Horizonte',
    
    	// `timeout` 指定请求超时的毫秒数。
    	// 如果请求时间超过 `timeout` 的值,则请求会被中断     [掌握]
    	timeout: 1000, // 默认值是 `0` (永不超时)
    
    	// `withCredentials` 表示跨域请求时是否需要使用凭证
    	withCredentials: false, // default
    
    	// `adapter` 允许自定义处理请求,这使测试更加容易。
    	// 返回一个 promise 并提供一个有效的响应 (参见lib/adapters/README.md)。
    	adapter: function (config) {
    		/* ... */
    	},
    
    	// `auth` HTTP Basic Auth
    	auth: {
    		username: 'janedoe',
    		password: 's00pers3cret'
    	},
    
    	// `responseType` 表示浏览器将要响应的数据类型
    	// 选项包括: 'arraybuffer', 'document', 'json', 'text','stream'
    	// 浏览器专属:'blob'
    	responseType: 'json', // 默认值
    
    	// `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
    	// 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
    	// Note: Ignored for `responseType` of 'stream' or clientside requests
    	responseEncoding: 'utf8', // 默认值
    	
    	// `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
    	xsrfCookieName: 'XSRF-TOKEN', // 默认值
    	
    	// `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
    	xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值
    	
    	// `onUploadProgress` 允许为上传处理进度事件
    	// 浏览器专属
    	onUploadProgress: function (progressEvent) {
    		// 处理原生进度事件
    	},
    
    	// `onDownloadProgress` 允许为下载处理进度事件
    	// 浏览器专属
    	onDownloadProgress: function (progressEvent) {
    		// 处理原生进度事件
    	},
    
    	// `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
    	maxContentLength: 2000,
    	
    	// `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
    	maxBodyLength: 2000,
    	
    	// `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是reject promise。
    	// 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或`undefined`),
    	// 则promise 将会 resolved,否则是 rejected。
    	validateStatus: function (status) {
    		return status >= 200 && status < 300; // 默认值
        },
    
         // `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
         // 如果设置为0,则不会进行重定向
         maxRedirects: 5, // 默认值
    
         // `socketPath` 定义了在node.js中使用的UNIX套接字。
         // e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
         // 只能指定 `socketPath` 或 `proxy` 。
         // 若都指定,这使用 `socketPath` 。
         socketPath: null, // default
    
         // `httpAgent` and `httpsAgent` define a custom agent to beused when performing http
         // and https requests, respectively, in node.js. This allows options to be added like
         // `keepAlive` that are not enabled by default.
         httpAgent: new http.Agent({ keepAlive: true }),
         httpsAgent: new https.Agent({ keepAlive: true }),
    
         // `proxy` 定义了代理服务器的主机名,端口和协议。
         // 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
         // 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
         // `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
         // 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers`中已存在的自定义 `Proxy-Authorization` 请求头。
         // 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
         proxy: {
         	protocol: 'https',
         	host: '127.0.0.1',
         	port: 9000,
         	auth: {
         		username: 'mikeymike',
         		password: 'rapunz3l'
         	}
         },
                                                 
         // see https://axios-http.com/zh/docs/cancellation
         cancelToken: new CancelToken(function (cancel) {
         }),
                                                 
         // `decompress` indicates whether or not the response bodyshould be decompressed
         // automatically. If set to `true` will also remove the'content-encoding' header
         // from the responses objects of all decompressed responses
         // - Node only (XHR cannot turn off decompression)
         decompress: true // 默认值
    }
    

    10.4、响应数据结构

    axios 针对服务器接口返回的数据进行了 二次包装 ,返回了响应对象,对象内部的数据描述:

    ​ 参考官方文档: https://www.axios-http.cn/docs/res_schema

    data :服务器接口返回的数据,被封装在这个选项中返回了,我们需要的数据可以从 response.data 中获取和使用

    {
       // `data` 由服务器提供的响应
       // 【掌握】:服务器接口返回的真实数据,包含在该选项中
       data: {},
    
       // `status` 来自服务器响应的 HTTP 状态码
       status: 200,
    
       // `statusText` 来自服务器响应的 HTTP 状态信息
       statusText: 'OK',
    
       // `headers` 是服务器响应头
       // 所有的 header 名称都是小写,而且可以使用方括号语法访问
       // 例如: `response.headers['content-type']`
       headers: {},
    
       // `config` 是 `axios` 请求的配置信息
       config: {},
    
       // `request` 是生成此响应的请求
       // 在node.js中它是最后一个ClientRequest实例 (in redirects),
       // 在浏览器中则是 XMLHttpRequest 实例
       request: {}
    }
    

    10.5、拦截器配置

    axios 主要完成了前端向后端发送请求的过程,从后端接口中获取数据并使用!

    ​ 但是常规的请求和响应处理时,会包含一些公共的代码片段,如:

    • 请求:请求中需要处理请求头,如添加 token 令牌
    • 响应:针对响应数据进行处理,如拆分数据、处理 404 错误、处理 500 错误

    Vue2.x全家桶学习笔记_第14张图片

    ​ 官方文档: https://www.axios-http.cn/docs/interceptors

    ​ 基本语法:

    // 通过axios实例对象,声明拦截器
    axios.interceptors.request.use( request => {
    	// 拦截请求,对请求进行处理
    	return request
    })
    
    axios.interceptors.response.use( response => {
    	// 拦截响应,对响应数据进行处理
    	// 如:从响应对象中,拆出服务器返回的真实数据
    	return response.data
    })
    

    ​ 封装的 axios ,编辑 src/utils/request.js

    /** 封装axios模块 */
    import axios from "axios"
    
    // 创建实例
    const instance = axios.create({
    	// 实例中封装的公共代码
    	baseURL: 'http://localhost:3000'
    })
    
    // 实例上添加拦截器
    instance.interceptors.request.use(request => {
    	// 针对请求进行通用设置,如设置请求头
    	return request
    })
    
    instance.interceptors.response.use(response => {
    	// 针对响应数据进行通用处理,如处理404\500错误等等
    	// 如;拆分服务器返回的真实数据
    	return response.data
    })
    
    export default instance
    

    思考:前端应用中的 路由导航守卫axios 中的 拦截器 有什么区别?

    • 相同点:导航守卫和拦截器都可以拦截用户请求
    • 导航守卫:拦截路由请求,从一个页面切换到另一个页面的请求
    • 拦截器:拦截 ajax 数据请求,前端页面中发送请求给数据接口

    10.6、取消请求

    Vue 中一个页面组件 Goods.vue 展示商品数据的时候,需要给后端接口发送请求获取要展示的数据;但是如果数据还没有展示的情况下用户又切换到其他页面About.vue ,这时候前面这个页面组件 Goods.vue 中发送的请求就不需要处理,为了节省系统资源需要取消 ajax 请求

    axios 提供了取消请求的实现方式:

    ​ 官方文档: https://www.axios-http.cn/docs/cancellation

    ​ 代码操作:

    # axios版本有关系:0.22+版本
    // 1、创建一个浏览器中的请求控制对象
    const reqController = new AbortControll()
    
    // 2、发送请求时需要添加请求信号
    axios.get("/goods/list", {
    	signal: reqController.signal // 添加一个信号控制器
    }).then(response => {
    	console.log("服务器返回的数据:", response)
    })
    
    // 3、某些场景中需要取消请求
    reqController.abort()
    

    ​ 如果工作过程中维护的是一个老版本 axios 的项目:需要使用令牌的方式取消请求

    const CancelToken = axios.CancelToken;
    const source = CancelToken.source();
    axios.get('/user/12345', {
    	cancelToken: source.token
    }).catch(function (thrown) {
    	if (axios.isCancel(thrown)) {
    		console.log('Request canceled', thrown.message);
    	} else {
    		// 处理错误
    	}
    });
    axios.post('/user/12345', {
    		name: 'new name'
    	}, {
    		cancelToken: source.token
    })
    
    // 取消请求(message 参数是可选的)
    source.cancel('Operation canceled by the user.');
    

    10.7、项目封装

    Vue 项目中需要对 axios 做一个封装,方便在项目中直接使用 axios 完成请求、请求拦截、响应拦截处理、取消请求等各种操作方式,一般项目中封装方式:

    创建 src/utils/request.js

    ​ 封装基础实现:参考https://blog.csdn.net/DevilAngelia/article/details/125778269

    /** 封装axios模块 */
    import axios from "axios"
    // 创建实例
    const instance = axios.create({
        // 实例中封装的公共代码
        baseURL: 'http://localhost:3000'
    })
    // 实例上添加拦截器
    instance.interceptors.request.use(request => {
        // 针对请求进行通用设置,如设置请求头
        return request
    })
    instance.interceptors.response.use(response => {
        // 针对响应数据进行通用处理,如处理404\500错误等等
        // 如;拆分服务器返回的真实数据
        return response.data
    })
    // 封装一个请求函数
    function request({ url, method = 'get', data = {}, params =
                      {}, reqController }) {
        // 获取取消信号对象
        const signal = reqController ? reqController.signal :undefined;
        // 返回请求
        return instance({
            url,
            method,
            data,
            params,
            signal
        })
    }
    
    export default request
    

    封装接口业务代码

    ​ 访问接口的函数,尽量不要直接写在 Vue 组件中,一般开发要求:单独封装获取接口数据的函数

    ​ 创建: src/api/goods.js (专门操作商品数据的 api接口函数

    /** 商品接口数据访问模块 */
    import axios from '../utils/request'
    
    // 获取接口数据的函数
    export const getGoodsPage = (page, size, controller) =>axios({
        url: '/goods/page',
        method: 'get',
        params: { page, size },
        controller
    })
    
    // 新增商品的接口函数
    // export const addGoods => () => {..}
    
    // 编辑商品的接口函数
    // export const editGoods = () => {...}
    
    // ...
    

    ​ 编辑 src/views/Goods.vue ,使用 api接口函数 获取数据

    
    
    

    ​ 本质上组件中获取数据的流程如图所示:

    • 这样的封装,将发送一个请求的功能代码,拆分到了多个文件:表面上看上去变得复杂了
    • 实际的情况:以后的开发和维护, Goods.vue 页面组件中几乎不需要代码修改;组件的健壮性得到提升

    Vue2.x全家桶学习笔记_第15张图片

    十二、专项补充

    12.1、脚手架项目初始化(v2.x)

    1. 确认自己vue命令版本
    • 查看vue命令版本(注意: 是大V)
    vue -V
    
    • 如果不是5.x版本, 可以执行命令更新
    npm i @vue/cli -g
    
    1. 采用自定义预设来创建项目(或者使用你之前保存过的预设)
    • 需要vue2版本

    • 需要babel

    • 需要vue-router

    • 需要vuex

    • 需要eslint

    • 需要less

    1. 如果你没有保存过以上的预设, 请按照下面步骤选择, 如果有则此步骤略过

    ​ vue create 项目名

    ​ 选择第3个 自定义预设

    Vue2.x全家桶学习笔记_第16张图片
    初始化 vue-cli 的核心步骤(用空格选中):

    3.1 Manually select features

    • (*) Babel

    • ( ) TypeScript

    • ( ) Progressive Web App (PWA) Support

    • (*) Router

    • (*) Vuex

    • (*) CSS Pre-processors

    • (*) Linter / Formatter

    • ( ) Unit Testing

    • ( ) E2E Testing

    3.2 Choose a version of Vue.js that you want to start the project with (Use arrow keys)

    • 2.x

    • 3.x

    3.3 Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)

    • n

    3.4 Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): (Use arrow keys)

    • Sass/SCSS (with dart-sass)

    • Sass/SCSS (with node-sass)

    • Less

    • Stylus

    3.5 Pick a linter / formatter config: (Use arrow keys)

    • ESLint + Airbnb config

    • ESLint + Standard config

    • ESLint + Prettier

    3.6 Pick additional lint features: (Press to select, to toggle all, to invert selection)

    • (*) Lint on save

    • ( ) Lint and fix on commit

    3.7 Where do you prefer placing config for Babel, ESLint, etc.? (Use arrow keys)

    • In dedicated config files

    • In package.json

    3.8 Save this as a preset for future projects? (y/N)

    • N

    4.梳理项目结构

    为项目开发做准备,把不需要的代码、文件删除掉

    4.1 重置 src/App.vue 组件中的代码

    
    
    
    
    
    

    4.2 重置 src/router/index.js 路由模块中的代码

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    Vue.use(VueRouter)
    
    const routes = []
    
    const router = new VueRouter({
      routes
    })
    
    export default router
    

    4.3 清空 src/components 目录和 src/views 目录。

    4.4 把 图片素材 目录下的 images 文件夹(项目中需要用到的图片),复制粘贴到 src/assets 目录下。

    4.5 并把global.less, 引入到main.js

    import '@/assets/global.less' // 全局初始化样式
    

    12.2、vue-router

    vue路由基本步骤分为七步

    1. 安装vue-router包

    npm i vue-router

    1. 在main.js入口文件引入VueRouter

    import VueRouter from ‘vue-router’

    1. 将VueRouter添加到Vue.use(Router)上

    Vue.use(VueRouter)

    • 添加到Vue.use()上后,会自动注册全局组件RouterLink和RouterView

    • RouterLink是定义连接的地址

      • 相当于替换a标签 to替代href
    • RouterView是使用路由组件

    1. 定义路由规则数组套对象(规则: 路径 对应相应的 组件 映射关系)
    let routesArr = [
      {
        // 一个对象就是一个规则
        // 路径地址
        path: '/find',
        // 组件的名字
        component: Find,
      },
      {
        // 一个对象就是一个规则
        path: '/my',
        component: My,
      },
      {
        // 一个对象就是一个规则
        path: '/part',
        component: Part,
      },
    ]
    
    1. 用规则数组,生成路由对象
    // 5. 用规则数组, 生成路由对象,到处路由表
    let routerObj = new VueRouter({
        routes: routesArr,
        // key名字固定配置项叫routes(等待传入规范数组)
    })
    
    1. 把路由对象添加到new Vue实例中(让整个项目拥有路由功能)
    new Vue({
        // 6. 把路由对象添加到new Vue实例中(让整个项目拥有路由功能)
        // 注意: vue内置了配置项(准备) key名固定 router
        router: routerObj,
     
        render: (h) => h(App),
    }).$mount('#app')
    
    1. 使用路由切换,切换的新组件寻找到挂载点,需要挂载到那个地方,使用标签进行挂载.使用标签替换挂载点vue文件的a标签,这两个标签是在第三步将VueRouter添加到Vue.use(Router)上全局注册的

    12.3、组件间通信

    1.props定义属性父传子:

    父组件在子组件使用的位置通过属性的方式传递数据,子组件通过prop接收数据

    
    
    
    
     
    
    

    2.this.$parent实现父传子

    this.$parent是用来获取组件的父组件节点,进而获取父组件的数据

    
     
    
    

    3.props定义自定义事件+$emit实现子传父

    解释:

    ​ 父组件在使用子组件的位置绑定一个自定义事件,传递给子组件,子组件绑定并触发事件,通过$emit方法触发子父组件中的自定义事件,并将数据传递给父组件,父组件的在自定义事件中将数据接受并保存。

    流程:

    ​ 父组件自定义事件,通过props向子组件传递一个方法————>子组件触发事件————>通过$emit方法触发父组件自定义事件,并传递数据————>父组件方法接受并保存数据

    
    
    
    
     
    
    

    注意:

    父子组件钩子函数执行顺序*(父先行动,子先干完)*:

    加载渲染过程:
    父组件 beforeCreate——>父组件 created——>父组件 beforeMount——>子组件 beforeCreate——>子组件 created——>子组件 beforeMount——>子组件 mounted——>父组件 mounted
    更新过程:

    父组件 beforeUpdate——>子组件 beforeUpdate——>子组件 updated——>父组件 updated
    销毁过程:

    父组件 beforeDestroy——>子组件 beforeDestroy——>子组件 destroyed——>父组件 destoryed

    5. $root根组件实现任意组件之间数据传递

    r o o t 是获取跟组件,之后配合 root是获取跟组件,之后配合 root是获取跟组件,之后配合on和$emit进行事件的监听和发送

    发送组件:

    ​ 通过this. r o o t . root. root.emit进行数据的发送:this. r o o t . root. root.emit(‘自定义事件名’,所传递的数据)

    
     
    
    

    接受组件:

    ​ 使用this. r o o t . root. root.on进行自定义事件监听,这里必须要和发送方的自定义事件一致:this. r o o t . root. root.on(‘自定义事件名’,监听变化后的事件回调)

    
     
    
    

    6.父组件充当中间件实现兄弟之间传递

    ​ 这个是方法1和方法3的组合方法,方法3先实现子组件向父组件传递数据并保存到父组件,之后再使用方法1将保存的数据传递给另一个子组件;(简单,不在展示)

    7.使用$ref获取子节点,实现子传父

    ​ 这个与 c h i l d r e n 实现基本一致,都是获取到子节点,然后取出需要的数据。不同的是 children实现基本一致,都是获取到子节点,然后取出需要的数据。不同的是 children实现基本一致,都是获取到子节点,然后取出需要的数据。不同的是children是获取该组件的所有子节点,而$ref是针对性的获取某个节点;

    ​ 在父组件中使用子组件的地方,对子组件进行ref打标识,然后在使用的时候通过$refs获取节点进行操作;

    
    
    
    

    8.全局事件总线(任意组件之间通信)

    这个方法与方法5$root跟组件实现任意组件之间数据传递一样,只是方法5是利用了该实例自带的根组件实例进行数据传递,而该方法则是在原型上添加一个属性,并将该属性指向该实例方法来实现;全局事件总线的原理是给 Vue 的原型对象上面添加一个属性。这样的话我所有组件的都可以访问到这个属性,然后可以通过这个属性来访问其他组件给这个属性上面绑定的一些方法,从而去传递数据。而且这个属性还可以去访问 Vue 实例对象上的方法。因为 Vue 组件构造函数的原型对象的原型对象是指向 Vue 的原型对象的

    main.js安装全局事件总线

    安装全局事件总线是在Vue的原型上,绑定一个属性(一般属性名命名为$bus,公交车,都可以进行数据搭载的意思),并将该属性值指向该实例对象;

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
      render: h => h(App),
      beforeCreate () {
        Vue.prototype.$bus = this // 安装全局事件总线
      }
    }).$mount('#app')
    

    发送方组件:

    发送发组件通过$emit方法进行数据发送;

    
     
    
    

    接受方组件:

    接收方组件是通过$on进行监听自定义事件,更新渲染页面;

    
    
    
    

    9.消息订阅与发布

    ​ vue和react使用消息订阅与发布进行数据传输是一样的,都是通过安装一些外在的插件库实现数据传递;消息的订阅与发布可以用订阅报纸那样比拟,订阅消息就相当于你订购了哪一家的报纸,等报社发布后,你就会第一时间收到这家的报纸;这种插件多的很,但是我推荐用pubsub.js这个插件,比较好用,用的也比较多;

    安装:

    npm i pubsub-js
    

    订阅消息:

    ​ 一般在订阅消息的时候会将该订阅保存到实例对象身上,方便后边取消订阅,减少空间浪费;

    
     
    
    

    发布消息:

    
     
    
    

    取消订阅:

    PubSub.unsubscribe(pid)
    

    10.vuex(跨组件间通信)

    官网:https://vuex.vuejs.org/zh/

    ​ Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。是vue官方进行维护的一个用于数据出本地的插件。它采用集中式存储管理应用的所有组件的状态,将所有的数据存放在一个公共的仓库中,并且这个仓库所有的组件都可以访问到,一都可以进行数据的获取和存储,并以相应的规则保证状态以一种可预测的方式发生变化。

    ​ 状态自管理应用包含三部分,以及对个部分进行的修改的示意图如下:

    Vue2.x全家桶学习笔记_第17张图片Vue2.x全家桶学习笔记_第18张图片

    安装(vue2.0——>vuex3,vue3.0——>vuex4):

    yarn add vuex
    npm i vuex
    
    state------>数据源:

    ​ state:数据的存储仓库,所有数据只能在这里进行获取,是用来存放数据的;

    初始化仓库:

    ​ 在定义的store仓库中,配置state属性,该属性是一个对象,里边是以键值对的形式存储数据的

    const store = new Vuex.Store({
        state: {
            `变量名: 初始值`
        }
    })
    

    使用仓库数据:

    方法一(直接使用):

    this.$store.state.变量名
    

    方法二(使用vuex自带的mapState函数):

    在需要使用该数据的组件中分别引入mapState函数,

    import   { mapState }  from  'vuex'
    

    在计算属性computed中使用 … 运算符展开数据

    ...mapState([变量名])
    
    mutations------>修改数据:

    mutations:是 vuex 同步并且唯一进行数据修改的地方,里边是进行数据修改的一系列方法;

    初始化mutations:

    在定义的store仓库中,添加mutations配置项,该配置项中存放的是一系列进行数据修改的同步方法:

    const store = new Vuex.Store({
        state: {
            `变量名: 初始值`
            }mutations:{
    		方法名(state,value){
    		// 在这里state是仓库,value是进行变化额度的值
    		state.变量名是需要修改的数据源,这里需要进进行的就是具体的修改方法
    		}
    	}
    })
    

    使用mutations:

    方法一(直接使用):

    this.$store.commit("mutations中定义的函数名",修改值)
    

    方法二(函数映射):

    从vuex中引入 mapMutations 内置函数,通过该函数映射需要的方法

    import { mapState, mapMutations } from'vuex';
    

    映射需要的方法:

    ...mapMutations(['方法名'])
    

    使用映射出的方法::

    this.方法名(修改的数据)
    
    actions------>异步修改:

    异步:就是下一步的代码不需要等待异步代码完全实现后才实现,可以先执行后边的再执行这里的;常见异步代码:ajax,定时器,计时器…

    actions:是进行异步任务修改state中的数据的,但是这里进行修改也需要去调用mutations中的方法进行修改,因为mutations是唯一进行数据修改的地方,里边是进行数据修改的一系列异步方法;

    初始化actions:

    在定义的store仓库中,添加actions配置项,该配置项中存放的是一系列进行数据修改的异步方法:

    const store = new Vuex.Store({
    	state: {
    		`变量名: 初始值`
    	}mutations:{
    		方法名(state,value){
    		// 在这里state是仓库,value是进行变化额度的值
    		state.变量名是需要修改的数据源,这里需要进进行的就是具体的修改方法
    		}
    	},
    	actions:{
    		// 这里是异步函数,axios请求,定时器等,异步函数里会包含同步修改方法,所以使用**actions一定需要使用mutations**
    	}
    })
    

    mutations与actions区别:

    两个都是进行state中数据修改的地方,mutations进行同步修改,里边的方法接受两个参数,state和修改变量的变化值;actios进行异步修改,里边的方法接受两个参数,store和修改变量的变化值;

    使用actions:

    方法一(直接使用,一般用于没有第二个参数的情况):

    this.$store.dispatch('异步方法名',【修改的值,选填】)
    

    方法二(映射使用,只能用于有第二个参数的情况):

    从vuex中引入 mapActions 内置函数,通过该函数映射需要的方法

    import { mapState, mapMutations,mapActions } from'vuex';
    

    映射需要的方法:

    ...mapActions(['异步方法名'])
    

    使用映射出的方法::

    this.异步方法名(修改的数据)
    // 注意:mutations和actions只能接收一个参数值(如果传递多个,请传递一个完整的对象)
    
    getters------>计算属性:

    当state中的数据不能够很好的满足页面使用时,getters是进行数据整合和修改的地方,相当于vue中的computed属性,把state中的数据作为参数,将state中的参数进行过滤、整合等操作,得到一个我们能方便利用的数据;

    初始化getters:

    在定义的store仓库中,添加getters配置项,该配置项中是存放进行数据整合的方法:

    const store = new Vuex.Store({
        state: {
            `变量名: 初始值`
        }mutations:{
        	方法名(state,value){
        	// 在这里state是仓库,value是进行变化额度的值
        	state.变量名是需要修改的数据源,这里需要进进行的就是具体的修改方法
    		}
        },
        actions:{
        	// 这里是异步函数,axios请求,定时器等,异步函数里会包含同步修改方法,所以使用**actions一定需要使用mutations**
        },
        getters:{
            计算属性名(state){
                return 
            }
        }
    })
    

    使用getter:

    方法一(直接使用):

    this.$store.getter.计算方法名
    

    方法二(映射):

    从vuex中引入 mapGetters 内置函数,通过该函数映射需要的方法

    import { mapState, mapMutations,mapActions,mapGetters } from'vuex';
    

    映射需要的方法:

    ...mapGetters(['计算方法名'])
    

    使用映射出的方法::

    this.计算方法名(修改的数据)
    

    注意:

    vue2.0中,geeter的返回值会像计算属性一样,根据他的依赖被缓存起来,且只有当它的依赖值被修改之后才被重新计算,但是从 Vue 3.0 开始,getter 的结果不再像计算属性一样会被缓存起来,这是一个已知的问题,将会在 3.1 版本中修复。

    modules------>store仓库模块化

    当我们存储的数据较多时,如果将所有的数据都存放在一个仓库中,就会显着臃肿,非常不方便,所以这里引入了模块化开发,即所有的数据存放在不同的仓库中;

    使用:

    在store中创建不同的仓库,每个仓库都拥有与跟仓库一样的属性,包括state,mutations,actions,getters,modules,唯一不同的是,在子仓库中的state是以函数形式存在的,跟仓库中是以对象形式存在的;

    定义模块:

    上方使用模块是为了定义子模块,这里的定义,意思是将跟仓库与子仓库建立连接;

    modules: {
    	**模块名: 模块对象**
    }
    
    分模块-影响state取值方式
    • 方式1: 组件内 - 直接使用
      原语法:
    this.$store.state.变量名
    

    分模块后语法:

    this.$store.state.变量名
    
    • 方式2: 组件内 - 映射使用
      原语法:
    ...mapState(['state里变量名'])
    

    ...mapState({'变量名': "state里变量名"}) // 给映射过来的state起别的名字
    分模块后语法:

    ...mapState({
    

    '变量名': state => state.模块名.变量名

    })
    

    当使用模块化开发时,如果多个模块存在着同一个方法时,在使用时就会引起冲突,所以这里引入了命名空间的概念

    • 防止多个模块之间, mutations/actions/getters的名字冲突

    开启命名空间

    在模块对象内设置namespaced: true

    const moduleShopCar = {
        namespaced: true,
        state () {},
        mutations: {},
        actions: {},
        getters: {},
        modules: {}
    }
    
    state使用方式修改
    • 直接使用无变化: this.$store.state.模块名.变量名
    • 辅助函数需要遵守格式
    ...mapState("模块名", ['state变量名'])
    
    mutations使用方式修改
    • 方式1: 组件内 - 直接使用

      • 原语法:
    this.$store.commit("mutations里的函数名", 具体值)
    
      • 开命名空间后语法:
    this.$store.commit("模块名/mutations里的函数名", 具体值)
    
    • 方式2: 组件内 - 映射使用

      • 原语法:
    ...mapMutations(['mutations里方法名'])
    
      • 开命名空间后语法:
    ...mapMutations("模块名", ['mutations里方法名'])
    
    actions使用方式修改
    • 方式1: 组件内 - 直接使用

      • 原语法:
    this.$store.dispatch("actions里的函数名", 具体值)
    
      • 开命名空间后语法:
    this.$store.dispatch("模块名/actions里的函数名", 具体值)
    
    • 方式2: 组件内 - 映射使用

      • 原语法:
    ...mapActions(['actions里方法名'])
    
      • 开命名空间后语法:
    ...mapActions("模块名", ['actions里方法名'])
    
    getters使用方式修改
    • 方式1: 组件内 - 直接使用

      • 原语法:
    this.$store.getters.计算属性名
    
      • 开命名空间后语法:
    this.$store.getters['模块名/计算属性名']
    
    • 方式2: 组件内 - 映射使用

      • 原语法:
    ...mapGetters(['getters里计算属性名'])
    
      • 开命名空间后语法:
    ...mapGetters("模块名", ['getters里计算属性名'])
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x4HQTQta-1677134127824)( image\1667378968491-c6ec2920-246e-44f4-ac48-17c91c4a99e2-16687394834183.png)]

    12.4、插槽

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LKgcE3ik-1677134127825)(image\image-20230109195833548.png)]

    你可能感兴趣的:(vue,vue.js,javascript,前端)