本教程系统讲解了 Vue2 的核心知识,包括 Vue 基础、Vue 脚手架、Vue Router 和 Vuex。通过丰富的代码示例和详细讲解,帮助前端开发者快速掌握 Vue2 开发技能。
文章目录
-
- [第一章:Vue 基础](#第一章:Vue 基础)
-
- [1.1 初识 Vue](#1.1 初识 Vue)
- [1.2 Vue 模板语法](#1.2 Vue 模板语法)
-
- [1. 插值语法](#1. 插值语法)
- [2. 指令语法](#2. 指令语法)
- 示例代码
- [1.3 数据绑定](#1.3 数据绑定)
- [1.4 el 与 data 的两种写法](#1.4 el 与 data 的两种写法)
-
- [el 的两种写法](#el 的两种写法)
- [data 的两种写法](#data 的两种写法)
- 示例代码
- [1.5 MVVM 模型](#1.5 MVVM 模型)
- [1.6 数据代理](#1.6 数据代理)
-
- 什么是数据代理
- [Vue 中的数据代理](#Vue 中的数据代理)
- [Object.defineProperty 回顾](#Object.defineProperty 回顾)
- [1.7 事件处理](#1.7 事件处理)
- [1.8 计算属性](#1.8 计算属性)
- [1.9 监视属性](#1.9 监视属性)
- [1.10 绑定样式](#1.10 绑定样式)
-
- [class 样式](#class 样式)
- [style 样式](#style 样式)
- [1.11 条件渲染](#1.11 条件渲染)
- [1.12 列表渲染](#1.12 列表渲染)
-
- [v-for 指令](#v-for 指令)
- [key 的原理](#key 的原理)
- 示例代码
- [Vue 监测数据的原理](#Vue 监测数据的原理)
- [1.13 收集表单数据](#1.13 收集表单数据)
- [1.14 过滤器](#1.14 过滤器)
- [1.15 内置指令](#1.15 内置指令)
-
- [v-text 指令](#v-text 指令)
- [v-html 指令](#v-html 指令)
- [v-cloak 指令](#v-cloak 指令)
- [v-once 指令](#v-once 指令)
- [v-pre 指令](#v-pre 指令)
- [1.16 自定义指令](#1.16 自定义指令)
- [1.17 生命周期](#1.17 生命周期)
- [1.18 组件基础](#1.18 组件基础)
-
- [Vue 中使用组件的三大步骤](#Vue 中使用组件的三大步骤)
- 定义组件的注意点
- 几个注意点
- 组件的嵌套
- VueComponent
- 一个重要的内置关系
- [第二章:Vue 脚手架](#第二章:Vue 脚手架)
-
- [2.1 Vue 脚手架结构分析](#2.1 Vue 脚手架结构分析)
-
- 项目结构
- [main.js 分析](#main.js 分析)
- [关于不同版本的 Vue](#关于不同版本的 Vue)
- [2.2 ref 属性](#2.2 ref 属性)
- [2.3 props 配置](#2.3 props 配置)
- [2.4 mixin 混入](#2.4 mixin 混入)
- [2.5 插件](#2.5 插件)
- [2.6 scoped 样式](#2.6 scoped 样式)
- [2.7 TodoList 案例](#2.7 TodoList 案例)
- [2.8 浏览器本地存储](#2.8 浏览器本地存储)
- [2.9 组件自定义事件](#2.9 组件自定义事件)
- [2.10 全局事件总线](#2.10 全局事件总线)
- [2.11 消息订阅与发布](#2.11 消息订阅与发布)
- [2.12 nextTick](#2.12 nextTick)
- [2.13 过渡与动画](#2.13 过渡与动画)
-
- [Vue 动画理解](#Vue 动画理解)
- [使用 `<transition>` 组件](#使用
<transition>组件) - 过渡的类名
- [第三章:Vue Router](#第三章:Vue Router)
-
- [3.1 基本使用](#3.1 基本使用)
- [3.2 嵌套路由](#3.2 嵌套路由)
- [3.3 路由的 query 参数](#3.3 路由的 query 参数)
- [3.4 命名路由](#3.4 命名路由)
- [3.5 路由的 params 参数](#3.5 路由的 params 参数)
- [3.6 路由的 props 配置](#3.6 路由的 props 配置)
- [3.7 router-link 的 replace 属性](#3.7 router-link 的 replace 属性)
- [3.8 编程式路由导航](#3.8 编程式路由导航)
- [3.9 缓存路由组件](#3.9 缓存路由组件)
- [3.10 两个新的生命周期钩子](#3.10 两个新的生命周期钩子)
- [3.11 全局路由守卫](#3.11 全局路由守卫)
- [3.12 独享路由守卫](#3.12 独享路由守卫)
- [3.13 组件内路由守卫](#3.13 组件内路由守卫)
- [3.14 history 模式与 hash 模式](#3.14 history 模式与 hash 模式)
-
- 对比
- [配置 history 模式](#配置 history 模式)
- 第四章:Vuex
-
- [4.1 Vuex 简介](#4.1 Vuex 简介)
-
- 概念
- [Vuex 工作原理](#Vuex 工作原理)
- [什么时候使用 Vuex](#什么时候使用 Vuex)
- [4.2 基本使用](#4.2 基本使用)
-
- [安装 vuex](#安装 vuex)
- [创建 store](#创建 store)
- [组件中使用 Vuex](#组件中使用 Vuex)
- [Vuex 工作流程](#Vuex 工作流程)
- [4.3 getters 的使用](#4.3 getters 的使用)
-
- [定义 getters](#定义 getters)
- [组件中使用 getters](#组件中使用 getters)
- [4.4 mapState 与 mapGetters](#4.4 mapState 与 mapGetters)
- [4.5 mapMutations 与 mapActions](#4.5 mapMutations 与 mapActions)
- [4.6 多组件共享数据](#4.6 多组件共享数据)
- [4.7 模块化编码](#4.7 模块化编码)
- 总结
第一章:Vue 基础
1.1 初识 Vue
概念
Vue 是一套用于构建用户界面的渐进式 JavaScript 框架。
初识 Vue 要点
- 想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象
- root 容器里的代码依然符合 HTML 规范,只不过混入了一些特殊的 Vue 语法
- root 容器里的代码被称为【Vue 模板】
- Vue 实例和容器是一一对应的
- 真实开发中只有一个 Vue 实例,并且会配合着组件一起使用
{``{xxx}}中的 xxx 要写 JS 表达式,且 xxx 可以自动读取到 data 中的所有属性- 一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新
JS 表达式与 JS 代码的区别
javascript
// JS 表达式:会产生一个值,可以放在任何一个需要值的地方
a // 变量
a + b // 运算
demo(1) // 函数调用
x === y ? 'a' : 'b' // 三元表达式
// JS 代码(语句)
if(){} // 条件语句
for(){} // 循环语句
示例代码
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>初识Vue</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="demo">
<h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
// 创建Vue实例
new Vue({
el:'#demo', // el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
data:{ // data中用于存储数据,数据供el所指定的容器去使用
name:'前端开发教程',
address:'北京'
}
})
</script>
</body>
</html>
1.2 Vue 模板语法
Vue 模板语法有两大类:
1. 插值语法
- 功能:用于解析标签体内容
- 写法 :
{``{xxx}},xxx 是 JS 表达式,且可以直接读取到 data 中的所有属性
2. 指令语法
- 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件等)
- 举例 :
v-bind:href="xxx"或简写为:href="xxx" - 备注 :Vue 中有很多的指令,且形式都是
v-???
示例代码
html
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
<hr>
<h1>指令语法</h1>
<a v-bind:href="school.url.toUpperCase()">点我去学习1</a>
<a :href="school.url">点我去学习2</a>
</div>
<script>
new Vue({
el:'#root',
data:{
name:'jack',
school:{
name:'前端开发教程',
url:'在线学习平台',
}
}
})
</script>
1.3 数据绑定
Vue 中有两种数据绑定的方式:
| 绑定方式 | 说明 | 特点 |
|---|---|---|
| 单向绑定 (v-bind) | 数据只能从 data 流向页面 | :value="name" |
| 双向绑定 (v-model) | 数据不仅能从 data 流向页面,还可以从页面流向 data | v-model="name" |
备注:
- 双向绑定一般都应用在表单类元素上(如:input、select 等)
v-model:value可以简写为v-model,因为 v-model 默认收集的就是 value 值
示例代码
html
<div id="root">
<!-- 简写 -->
单向数据绑定:<input type="text" :value="name"><br>
双向数据绑定:<input type="text" v-model="name"><br>
</div>
<script>
new Vue({
el:'#root',
data:{
name:'前端开发教程'
}
})
</script>
1.4 el 与 data 的两种写法
el 的两种写法
- new Vue 时配置 el 属性
- 先创建 Vue 实例,随后再通过
vm.$mount('#root')指定 el 的值
data 的两种写法
- 对象式:
data: { name: 'xxx' } - 函数式:
data() { return { name: 'xxx' } }
重要原则:由 Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了。
示例代码
javascript
// el 的两种写法
const v = new Vue({
// el:'#root', // 第一种写法
data:{
name:'前端开发教程'
}
})
v.$mount('#root') // 第二种写法
// data 的两种写法
new Vue({
el:'#root',
// data的第一种写法:对象式
/* data:{
name:'前端开发教程'
} */
// data的第二种写法:函数式
data(){
console.log('@@@',this) // 此处的this是Vue实例对象
return{
name:'前端开发教程'
}
}
})
1.5 MVVM 模型
| 缩写 | 全称 | 说明 |
|---|---|---|
| M | Model | data 中的数据 |
| V | View | 模板代码 |
| VM | ViewModel | Vue 实例 |
观察发现:
- data 中所有的属性,最后都出现在了 vm 身上
- vm 身上所有的属性及 Vue 原型上所有属性,在 Vue 模板中都可以直接使用
1.6 数据代理
什么是数据代理
通过一个对象代理对另一个对象中属性的操作(读/写)。
Vue 中的数据代理
- 通过 vm 对象来代理 data 对象中属性的操作(读/写)
- Vue 中数据代理的好处:更加方便的操作 data 中的数据
- 基本原理 :
- 通过
Object.defineProperty()把 data 对象中所有属性添加到 vm 上 - 为每一个添加到 vm 上的属性,都指定一个 getter/setter
- 在 getter/setter 内部去操作(读/写)data 中对应的属性
- 通过
Object.defineProperty 回顾
javascript
let number = 18
let person = {
name:'张三',
sex:'男',
}
Object.defineProperty(person,'age',{
// 当有人读取person的age属性时,get函数(getter)就会被调用
get(){
console.log('有人读取age属性了')
return number
},
// 当有人修改person的age属性时,set函数(setter)就会被调用
set(value){
console.log('有人修改了age属性,且值是',value)
number = value
}
})
1.7 事件处理
事件的基本使用
- 使用
v-on:xxx或@xxx绑定事件,其中 xxx 是事件名 - 事件的回调需要配置在 methods 对象中,最终会在 vm 上
- methods 中配置的函数,不要用箭头函数!否则 this 就不是 vm 了
- methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或组件实例对象
@click="demo"和@click="demo($event)"效果一致,但后者可以传参
示例代码
html
<div id="root">
<button @click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
</div>
<script>
new Vue({
el:'#root',
data:{},
methods:{
showInfo1(event){
alert('同学你好!')
},
showInfo2(event,number){
console.log(event,number)
alert('同学你好!!')
}
}
})
</script>
事件修饰符
Vue 中的事件修饰符:
| 修饰符 | 说明 |
|---|---|
.prevent |
阻止默认事件(常用) |
.stop |
阻止事件冒泡(常用) |
.once |
事件只触发一次(常用) |
.capture |
使用事件的捕获模式 |
.self |
只有 event.target 是当前操作的元素时才触发事件 |
.passive |
事件的默认行为立即执行,无需等待事件回调执行完毕 |
修饰符可以连续写 :@click.prevent.stop="showInfo"
键盘事件
Vue 中常用的按键别名:
| 别名 | 对应键 |
|---|---|
.enter |
回车 |
.delete |
删除/退格 |
.esc |
退出 |
.space |
空格 |
.tab |
换行(需配合 keydown) |
.up/.down/.left/.right |
方向键 |
未提供别名的按键 :可以使用按键原始的 key 值,但要转为 kebab-case(如 @keyup.page-down)
系统修饰键:ctrl、alt、shift、meta
- 配合 keyup:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
- 配合 keydown:正常触发事件
自定义按键别名 :Vue.config.keyCodes.huiche = 13
1.8 计算属性
计算属性介绍
- 定义:要用的属性不存在,要通过已有属性计算得来
- 原理 :底层借助了
Object.defineProperty方法提供的 getter 和 setter - get 函数什么时候执行 :
- 初次读取时会执行一次
- 当依赖的数据发生改变时会被再次调用
- 优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便
- 备注 :
- 计算属性最终会出现在 vm 上,直接读取使用即可
- 如果计算属性要被修改,必须写 set 函数去响应修改
示例代码
html
<div id="root">
姓:<input type="text" v-model="firstName"> <br><br>
名:<input type="text" v-model="lastName"> <br><br>
全名:<span>{{fullName}}</span> <br><br>
</div>
<script>
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
},
computed:{
fullName:{
// get有什么作用?当有人读取fullName时,get就会被调用
// get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时
get(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
},
// set什么时候调用? 当fullName被修改时
set(value){
console.log('set',value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
})
</script>
计算属性简写
当只有 getter 而没有 setter 时,可以简写:
javascript
computed:{
// 简写形式(只有getter时使用)
fullName(){
console.log('get被调用了')
return this.firstName + '-' + this.lastName
}
}
1.9 监视属性
watch 监视属性
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视
- 监视的两种写法:
- new Vue 时传入 watch 配置
- 通过
vm.$watch监视
示例代码
javascript
const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods:{
changeWeather(){
this.isHot = !this.isHot
}
},
watch:{
isHot:{
immediate:true, // 初始化时让handler调用一下
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
}
})
// 第二种写法
vm.$watch('isHot',{
immediate:true,
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
深度监视
- Vue 中的 watch 默认不监测对象内部值的改变(一层)
- 配置
deep: true可以监测对象内部值改变(多层)
javascript
watch:{
// 监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers改变了')
}
}
}
computed 和 watch 的区别
- computed 能完成的功能,watch 都可以完成
- watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作
两个重要的小原则:
- 所被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或组件实例对象
- 所有不被 Vue 所管理的函数(定时器的回调函数、ajax 的回调函数等、Promise 的回调函数),最好写成箭头函数,这样 this 的指向才是 vm 或组件实例对象
1.10 绑定样式
class 样式
| 写法 | 说明 | 适用场景 |
|---|---|---|
:class="xxx" |
字符串写法 | 类名不确定,要动态获取 |
:class="[xxx, yyy]" |
数组写法 | 样式个数不确定,名字也不确定 |
:class="{xxx: true}" |
对象写法 | 样式个数确定,名字也确定,但不确定用不用 |
style 样式
html
<!-- 对象写法 -->
<div :style="{fontSize: xxx}"></div>
<!-- 数组写法 -->
<div :style="[styleObj1, styleObj2]"></div>
1.11 条件渲染
v-if
html
<!-- 写法 -->
<div v-if="condition"></div>
<div v-else-if="condition2"></div>
<div v-else></div>
- 适用于:切换频率较低的场景
- 特点:不展示的 DOM 元素直接被移除
- 注意:v-if 可以和 v-else-if、v-else 一起使用,但要求结构不能被"打断"
v-show
html
<div v-show="condition"></div>
- 适用于:切换频率较高的场景
- 特点:不展示的 DOM 元素未被移除,仅仅是使用样式隐藏掉
备注
使用 v-if 的时,元素可能无法获取到,而使用 v-show 一定可以获取到。
1.12 列表渲染
v-for 指令
html
<!-- 语法 -->
v-for="(item, index) in xxx" :key="yyy"
可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
key 的原理
-
虚拟 DOM 中 key 的作用:
- key 是虚拟 DOM 对象的标识
- 当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】
- 随后进行【新虚拟 DOM】与【旧虚拟 DOM】的差异比较
-
对比规则:
- 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
- 若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
- 若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
- 旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key:创建新的真实 DOM,随后渲染到页面
- 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
-
用 index 作为 key 可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新
- 如果结构中还包含输入类的 DOM,会产生错误 DOM 更新
-
开发中如何选择 key:
- 最好使用每条数据的唯一标识作为 key(id、手机号、身份证号等)
- 如果不存在对数据的逆序操作,仅用于渲染列表,使用 index 作为 key 是没有问题的
示例代码
html
<div id="root">
<!-- 遍历数组 -->
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<ul>
<li v-for="(value,k) of car" :key="k">
{{k}}-{{value}}
</li>
</ul>
</div>
<script>
new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
],
car:{
name:'奥迪A8',
price:'70万',
color:'黑色'
}
}
})
</script>
Vue 监测数据的原理
对象监测:
- Vue 会监视 data 中所有层次的数据
- 通过 setter 实现监视,且要在 new Vue 时就传入要监测的数据
- 对象中后追加的属性,Vue 默认不做响应式处理
- 如需给后添加的属性做响应式,请使用
Vue.set(target, key, value)或vm.$set(target, key, value)
数组监测:
- 通过包裹数组更新元素的方法实现
- 在 Vue 修改数组中的某个元素一定要用如下方法:
- 使用 API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
- Vue.set() 或 vm.$set()
特别注意 :Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象添加属性!
1.13 收集表单数据
| input 类型 | v-model 收集的值 |
|---|---|
<input type="text"/> |
value 值 |
<input type="radio"/> |
value 值 |
<input type="checkbox"/> |
没有配置 value 属性:checked(布尔值) 配置了 value 属性:value 组成的数组 |
v-model 的三个修饰符:
.lazy:失去焦点再收集数据.number:输入字符串转为有效的数字.trim:输入首尾空格过滤
1.14 过滤器
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
语法:
- 注册过滤器:
Vue.filter(name, callback)或new Vue({ filters:{} }) - 使用过滤器:
{``{ xxx | 过滤器名}}或v-bind:属性 = "xxx | 过滤器名"
备注:
- 过滤器也可以接收额外参数、多个过滤器也可以串联
- 并没有改变原本的数据,是产生新的对应的数据
示例代码
html
<div id="root">
<h3>现在是:{{ time | timeFormater }}</h3>
<h3>现在是:{{ time | timeFormater('YYYY_MM_DD') | mySlice }}</h3>
</div>
<script>
// 全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})
new Vue({
el:'#root',
data:{
time:1621561377603,
},
// 局部过滤器
filters:{
timeFormater(value, str='YYYY年MM月DD日 HH:mm:ss'){
return dayjs(value).format(str)
}
}
})
</script>
1.15 内置指令
| 指令 | 说明 |
|---|---|
v-bind |
单向绑定解析表达式,可简写为 :xxx |
v-model |
双向数据绑定 |
v-for |
遍历数组/对象/字符串 |
v-on |
绑定事件监听,可简写为 @ |
v-if |
条件渲染(动态控制节点是否存存在) |
v-else |
条件渲染(动态控制节点是否存存在) |
v-show |
条件渲染(动态控制节点是否展示) |
v-text 指令
- 作用:向其所在的节点中渲染文本内容
- 与插值语法的区别:v-text 会替换掉节点中的内容,
{``{xx}}则不会
v-html 指令
- 作用:向指定节点中渲染包含 html 结构的内容
- 与插值语法的区别:
- v-html 会替换掉节点中所有的内容,
{``{xx}}则不会 - v-html 可以识别 html 结构
- v-html 会替换掉节点中所有的内容,
严重注意:v-html 有安全性问题!在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。一定要在可信的内容上使用 v-html,永不要用在用户提交的内容上!
v-cloak 指令
- 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性
- 使用 css 配合 v-cloak 可以解决网速慢时页面展示出
{``{xxx}}的问题
html
<style>
[v-cloak]{
display:none;
}
</style>
<h2 v-cloak>{{name}}</h2>
v-once 指令
- v-once 所在节点在初次动态渲染后,就视为静态内容了
- 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能
v-pre 指令
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
1.16 自定义指令
定义语法
局部指令:
javascript
new Vue({
directives:{指令名:配置对象} 或 directives{指令名:回调函数}
})
全局指令:
javascript
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
配置对象中常用的三个回调
| 回调 | 说明 |
|---|---|
bind |
指令与元素成功绑定时调用 |
inserted |
指令所在元素被插入页面时调用 |
update |
指令所在模板被重新解析时调用 |
备注
- 指令定义时不加
v-,但使用时要加v- - 指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 命名
示例代码
html
<div id="root">
<h2>当前的n值是:<span v-text="n"></span> </h2>
<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
<button @click="n++">点我n+1</button>
<hr>
<input type="text" v-fbind:value="n">
</div>
<script>
new Vue({
el:'#root',
data:{
n:1
},
directives:{
big(element, binding){
console.log('big',this) // 注意此处的this是window
element.innerText = binding.value * 10
},
fbind:{
bind(element, binding){
element.value = binding.value
},
inserted(element, binding){
element.focus()
},
update(element, binding){
element.value = binding.value
}
}
}
})
</script>
1.17 生命周期
生命周期简介
- 又名:生命周期回调函数、生命周期函数、生命周期钩子
- 是什么:Vue 在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的 this 指向是 vm 或组件实例对象
生命周期流程图
下图展示了 Vue 实例从创建到销毁的完整生命周期过程,包括各个生命周期钩子函数的调用时机:

创建阶段:
beforeCreate() - 数据代理和响应式创建之前
created() - 数据代理和响应式创建完成
beforeMount() - 模板解析之前
mounted() - 模板解析完成(挂载完毕)
更新阶段:
beforeUpdate() - 数据更新之前
updated() - 数据更新完成
销毁阶段:
beforeDestroy() - 销毁之前
destroyed() - 销毁完成
常用的生命周期钩子
| 钩子 | 说明 |
|---|---|
mounted |
发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】 |
beforeDestroy |
清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】 |
关于销毁 Vue 实例
- 销毁后借助 Vue 开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生 DOM 事件依然有效
- 一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了
1.18 组件基础
Vue 中使用组件的三大步骤
- 定义组件 (创建组件):使用
Vue.extend(options)创建 - 注册组件 :
- 局部注册:
new Vue({ components:{组件名:组件} }) - 全局注册:
Vue.component('组件名',组件)
- 局部注册:
- 使用组件 :写组件标签
<school></school>
定义组件的注意点
- el 不要写,为什么?------ 最终所有的组件都要经过一个 vm 的管理,由 vm 中的 el 决定服务哪个容器
- data 必须写成函数,为什么?------ 避免组件被复用时,数据存在引用关系
- 使用 template 可以配置组件结构
几个注意点
关于组件名:
- 一个单词组成:
school或School - 多个单词组成:
my-school或MySchool(需 Vue 脚手架支持) - 备注:组件名尽可能回避 HTML 中已有的元素名称
- 可以使用 name 配置项指定组件在开发者工具中呈现的名字
关于组件标签:
<school></school>或<school/>(不使用脚手架时,后者会导致后续组件不能渲染)
一个简写方式 :Vue.extend(options) 可简写为 options
组件的嵌套
javascript
// 定义student组件
const student = Vue.extend({
name:'student',
template:`<div><h2>学生姓名:{{name}}</h2></div>`,
data(){
return { name:'张三', age:18 }
}
})
// 定义school组件
const school = Vue.extend({
name:'school',
template:`<div><h2>学校名称:{{name}}</h2><student/></div>`,
data(){
return { name:'前端开发教程', address:'北京' }
},
components:{ student }
})
VueComponent
- school 组件本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,是 Vue.extend 生成的
- 我们只需要写
<school/>或<school></school>,Vue 解析时会帮我们创建 school 组件的实例对象 - 特别注意 :每次调用
Vue.extend,返回的都是一个全新的 VueComponent - VueComponent 的实例对象,以后简称 vc(组件实例对象)
- Vue 的实例对象,以后简称 vm
一个重要的内置关系
javascript
VueComponent.prototype.__proto__ === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法。
第二章:Vue 脚手架
2.1 Vue 脚手架结构分析
项目结构
vue_test/
├── public/
│ ├── index.html
│ └── favicon.ico
├── src/
│ ├── assets/
│ ├── components/
│ │ ├── School.vue
│ │ └── Student.vue
│ ├── App.vue
│ └── main.js
├── babel.config.js
├── package.json
└── vue.config.js
main.js 分析
javascript
// 该文件是整个项目的入口文件
import Vue from 'vue'
import App from './App.vue'
// 关闭vue的生产提示
Vue.config.productionTip = false
// 创建Vue实例对象---vm
new Vue({
el:'#app',
// render函数完成了这个功能:将App组件放入容器中
render: h => h(App),
})
关于不同版本的 Vue
| 版本 | 说明 |
|---|---|
| vue.js | 完整版 Vue,包含:核心功能 + 模板解析器 |
| vue.runtime.xxx.js | 运行版 Vue,只包含:核心功能;没有模板解析器 |
因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容。
2.2 ref 属性
- 被用来给元素或子组件注册引用信息(替代 id)
- 应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上获取的是组件实例对象
- 使用方式:
- 打标识:
<h1 ref="xxx">...</h1>或<School ref="xxx"/> - 获取:
this.$refs.xxx
- 打标识:
html
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch"/>
</div>
</template>
<script>
export default {
name:'App',
data() {
return { msg:'欢迎学习Vue!' }
},
methods: {
showDOM(){
console.log(this.$refs.title) // 真实DOM元素
console.log(this.$refs.btn) // 真实DOM元素
console.log(this.$refs.sch) // School组件的实例对象
}
},
}
</script>
2.3 props 配置
功能:让组件接收外部传过来的数据
- 传递数据:
<Student name="李四" sex="女" :age="18"/> - 接收数据:
javascript
export default {
name:'Student',
// 简单声明接收
props:['name','age','sex'],
// 接收的同时对数据进行类型限制
props:{
name:String,
age:Number,
sex:String
},
// 接收的同时对数据进行类型限制+默认值的指定+必要性的限制
props:{
name:{
type:String, // name的类型是字符串
required:true, // name是必要的
},
age:{
type:Number,
default:99 // 默认值
},
sex:{
type:String,
required:true
}
}
}
注意:props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告。若业务需求确实需要修改,那么请复制 props 的内容到 data 中,然后去修改 data 中的数据。
2.4 mixin 混入
功能:可以把多个组件共用的配置提取成一个混入对象
混入的使用方式
第一步:定义混入
javascript
export const hunhe = {
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
},
}
第二步:使用混入
javascript
// 局部混入
export default {
name:'School',
data() {
return { name:'前端开发教程', address:'北京', x:666 }
},
mixins:[hunhe, hunhe2],
}
// 全局混入
Vue.mixin(xxx)
混入的合并规则:
- 数据对象在内部进行递归合并,并在发生冲突时以组件数据优先
- 同名钩子函数将合并为一个数组,都会被调用,且混入对象的钩子先调用
- 值为对象的选项(如 methods、components 等)将合并为同一个对象,键名冲突时取组件对象的键值对
2.5 插件
功能:用于增强 Vue
本质:包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
javascript
export default {
install(Vue, x, y, z){
console.log(x, y, z)
// 全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0, 4)
})
// 定义全局指令
Vue.directive('fbind', {
bind(element, binding){
element.value = binding.value
},
inserted(element, binding){
element.focus()
},
update(element, binding){
element.value = binding.value
}
})
// 定义混入
Vue.mixin({
data() {
return { x:100, y:200 }
},
})
// 给Vue原型上添加一个方法
Vue.prototype.hello = () => { alert('你好啊') }
}
}
使用插件:
javascript
import Vue from 'vue'
import App from './App.vue'
import plugins from './plugins'
Vue.use(plugins)
new Vue({
el:'#app',
render: h => h(App),
})
2.6 scoped 样式
作用:让样式在局部生效,防止冲突
写法 :<style scoped>
html
<template>
<div class="title">你好啊</div>
</template>
<script>
export default { name:'App' }
</script>
<style scoped>
.title{
color: red;
}
</style>
备注:
- scoped 不影响使用 class 或 id 选择器
- scoped 会在元素的标签上添加一个
data-v-xxxxx属性 - 如果组件中还有引入的其他组件,后代选择器会作用于子组件的根元素
2.7 TodoList 案例
TodoList 是一个经典的任务管理案例,展示了 Vue 开发中的核心概念。
案例结构
TodoList/
├── App.vue # 应用根组件
└── components/
├── MyHeader.vue # 添加任务头部
├── MyList.vue # 任务列表
├── MyItem.vue # 单个任务项
└── MyFooter.vue # 任务统计底部
App.vue(数据管理)
html
<template>
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo"/>
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter.vue'
export default {
name:'App',
components:{MyHeader, MyList, MyFooter},
data() {
return {
todos:[
{id:'001', title:'抽烟', done:true},
{id:'002', title:'喝酒', done:false},
{id:'003', title:'开车', done:true}
]
}
},
methods: {
// 添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
// 勾选or取消勾选一个todo
checkTodo(id){
this.todos.forEach((todo) => {
if(todo.id === id) todo.done = !todo.done
})
},
// 删除一个todo
deleteTodo(id){
this.todos = this.todos.filter(todo => todo.id !== id)
},
// 全选or取消全选
checkAllTodo(done){
this.todos.forEach((todo) => {
todo.done = done
})
},
// 清除所有已经完成的todo
clearAllTodo(){
this.todos = this.todos.filter((todo) => {
return !todo.done
})
}
}
}
</script>
MyHeader.vue
html
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"
v-model="title" @keyup.enter="add"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name:'MyHeader',
props:['addTodo'],
data() {
return { title:'' }
},
methods: {
add(){
if(!this.title.trim()) return alert('输入不能为空')
const todoObj = {id:nanoid(), title:this.title, done:false}
this.addTodo(todoObj)
this.title = ''
}
},
}
</script>
MyFooter.vue
html
<template>
<div class="todo-footer" v-show="total">
<input type="checkbox" v-model="isAll"/>
<span>
<span>已完成{{doneTotal}}</span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name:'MyFooter',
props:['todos', 'checkAllTodo', 'clearAllTodo'],
computed: {
total(){
return this.todos.length
},
doneTotal(){
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0)
},
isAll:{
get(){
return this.doneTotal === this.total && this.total > 0
},
set(value){
this.checkAllTodo(value)
}
}
},
}
</script>
2.8 浏览器本地存储
localStorage
- 数据永久存储,除非手动删除
- 存储容量约为 5MB
- 以键值对的形式存储
javascript
// 保存数据
localStorage.setItem('msg', 'hello!!!')
localStorage.setItem('person', JSON.stringify(p))
// 读取数据
localStorage.getItem('msg')
JSON.parse(localStorage.getItem('person'))
// 删除数据
localStorage.removeItem('msg')
// 清空数据
localStorage.clear()
sessionStorage
- 数据存储在会话级别,关闭浏览器或标签页后数据消失
- 其他特性与 localStorage 相同
2.9 组件自定义事件
一种组件间通信的方式,适用于:子组件 ===> 父组件
绑定自定义事件
方式一:通过父组件给子组件绑定自定义事件
html
<!-- 第一种写法:使用@或v-on -->
<Student @studentEvent="getStudentName" @demo="m1"/>
<!-- 第二种写法:使用ref -->
<Student ref="student" @click.native="show"/>
方式二:通过代码绑定
javascript
mounted() {
this.$refs.student.$on('studentEvent', this.getStudentName)
// this.$refs.student.$once('studentEvent', this.getStudentName) // 一次性
}
触发自定义事件
html
<template>
<div class="student">
<button @click="sendStudentName">把学生名给父组件</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return { name:'张三', sex:'男' }
},
methods: {
sendStudentName(){
// 触发 Student 实例上绑定的 studentEvent 事件
this.$emit('前端开发教程', this.name, 1, 2, 3)
}
},
}
</script>
解绑自定义事件
javascript
methods: {
unbind(){
this.$off('studentEvent') // 解绑一个自定义事件
// this.$off(['前端开发教程', 'demo']) // 解绑多个
// this.$off() // 解绑所有自定义事件
}
}
2.10 全局事件总线
一种组件间通信的方式,适用于任意组件间通信
安装全局事件总线
javascript
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this // 安装全局事件总线
},
})
使用事件总线
接收数据:A 组件想接收数据,则在 A 组件中给 $bus 绑定自定义事件,事件的回调留在 A 组件自身
javascript
mounted() {
this.$bus.$on('hello', (data) => {
console.log('我是School组件,收到了数据', data)
})
},
beforeDestroy() {
this.$bus.$off('hello')
},
提供数据:
javascript
methods: {
sendStudentName(){
this.$bus.$emit('hello', this.name)
}
}
2.11 消息订阅与发布
一种组件间通信的方式,适用于任意组件间通信(与全局事件总线类似)
使用步骤
- 安装 pubsub:
npm i pubsub-js - 引入:
import pubsub from 'pubsub-js' - 订阅消息:
javascript
mounted() {
this.pubId = pubsub.subscribe('hello', (msgName, data) => {
console.log('收到消息了', msgName, data)
})
},
beforeDestroy() {
pubsub.unsubscribe(this.pubId)
},
- 发布消息:
javascript
methods: {
sendStudentName(){
pubsub.publish('hello', 666)
}
}
2.12 nextTick
this.$nextTick(回调函数)- 作用:在下一次 DOM 更新结束后执行其指定的回调
- 什么时候用:当改变数据后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick 所指定的回调函数中执行
javascript
methods: {
showInput(){
this.isEdit = true
this.$nextTick(function(){
this.$refs.inputTitle.focus()
})
}
}
2.13 过渡与动画
Vue 动画理解
- 操作元素的 CSS 类名
- 元素显示/隐藏时触发
使用 <transition> 组件
html
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<style scoped>
h1{
background-color: orange;
}
/* 入场动画 */
.hello-enter-active{
animation: fade 0.5s linear;
}
/* 离场动画 */
.hello-leave-active{
animation: fade 0.5s linear reverse;
}
@keyframes fade {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>
过渡的类名
| 进入 | 离开 | 说明 |
|---|---|---|
v-enter |
v-leave |
元素进入的起始状态 |
v-enter-active |
v-leave-active |
元素进入的动画状态 |
v-enter-to |
v-leave-to |
元素进入的终止状态 |
如果使用 <transition name="hello">,则类名变为 hello-enter 等。
第三章:Vue Router
3.1 基本使用
路由概念
- 路由:就是一组 key-value 的对应关系
- 多个路由需经过路由器的管理
基本使用步骤
第一步:安装 vue-router
bash
npm i vue-router
第二步:引入并应用插件
javascript
import VueRouter from 'vue-router'
Vue.use(VueRouter)
第三步:创建路由器
javascript
// router/index.js
import VueRouter from 'vue-router'
import About from '../components/About'
import Home from '../components/Home'
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
}
]
})
第四步:在 Vue 实例中配置 router
javascript
new Vue({
el:'#app',
render: h => h(App),
router:router
})
第五步:编写路由链接
html
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
<!-- 指定组件的呈现位置 -->
<router-view></router-view>
3.2 嵌套路由
多级路由 :在父路由的 children 配置中添加子路由
javascript
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[
{
path:'news', // 不要加斜杠
component:News,
},
{
path:'message',
component:Message,
}
]
}
]
})
使用:在子路由组件中继续使用 <router-link> 和 <router-view>
3.3 路由的 query 参数
传递参数:
html
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
接收参数:
html
<li>消息编号:{{$route.query.id}}</li>
<li>消息标题:{{$route.query.title}}</li>
3.4 命名路由
作用:可以简化路由的跳转
javascript
routes:[
{
name:'guanyu', // 给路由命名
path:'/about',
component:About
}
]
使用:
html
<!-- 简化前 -->
<router-link to="/about">关于</router-link>
<!-- 简化后 -->
<router-link :to="{name:'guanyu'}">关于</router-link>
<!-- 传递query参数 -->
<router-link :to="{
name:'xiangqing',
query:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
3.5 路由的 params 参数
配置路由:
javascript
routes:[
{
path:'/home',
component:Home,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', // 使用占位符声明接收 params 参数
component:Detail,
}
]
}
]
传递参数:
html
<!-- 字符串写法 -->
<router-link :to="`/home/message/detail/${m.id}/${m.title}`">
{{m.title}}
</router-link>
<!-- 对象写法 -->
<router-link :to="{
name:'xiangqing',
params:{
id:m.id,
title:m.title
}
}">
{{m.title}}
</router-link>
接收参数:
html
<li>消息编号:{{$route.params.id}}</li>
<li>消息标题:{{$route.params.title}}</li>
3.6 路由的 props 配置
作用:让路由组件更方便的收到参数
javascript
routes:[
{
name:'xiangqing',
path:'detail',
component:Detail,
// props的第一种写法,值为对象
// 该对象中的所有key-value都会以props的形式传给Detail组件
// props:{a:1, b:'hello'}
// props的第二种写法,值为布尔值
// 若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件
// props:true
// props的第三种写法,值为函数
props($route){
return {
id:$route.query.id,
title:$route.query.title,
a:1,
b:'hello'
}
}
}
]
接收参数的组件:
javascript
export default {
name:'Detail',
props:['id', 'title'],
}
3.7 router-link 的 replace 属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push(追加历史记录)和replace(替换当前记录) - 默认是 push 模式
- 启用 replace 模式:
<router-link replace to="/about">关于</router-link>
3.8 编程式路由导航
作用 :不借助 <router-link> 实现路由跳转,让路由跳转更加灵活
javascript
methods: {
pushShow(m){
this.$router.push({
name:'xiangqing',
query:{
id:m.id,
title:m.title
}
})
},
replaceShow(m){
this.$router.replace({
name:'xiangqing',
query:{
id:m.id,
title:m.title
}
})
},
back(){
this.$router.back() // 后退
},
forward(){
this.$router.forward() // 前进
}
}
3.9 缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁
html
<!-- 缓存一个路由组件 -->
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
<!-- 缓存多个路由组件 -->
<keep-alive :include="['News', 'Message']">
<router-view></router-view>
</keep-alive>
3.10 两个新的生命周期钩子
| 钩子 | 说明 |
|---|---|
activated |
路由组件被激活时触发 |
deactivated |
路由组件失活时触发 |
javascript
export default {
name:'News',
activated(){
console.log('News组件被激活了')
},
deactivated(){
console.log('News组件失活了')
}
}
3.11 全局路由守卫
全局前置路由守卫
初始化时被调用、每次路由切换之前被调用
javascript
router.beforeEach((to, from, next) => {
console.log('前置路由守卫', to, from)
if(to.meta.isAuth){ // 判断是否需要鉴权
if(localStorage.getItem('school') === '前端开发教程'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
})
全局后置路由守卫
初始化时被调用、每次路由切换之后被调用
javascript
router.afterEach((to, from) => {
console.log('后置路由守卫', to, from)
document.title = to.meta.title || '在线教育平台'
})
3.12 独享路由守卫
只有一个路由配置,不需要放行
javascript
{
name:'xinwen',
path:'news',
component:News,
meta:{ isAuth:true, title:'新闻' },
beforeEnter: (to, from, next) => {
console.log('前置路由守卫', to, from)
if(to.meta.isAuth){
if(localStorage.getItem('school') === '前端开发教程'){
next()
}else{
alert('学校名不对,无权限查看!')
}
}else{
next()
}
}
}
3.13 组件内路由守卫
javascript
export default {
name:'About',
// 路由进入之前调用
beforeRouteEnter(to, from, next){
// ...
next()
},
// 路由离开之前调用
beforeRouteLeave(to, from, next){
// ...
next()
}
}
3.14 history 模式与 hash 模式
对比
| 模式 | URL 格式 | 特点 |
|---|---|---|
| hash | #/about |
兼容性好,地址栏带有 # 号 |
| history | /about |
地址栏干净,但需要后端配置支持 |
配置 history 模式
javascript
const router = new VueRouter({
mode:'history',
routes:[...]
})
注意:使用 history 模式部署到服务器时,需要后端配置支持,否则刷新页面会出现 404 错误。
第四章:Vuex
4.1 Vuex 简介
概念
专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 Vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
Vuex 工作原理
Vuex 采用集中式状态管理架构,通过单向数据流实现组件间的高效通信。其核心组成包括:

什么时候使用 Vuex
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
4.2 基本使用
安装 vuex
bash
npm i vuex@3
创建 store
javascript
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
// 准备 actions------用于响应组件中的动作
const actions = {
jiaOdd(context, value){
if(context.state.sum % 2){
context.commit('JIA', value)
}
},
jiaWait(context, value){
setTimeout(() => {
context.commit('JIA', value)
}, 500)
}
}
// 准备 mutations------用于操作数据(state)
const mutations = {
JIA(state, value){
state.sum += value
},
JIAN(state, value){
state.sum -= value
}
}
// 准备 state------用于存储数据
const state = {
sum: 0
}
// 创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
})
组件中使用 Vuex
html
<template>
<div>
<h1>当前求和为:{{$store.state.sum}}</h1>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>
<script>
export default {
name:'Count',
data() {
return { n: 1 }
},
methods: {
increment(){
this.$store.commit('JIA', this.n)
},
decrement(){
this.$store.commit('JIAN', this.n)
},
incrementOdd(){
this.$store.dispatch('jiaOdd', this.n)
},
incrementWait(){
this.$store.dispatch('jiaWait', this.n)
},
},
}
</script>
Vuex 工作流程
组件 ------(dispatch)→ actions ------(commit)→ mutations ------(mutate)→ state ------(render)→ 组件
↑ |
└───────────────── getters ────────────────────┘
4.3 getters 的使用
概念:当 state 中的数据需要经过加工后再使用时,可以使用 getters 加工
定义 getters
javascript
const getters = {
bigSum(state){
return state.sum * 10
}
}
export default new Vuex.Store({
// ...
getters
})
组件中使用 getters
html
<h1>当前求和放大10倍为:{{$store.getters.bigSum}}</h1>
4.4 mapState 与 mapGetters
作用:帮助我们映射 state 和 getters 中的数据为当前组件的计算属性
javascript
import { mapState, mapGetters } from 'vuex'
export default {
computed:{
// 借助mapState生成计算属性,从state中读取数据(对象写法)
...mapState({
sum:'sum',
school:'school',
subject:'subject'
}),
// 借助mapState生成计算属性,从state中读取数据(数组写法)
...mapState(['sum', 'school', 'subject']),
// 借助mapGetters生成计算属性,从getters中读取数据(数组写法)
...mapGetters(['bigSum'])
}
}
4.5 mapMutations 与 mapActions
作用 :帮助我们生成与 mutations 和 actions 对话的方法,即:包含 $store.commit(xxx) 和 $store.dispatch(xxx) 的函数
javascript
import { mapMutations, mapActions } from 'vuex'
export default {
methods:{
// 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations
...mapMutations({
increment:'JIA',
decrement:'JIAN'
}),
// 借助mapActions生成对应的方法,方法中会调用dispatch去联系actions
...mapActions({
incrementOdd:'jiaOdd',
incrementWait:'jiaWait'
})
}
}
注意:mapMutations 和 mapActions 使用时,需要在模板中绑定事件并传递参数。
4.6 多组件共享数据
将共享数据定义在 Vuex 的 state 中,各组件直接读取即可。
4.7 模块化编码
目的:让代码更好维护,让多种数据分类更加明确
开启命名空间
javascript
// store/count.js
export default {
namespaced: true,
actions: {
jiaOdd(context, value){ ... },
jiaWait(context, value){ ... }
},
mutations: {
JIA(state, value){ ... },
JIAN(state, value){ ... }
},
state: {
sum: 0,
school: '前端开发教程',
subject: '前端'
},
getters: {
bigSum(state){ ... }
},
}
javascript
// store/person.js
export default {
namespaced: true,
actions: {
addPersonWang(context, value){ ... },
addPersonServer(context){ ... }
},
mutations: {
ADD_PERSON(state, value){ ... }
},
state: {
personList: [
{id:'001', name:'张三'}
]
},
getters: {
firstPersonName(state){ ... }
},
}
合并模块
javascript
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
countAbout: countOptions,
personAbout: personOptions
}
})
组件中使用模块化数据
html
<h1>当前求和为:{{$store.state.countAbout.sum}}</h1>
<h1>当前求和放大10倍为:{{$store.getters['countAbout/bigSum']}}</h1>
<button @click="$store.commit('countAbout/JIA', n)">+</button>
<!-- 使用 mapState -->
...mapState('countAbout', ['sum', 'school', 'subject']),
<!-- 使用 mapGetters -->
...mapGetters('countAbout', ['bigSum']),
<!-- 使用 mapMutations -->
...mapMutations('countAbout', { increment:'JIA', decrement:'JIAN' }),
<!-- 使用 mapActions -->
...mapActions('countAbout', { incrementOdd:'jiaOdd', incrementWait:'jiaWait' }),
总结
本教程系统介绍了 Vue2 的核心知识,包括:
| 模块 | 主要内容 |
|---|---|
| Vue 基础 | 模板语法、数据绑定、计算属性、监视属性、生命周期、组件等 |
| Vue 脚手架 | ref、props、mixin、插件、scoped、组件通信、过渡动画等 |
| Vue Router | 基本使用、嵌套路由、参数传递、路由守卫、编程式导航等 |
| Vuex | state、mutations、actions、getters、模块化等 |