Vue
001、Vue-简介
一、Vue简介
- 一套用于构建用户界面的渐进式框架
- 核心思想是数据驱动:数据映射视图,数据的修改会自动引起视图的改变。
- 双向数据绑定:数据的改变会引起视图的变化,视图的变化也会引起数据的变化,这就是双向数据绑定。
二、如何理解Vue的渐进式框架
- Vue框架本身是分层的设计:
- 核心,视图渲染。
- 组件化
- 单页面路由
- 状态管理
- 构建系统
三、前端三大框架
框架名 | 出现时间 | 所属 | 一开始特色 |
---|---|---|---|
Angular | 2009年 | 谷歌 | 指令系统、双向数据绑定 |
React | 2013年 | 虚拟DOM、组件化 | |
Vue | 2015年 | 尤玉溪 | 指令系统、双向数据绑定、虚拟DOM、组件化 |
四、MVC、MVVM
-
MVC
-
M:模型:(Model)
-
V :视图 (view)
-
C : 控制器 (Controller)
-
各部分之间的通信:
Model—>View–>Controller—>Model
- View 传送指定到 Controller。
- Controller完成业务逻辑后要求Model改变状态。
- Model将新的数据发送到View,用户得到反馈。
-
所有通信都是单向的
-
-
MVVM
-
M :模型:(Model)
-
V :视图 (view)
-
VM : 视图模型 (ViewModel)
-
各部分之间的通信:
View<—>ViewModel<—>Model
- View 与 Model 不发生联系,都通过 ViewModel 传递
- View 与 ViewModel 之间双向绑定:View 的变动,自动反映在 ViewModel ,反之亦然。
- Angular、Vue 都采用了这种模式。
-
-
虚拟DOM
- 什么是MVP、MVC、MVVM模式?
- M model数据
- V view视图
- C control控制器
- 如何理解MVVM?
- VM 即视图模型,你可以理解为虚拟DOM
- 什么是虚拟DOM? 虚拟DOM就是一个json对象,它用于对真实DOM进行描述。
- 什么是diff运算? 当Vue实例中的数据发生变化时,Vue会获得一份对虚拟DOM的拷贝,如此我们就有两份虚拟DOM,一份是数据变化前的虚拟DOM,一份是数据变化后的虚拟DOM。所谓的Diff运算,就是对这两份虚拟DOM进行差异比较,从而找出它们的最小差异。再把这份最小的差异渲染到真实DOM中去。 MVVM框架,基于这种虚拟DOM的Diff运算,大大地减少了DOM的频繁操作,减少DOM操作本身就是一种性能优化。所以MVVM框架有利于性能优化,非常适合数据化的产品应用开发。
- 什么是MVP、MVC、MVVM模式?
五、hello Vue
-
Vue的简单使用步骤
-
页面中需要提供一个挂载点(用来承载Vue实例)。就提供一个div并设置一个id即可
-
引入Vue的核心js文件
- 开发版本 vue.js
- 生产版本 vue.min.js
-
实例化Vue ,并做相关设置
-
引入Vue核心js文件之后,就给window对象上加了一个Vue对象(Vue是一个类)
/实例化Vue var vm = new Vue({ //配置选项 //el:配置挂载点,也就是说这里生成的Vue实例会应用到哪个页面位置上,1.值可以使一个css选择器写法'#app',2.可以是一个元素的Dom对象document.getElementById('app') el:"", //data:声明式变量,当前实例的数据源,data选项中自己定义的数据可以通过实例对象直接访问,设置。 data:{ //这块是自己写的 } })
-
-
六、数据驱动
- Vue 核心思想是数据驱动,数据驱动:数据的变化会 引起视图(页面)的变化。
002、Vue - 内置指令
一、指令是什么
- Vue 中,以 v- 开头的标签属性被称为指令。不同的指令有不同的作用,指令的值是js表达式,""里的是变量。
二、双向数据绑定
- 双向数据绑定;数据的变化会引起 页面的变化,反之亦然,主要用于INPUT框。
三、常用的Vue指令
1.文本类
-
v-text
-
将数据绑定到元素的textContent上面
<P v-text='name'></p>
-
-
v-html
-
v-html与v-text基本一样
-
v-html会 解析html元素 ,v-text 不会解析html元素。
<P v-html='name'></p>
-
-
v-once
它只能和文本插值进行使用,它只渲染一次-
<P v-once>{{name}}</p>
-
-
{{}}
-
文本插值:渲染到页面的时候会有一闪而过的情况
<p>{{name}}</p>
-
2.动态属性、动态样式
-
v-bind
(简写 :)用于动态绑定html标签属性语法:
-
v-bind:attrname="数据"
-
:attrname="数据"
-
v-bind=对象
v-bind="{name:'张三',age:18}"==>v-bind:name="张三" v-bind:age="18"
v-bind="{name:name,age:age}"==>v-bind:name="name" v-bind:age="age"
- 此时对象的值在这里是变量,是data里的变量
-
Vue中class 属性是一个特殊的属性 ,使用v-bind绑定时有很多不同的用法
-
v-bind:class="'hello'"
- 此时 hello为字符串 ,代表给class加上hello这个类名
-
v-bind:class="hello"
- 此时hello为vue实例中data的属性,代表给class加上hello代理属性对应的类名
-
v-bind:class="{key1:value1,key2:value2}"
-
根据value1这个数据是否为真值,来控制是否要加上key1这个 类名
-
根据value2这个数据是否为真值,来控制是否要加上key2这个 类名
v-bind:class="{ active: isActive, 'text-danger': hasError }" v-bind:class="[isActive ? activeClass : '', errorClass]"
-
-
数组
v-bind:class="[value1, 'value2',value3,{key:value}]"
- 使用value1这个数据的值来作为其中一个l类名
- 使用value2这个字符串来作为其中一个l类名
- 使用value3这个数据的值来作为其中一个l类名
- 根据value这个数据是否为真值,来控制是否要加上key这个 类名
-
-
Vue中style 属性是一个特殊的属性 ,使用v-bind绑定时有很多不同的用法
-
v-bind:style="color : red"
这种是错误的写法会报错 -
v-bind:style="{key1:value1,key2:value2,key3:'value3'}"
key1、key2、key3、都是css的属性名
value1的值作为key1这个css属性名的值
value2的值作为key2这个css属性名的值
value3这个字符串会作为key3这个css属性名的值
v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"
-
v-bind:style="[value1, value2,{color:myColor}]"
使用value1这个数据来控制,data中的value1必须为对象{font-size:‘20px’}
使用value2这个数据来控制,data中的value2必须为对象{font-size:‘20px’}
使用myColor这个数据来控制color属性的值
-
-
v-bind 在组件时的sync修饰符
sync—update:prop 搭配使用 prop传到子组件的变量
<div id="app"> <hello v-bind:value1.sync="msg1" v-bind:value2.sync="msg2"></hello> </div> Vue.component("hello", { props: { value1: { type: String, }, value2: { type: String, }, }, template: ` <div> <p>{{ value1 }}</p> <p>{{ value2 }}</p> <button @click="fn1" >点我,修改 value1</button> <button @click="fn2" >点我,修改 value2</button> </div> `, methods: { fn1() { // 不能直接去修改prop数据 // this.msg = "我的天"; // 通知父亲去修改 this.$emit("update:value1", "我的天"); }, fn2() { this.$emit("update:value2", "我的地"); }, }, }); var vm = new Vue({ el: "#app", data: { msg1: "hello", msg2: "world", }, });
-
3.条件渲染
-
v-show
-
根据表达式的真假值,切换元素的display CSS显隐属性
<div id= 'app'> <button v-show=' !islogined'>登录</button> <button v-show=' islogined'>退出登录</button> </div> <script> var vm = new Vue({ el:'#app' data:{ islogined:true } }) </script>
-
-
v-if
-
v-if与v-show的效果基本一致
-
v-if与v-show的区别
-
v-if是真正的条件渲染 ,条件为true元素渲染出来,默认条件为flase时,不渲染,条件变为flase时会销毁,条件变成 true的时候渲染出来。
-
v-show只是简单去控制CSS的display属性
-
v-show有更高的初始渲染开销,(因为不管默认条件是true还是false,都会渲染出来)
v-if有更高的切换开销(切换时做重建销毁的操作)
如果需要频繁显示隐藏切换用v-show更好,其余情况使用v-if
-
v-if有v-else和v-else-if配套使用,v-show没有
-
v-if可以配合template元素一起使用,v-show不可以。
因为v-show是要控制元素的css属性的,渲染页面的,template是不会被渲染出来的。
<div id= 'app'> <button v-if=' !islogined'>登录</button> <button v-if=' islogined'>退出登录</button> </div> <script> var vm = new Vue({ el:'#app' data:{ islogined:true } }) </script>
-
-
-
v-else
与v-else-if
-
这两个指令都需要与v-if搭配才可以使用
-
v-else不需要表达式
<div id= 'app'> <button v-if=' !islogined'>登录</button> <button v-else>退出登录</button> <p v-if='score>=90'>优秀</p> <p v-else-if='score>=80'>良好</p> <p v-else-if='score>=60'>及格</p> <p v-else>不及格</p> </div> <script> var vm = new Vue({ el:'#app' data:{ islogined:true sorce:100 } }) </script>
-
v-else-if需要表达式
<div id= 'app'> <button v-if=' !islogined'>登录</button> <button v-else>退出登录</button> <p v-if='score>=90'>优秀</p> <p v-else-if='score>=80'>良好</p> <p v-else-if='score>=60'>及格</p> <p v-else>不及格</p> </div> <script> var vm = new Vue({ el:'#app' data:{ islogined:true sorce:100 } }) </script>
-
4.列表渲染
-
v-for
列表循环-
循环数组
-
v-for=“item in xxx”
-
v-for=“(item,index) in xxx”
<div id= 'app'> <div v-for='item in arr'> <span></span> <span></span> <span></span> </div> </div> <script> var vm = new Vue({ el:'#app' data:{ arr:[ {id:1,name:'yeshuai'}, {id:2,name:'xiezhaoyi'} ] } }) </script>
-
-
循环对象
v-for=“item in xxx”
v-for="(value,key,index) in xxx"
-
循环字符串
v-for=“item in "张三"
v-for=“item in "10"
- 相当于循环数组[1,2,3,4,5,6,7,8,9,10]
-
-
template
元素-
Vue给我们提供了一个template 元素,他的作用是包裹元素,不会渲染到浏览器上。
<div id= 'app'> <template v-if='isshow'> <p>优秀</p> <p>良好</p> <p>及格</p> <p>不及格</p> </template> </div> <script> var vm = new Vue({ el:'#app' data:{ isshow:true } }) </script>
-
5.v-on
绑定事件
-
v-on
语法:
v-on:xxxx= 'yyyy'
- xxxx 事件名,keyup,click,mousemove,mouseenter,
- yyyy 事件处理函数
@xxxx = 'yyyy'
- xxxx 事件名,keyup,click,mousemove,mouseenter,
- yyyy 事件处理函数
6.v-model
-
v-model
将数据与表单相关元素 input、textarea、select绑定起来,实现双向数据绑定 -
input为输入框的时候,v-model就双向绑定了输入框的值和data里对应的代理属性的值
-
当为单选框的时候设置v-model且data对应的代理属性一致,那么就 可以单选,且绑定值
-
多选框,定义一组数组类型的数据,然后通过v-model绑定到多选框上就可以了
<label><input type="checkbox" value="apple" v-model="loveFruits" />apple</label> <label><input type="checkbox" value="banana" v-model="loveFruits" />banana</label> <label><input type="checkbox" value="orange" v-model="loveFruits" />orange</label>
-
下拉选择框,v-model要绑定到select上,myCity不是数组
<select v-model="myCity"> <option value="shenzhen">深圳</option> <option value="shanghai">上海</option> <option value="guangzhou">广州 </option> <option value="beijing">北京</option> </select>
多选下拉选择框,v-model要绑定到select上,loveCity是数组
<select v-model="loveCity"> <option value="shenzhen">深圳</option> <option value="shanghai">上海</option> <option value="guangzhou">广州 </option> <option value="beijing">北京</option> </select>
-
多行文本输入框 ,v-model绑定到textarea
-
v-mode有三个修饰符
v-mode.lazy
相当于change事件,比如失去焦点v-mode.number
文本框的值变成number型v-mode.trim
给文本去除前后空格
-
v-mode其实是一个语法糖(写起来方便,让我们甜甜的)
<input type="text" v-model="msg" /> === <input type="text" v-bind:value="msg" v-on:input="msg=$event.target.value" />
-
$event
- 原生事件时它是事件对象
- 自定义事件时它是触发事件时传递过来的payload参数
-
单个复选框v-model用法
<input type="checkbox" v-model="checked" />我们的协议 <button @click="handleRegister" :disabled="!checked">注册</button> //页面打开时,调用后台接口,通过接口返回的数据来控制这个勾选框是否勾上。这时发现后台接口返回的数据不是一个 boolean 类型。而是字符串的 Y / N <input type="checkbox" v-model="isChecked" true-value="Y" false-value="N" /> var vm = new Vue({ el: "#app", data: { checked: false, isChecked: "N", }, methods: { handleRegister() { alert("注册"); }, handleRegister2() { if (this.checked) { alert("注册2"); } }, }, });
-
7.v-pre
-
有些时候我们需要在页面上 就直接输出{{}}这种格式的字符串,不让vue对其做解析的操作。这是只需要使用上v-pre就可以了
-
v-pre没有参数,也没有表达式
<p v-pre>{{msg}}</p>
8.v-cloak
-
vue页面闪烁问题:
-
有时会在页面出现插值表达式的语句
-
vue解析页面需要时间
解决方案:
- 不适用插值表达式改用v-text
- 使用v-cloak
- 将v-cloak写在挂载点元素上,他没有参数,也没有表达式
- 当vue第一次解析成功以后会将v-locak属性主动删除掉
- 设置一个全局的CSS样式。样式如下
-
[v-cloak]{
display:none;
}
003、Vue - 数据更新检测
一、Vue的核心思想是数据驱动
- Vue是数据驱动的,如果某个数据类型是数组类型,在一些数组的炒作下是可以更新页面的,还有一些数组的操作不会引起页面的更新。
二、可以引起页面更新的数组方法(怪异)
- unshift() 前面增加
- push() 后面增加
- shift() 前面删除
- pop() 后面删除
- splice() 高级方法删除、添加、删除
- sort() 排序
- reverse() 倒序
三、下面两种方式修改数组是不会引起页面的更新
-
直接通过下标的方式修改数组
vm.arr[10]=“halo”
-
通过length修改数组 vm.arr.length=num
解决办法:
- 使用
Vue.set(target,index,value)
的静态方法 - 使用实例对象
vm.$set(target,index,value)
方法Vue.set(target,index,value)
- target 要修改的数据源
- index 要修改的下表
- value 要修改成什么样
- 使用
四、对象更新检测的问题
-
直接给对象新增一个属性是不会引起页面更新的
解决办法:
- 使用
Vue.set(target,key,value)
或者vm.$set(target,key,value)
Vue.set(target,key,value)
- target 要添加的数据源
- key 要添加的key
- value 要修改成什么样
- 使用
004、Vue - 属性/方法
- 类似这种 Vue.xxx() 静态方法
- 通过实例对象 .\$xxx() 实例方法
- vm.\$set()
- vm.\$destory()
- 通过实例对象.\$xxx 实例属性
- vm.\$data
- vm.\$sel
- 代理属性,一般就是定义在data选项中的数据,通过实例对象去访问。
- 代理方法,一般就是定义在methods选项中的方法,通过实例对象直接访问
005、Vue - 修饰符
1.修饰符
- stop 阻止事件传播
- prevent 阻止默认事件
- self 只点自己
- capture 事件流-----事件捕获
- {kecode}|{keyAlias} 特定键盘修饰符
- keyAlias
- enter
- tab
- delete
- esc
- space
- up
- down
- left
- right
- Vue.config.keycodes.fn1=13
- once 只绑定一次
- left 鼠标左键
- right 鼠标右键
- middle 鼠标滚轮
- passive
2.系统修饰符
- ctrl
- shift
- alt
- meta windos键
- exact 精准匹配
-
native 事件修饰符
-
作用:将事件绑定到组件的根元素上
-
在调用组件时去绑定一些原生事件,比如 click dblclick mousedown。他们是不生效的。因为会将他们任何是自定义事件的监听
-
解决方法:
-
子组件触发这个事件
<div id="app"> <base-button @click="fn1">按钮1</base-button> </div> Vue.component("baseButton", { template: ` <button @click="$emit('click')"> <slot></slot> </button> `, }); var vm = new Vue({ el: "#app", methods: { fn1() { console.log("fn1"); }, }, });
-
使用 native 事件修饰符
<div id="app"> <base-button @click.native="fn1">按钮1</base-button> </div> Vue.component("baseButton", { template: ` <button> <slot></slot> </button> `, }); var vm = new Vue({ el: "#app", methods: { fn1() { console.log("fn1"); }, }, });
-
-
006、Vue - watch 配置选项
一、watch是做什么的?
-
监听数据的变化,当数据发生变化的时候,可以去做一些额外的操作
语法1:
-
watch:{
key1:value1,
key2:value2
}
key1,key2是要监听的数据表达式
value1,value2是处理函数。string|function|object|Array
处理函数有两个参数,newVal,oldVal
watch:{ a:function(newVal,oldVal){ console.log(newVal,oldVal) }//不可以写箭头函数 a:'hello' //要在methods配置选项里写hello函数,不然会报错 a:{ //一定要提供handler属性,属性值就是处理函数, handler:function(newVal,oldVal){ console.log(newVal,oldVal) } } 写成对象的写法可以监听,做更多的事情。 //深度监听;对象的属性值更改默认是监听不到的 //immediate 默认触发一次监听 obj:{ handler:function(newVal,oldVal){ console.log(newVal,oldVal) }, deep:true//深度监听 immediate;true } //key的表达式的写法,直接监对象里的属性 "obj.name":function(newVal,oldVal){ console.log(newVal,oldVal) } }
-
007、Vue - computed 配置选项
-
computed 配置选项(计算属性,衍生属性,派生属性)
-
其基于其余的数据算出来的一分新数据
-
computed 的依赖项可以是 data 数据 props 数据或者其余的 computed 数据
-
语法
-
computed:{
key1:value1,
key2:value2
}
- key1/key2就是计算属性
- value1、value2就是对应的计算属性的处理函数 (计算方法),且必须有返回值,返回值就是这个计算属性的值
computed:{ key1:function(){ return 计算属性+计算属性 }, key2:function(){ return 计算属性+计算属性 }, }
-
computed:{
key1:{
get:function(){
return 计算属性+计算属性
},
set:function(value){
return 计算属性+计算属性
}
}
}
-
计算属性的特点:
- 计算属性也可以像data中的数据那样去使用
- 计算属性的值不允许手动修改,他的值基于依赖项
- 计算属性有缓存,默认计算出来之后,如果他的依赖项不发生变化,那么后续将一直使用缓存的数据
-
008、Vue - 生命周期/钩子函数
一、什么是生命周期?
- vue实例或组件(组件本质上就是一个具有预定义选项的实例)创建到销毁的一系列过程,就叫生命周期
二、什么是钩子函数?
- 在生命周期不同的阶段中会自动执行的函数,就叫做生命周期的钩子函数
三、钩子函数有哪些?
beforeCreate 实例创建之前
created 实例创建之后
beforeMount 实例挂载之前
mount 实例挂载之后
beforeUpdate 数据更新之前
updated 数据更新之后
beforeDestroy 实例销毁之前
destroyed 实例销毁之后
-
swiper 引入接口的图片会有小问题,图片是异步回调,
-
解决方案:
-
-
使用updated钩子函数,等图片都回来渲染到视图上,在this.swiper.update()
-
在ajax请求成功的回调函数内
this.$nextTick(()=>{ this.swiper.update() })
-
-
-
009、Vue - props选项
一、props选项
- 将组件看成是一个函数,props 就是这个函数接收到的参数集合
- prop 就是 props 中的具体一个参数
function hello(props) {
}
hello({name: 张三, age: 18, sex: '男'})
props => {name: 张三, age: 18, sex: '男'} 形参
name - prop
age - prop
sex - prop
//这里就是举一个例子
二、组件定义时如何设置形参,就是设置 props 选项
Vue.component('hello', {
//最简单的方式,写成数组形式,数组中每一项就是一个 prop
//注意:调用时不传递是没问题的
//定义的每一个 prop 。可以直接通过实例对象去访问到,就向访问 data 数据和 computed 数据那样
props: ['name', 'age', 'sex'],
template: `
<div>Hello {{ name }} - {{ age }}</div>
`,
methods: {
hi() {
console.log(this.name)
}
}
})
三、调用组件时,如何传递参数,就是在组件的标签上写属性即可
hello 组件中的 name 得到的是 “张三”
hello 组件中的 age 得到的是 “18”
hello 组件中的 sex 得到的是 undefined
-
如果调用组件时,没有传递某个 porp 下来,那么组件中的这个 prop 的值是 undefined
我们希望能够设置 prop 的默认值,也就是说调用时如果没有传递的化,我就使用默认值
function hello(name = ‘张三’) {
}
hello()
组件中如何给 prop 设置默认值呢?关键点将 props 选项从数组格式修改成 对象格式
Vue.component("hello", { props: { key1: { type: String, default: 'xxx' }, key2: { type: Number, default: 'yyy' } }, template: ` <div>Hello. {{ name }}</div> `, }) //key1、key2 就是 prop 的名字。 //type: String, type: Number, 代表对应 prop 希望接收到的数据类型 //default: 'xxx', 默认值设置。
四、希望在调用组件时传递过来的prop是有效的。
-
比如说:
我希望你给我传 String 类型, 调用时就要传 String 类型过来。
props: {
// value 直接写要的类型
key1: String
// value 写成对象,在对象中再通过 type 属性去控制类型
key2: {
type: Number
}
}
五、单向数据流
-
单向数据流:数据的流向是从高往地流。不能反过来
root ->prop-> hello √
hello ->prop-> root ×
六、非 prop 的 attribute (非 prop 的特性)
- 在调用组件时,设置的属性(attribute)如果这个组件中没有定义相应的prop。那么这个属性就叫做 非prop的attribute
Vue.component('hello', {
props: {
name: String,
age: Number
},
template: `<div>hello</div>`
})
<hello name="张三" age="18" id="box" title="我的天" class="box1" style="color: red"></hello>
- 这里 hello 组件调用时。设置的属性有:name、age、id、title、class、style
- hello 组件中将 name、age 是规定为 prop 数据的
- 那么 这里 id 和 title 这两个属性就叫做非prop的特性
注意:class 与 style 不是非prop的特性。他们两个是非常非常特殊的一个属性
-
非prop的attribute 的特点
- 最主要的一个特点就是会继承到组件的根元素上
- 并且是一种替换的操作
-
class与style是非常特殊的
- 他们有 非prop的attribute 的继承的特点
- 但是他们不是替换的操作,而是拼接操作
注意:用处。调用别人的组件的时候,这个组件的源代码我不能修改,这时如果要修改样式,就可以在调用时
设置自己的 class 或 style
七、组件的 inheritAttrs 配置选项
- 组件接收到的 非prop特性 会自动继承到组件的 根元素上。如果不希望继承,就可以配置 inheritAttrs 为 false 。
Vue.component("hello", {
inheritAttrs: false,
props: {
name: String,
age: Number,
},
template: `
<div>hello</div>
`,
});
八、 $attrs 和 inheritAttrs来实现一些基础组件
Vue.component("baseInput", {
inheritAttrs: false,
props: {
label: {
type: String,
required: true,
},
},
// 不使用prop数据,而是使用非prop特性数据
// template: `
// <div>
// <label>
// <span>{{ label }}</span>
// <input
// :type="$attrs.type"
// :placeholder="$attrs.placeholder"
// :disabled="$attrs.disabled"
// :readonly="$attrs.readonly" />
// </label>
// </div>
// `,
// 一个一个的去绑定 $attrs 也挺费劲。这时可以使用 v-bind 直接绑定一个对象
// v-bind="{key1: value1, key2: value2}" => v-bind:key1="value1" v-bind:key2="value2"
template: `
<div>
<label>
<span>{{ label }}</span>
<input
v-bind="$attrs" />
</label>
</div>
`,
created() {
console.log(this.$attrs);
},
});
010、Vue - 组件之间的关系
一、父—>子通信
- 就是使用prop传递数据给子组件即可
二、子—>父通信
-
子组件通过$emit() 去出发一个自定义事件
//子组件的$emit()去触发一个自定义事件 methods:{ fn1(){ //触发自定义事件 this.$emit('abc') //'abc'是父组件自定义的监控子组件的事件 } } //同时父组件在调用子组件的时候也要去监听这个自定义事件 <son @abc='父组件methods里的事件处理函数'></son>
三、兄弟通信
-
中央事件管理器(中央事件总线),适用于: 亲兄弟之间或者关系特别复杂的组件之间
-
先创建一个空的vue实例对象。也就是不需要额外配置什么选项。
const bus = new Vue()
-
A B 两个组件,A 通信 B
-
先在 B 组件的 created 中去通过 bus 来监听一个自定义事件
bus.$on(eventName, callback)
- eventName 要监听的事件的名字 - callback 事件触发时的一个回调函数
-
在 A 组件的某个时候去触发自定义事件
bus.$emit(eventName, payload)
- eventName 要触发的事件的名字
- payload 触发事件时传递过去的参数
-
-
-
// 创建一个事件总线
const bus = new Vue();
Vue.component("hello", {
data() {
return {
name: "hello",
};
},
template: `
<div>hello - {{ name }}</div>
`,
created() {
// 监听
bus.$on("abc", () => {
console.log("abc事件被触发了");
// this === hello 组件实例
this.name = "hellohello";
});
},
});
Vue.component("world", {
data() {
return {
name: "world",
};
},
template: `
<div>
world - {{ name }}
<button @click="fn1">点击,去修改 hello 组件的name</button>
</div>
`,
methods: {
fn1() {
// 触发abc
bus.$emit("abc");
},
},
});
var vm = new Vue({
el: "#app",
});
- vuex 全局状态管理器
011、Vue - slot插槽
- 调用组件时,写在组件标签内的内容,默认是不会被渲染的。有时,我们需要在写在组件标签内的内容能够被渲染出来,这时就需要使用 slot 插槽这个功能点
一、slot相关的一些话术
-
slot内容、插槽内容、插槽模板内容
调用组件时写在组件标签内的内容就叫做 slot 内容
-
slot标签、slot组件、slot插槽、插槽 (坑、茅坑)
Vue 内置的一个组件
二、slot 的基本使用
-
定义组件时想好要在哪个位置去渲染 slot 内容
-
在想好的位置哪里,放置一个 slot 标签 即可
-
slot 内容 就会自动去替换 slot 标签
<div id="app">
<hello>
<p>我的天,我不帅吗?</p>
</hello>
</div>
Vue.component("hello", {
template: `
<div>
<h1>Hello</h1>
<slot></slot>
</div>
`,
});
三、slot 默认内容 (slot 后备内容)
- 写在 slot 标签 内的内容就是这个 slot 标签 的默认内容
四、具名 slot (具名插槽)
- 给 slot 标签 设置一个 name 属性。这时 这个 slot 标签就叫做 具名slot便签
- slot 在一个组件内是可以使用多次的
- 给 slot 取了名字之后,slot 内容想要渲染在哪个 slot 里面的话,就需要设置 slot 属性。属性的值为某个 slot 的名字。
- slot 有一个默认的名字,就叫做 default
<div id="app">
<hello>
<p slot="bottom">我的天,我不帅吗?</p>
<button>点我</button>
</hello>
</div>
Vue.component("hello", {
template: `
<div>
<slot name="top"></slot>
<h1>Hello</h1>
<slot name="bottom"></slot>
<hr />
<slot name="default"></slot>
</div>
`,
});
五、插槽的编译作用域
- 编译作用域:
- 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
- 插槽的编译作用域:
- 注意:虽然说插槽内容最终渲染在子组件内。但是他的编译作用域还是要看插槽内容写在哪个组件的template里面。而不是看插槽内容最终渲染在哪个组件的template里面的slot标签。
- 写在组件标签内的插槽内容是不属于组件的template的。只是通过slot可以让他渲染在组件里面去。
六、作用域插槽
<!-- 老语法的作用域插槽使用 -->
<world>
<p slot="top" slot-scope="{ msg, age }">
我的天-{{ msg }} - {{ age }}
</p>
</world>
<!-- 新语法的作用域插槽使用 -->
<world>
<template #top="obj">
<p>
我的天-{{ obj.msg }} - {{ obj.age }}
</p>
</template>
</world>
<!-- 新语法的作用域插槽使用 解构赋值-->
<world>
<template #top="{ msg, age }">
<p>
我的天-{{ msg }} - {{ age }}
</p>
</template>
</world>
Vue.component("world", {
data() {
return {
msg: "张三",
age: 18,
};
},
template: `
<div>
<slot name="top" :msg="msg" :age="age"></slot>
<h1>world</h1>
<slot name="bottom"></slot>
</div>
`,
});
七、插槽的新语法
-
slot 插槽 在 2.6.0 这个版本的时候,做了更新。提供了一个新的指令叫做 v-slot。后续实现具名插槽与作用域插槽都使用 v-slot 来实现
-
2.6.0 版本在什么时候发布的:2019-02-04号(农历2018年过年那天)
-
v-slot语法:
-
v-slot:xxxx=yyyy
- xxxx 插槽名字
- yyyy 作用域插槽数据
-
v-slot 必须用在 template 元素上。也就是说 插槽内容 需要通过 template 元素进行包裹
-
如果插槽没有设置 name 名字的化,
v-slot === v-slot:default
-
v-bind 指令有个简写 :
v-on 指令有个简写 @
v-slot 指令有个简写 #
注意,使用 简写时必须携带名字。 默认的名字需要写成 #default
- 插槽只有一个的情况下,可以不使用 template 去包裹插槽内容。而是直接将 v-slot 写在组件标签上。
-
-
012、Vue - component/keep-alive
一、什么是动态组件
- Vue 中提供有一个内置的组件叫做 component 。这个组件就是动态组件。它需要设置 is 属性。is 属性的值就是需要渲染的组件的名字
- component 不具备缓存能力,是一直在创建,销毁
<component is="hello" ></component> hello 组件替换这个内容
<component is="world" ></component> world 组件替换这个内容
- 如果组件不存在就会报错
- 将is属性设置为一个可以变化的数据,就能实现组件的动态切换
二、keep-live
- Vue 中提供了一个 keep-alive 内置组件。可以实现组件的缓存。
- 将要缓存的组件使用 keep-alive 包裹住即可。
- 被 keep-alive 缓存了的组件的特点:
- 切换组件时,当前组件不会触发销毁的生命周期钩子。也就是说不会销毁了。
- 切换回来时,也不会重新创建。(既然都没有被销毁,哪里来的重新创建呢)
- 会多出两个生命周期的钩子函数
- activated 缓存激活 第一次会触发、组件能被看到
- 一般根 created 做一样的事情:请求数据
- deactivated 缓存失活 组件不能被看到
- 一般根 beforeDestroy 做一样的事情: 清除定时器、移除全局事件监听
- activated 缓存激活 第一次会触发、组件能被看到
<keep-alive>
<component :is="curPage"></component>
</keep-alive>
-
keep-alive 的更多属性设置:
-
include 包含
- include=“组件1,组件2” 注意 逗号前后不要有空格
- :include="[组件1, 组件2]"
- :include="/^hello/"
-
exclude 排除
- exclude=“组件1,组件2” 注意 逗号前后不要有空格
- :exclude="[组件1, 组件2]"
- :exclude="/^hello/"
-
max 规定最大能缓存组件的数量,默认是没有限制的
-
max=x2 规定最大能缓存组件的数量为2
假定缓存队列是 [home, list]
现在进入about的时候 about 也会被缓存上,这时会将之前的第一个给排出去 [home, list, about] => [list, about]
先进先出原则
-
013、Vue - 脚手架/@vue/cli
一、安装
- 安装脚手架指令
$ npm install @vue/cli -g
- 查看vue脚手架版本号
$ vue --version
二、创建项目
- 找到创建目录的文件夹 运行git bash here
$ vue create 项目名称
三、启动项目
- 启动项目,生成服务器
$ npm run serve
四、解释项目构成
- node_modules 项目依赖包
- public 静态资源文件,里面有一个默认的index.html和favicon.ico小图标
- index.html 整个项目的入口文件,它只提供了一个挂载点元素,js内容都是构建工具去处理的
- favicon.ico 当前项目文的浏览器小图标
- src 项目的源代码文件(放在这个文件夹里的东西我们认为都是需要构建的)
- assets 静态资源文件
- components 存放公用vue组件的文件
- .gitignore git忽略文件的设置文件
- babel.config.js babel相关的配置文件(es5以上语法转es5语法)
- package.json 项目描述文件
- README.MD 读我文件。接收一个项目先看读我文件
- 项目文件夹下有一个 APP.vue 文件
- 这是根容器,就是new Vue()
- 一个 .vue 的文件就是一个组件,浏览器不能识别
- template 是一个视图,他的里面只能有一个子节点
- 视图容器
- script标签内写的是脚本 ,如果需要外部的东西,就引进来
//外部引入的组件不能直接用,要先转换成局部组件
import xxx from './components/HelloWorld.vue'
xxx 是组件名
'' 是组件的地址
6. exports default {} //当前组件的选项
export default {
//
name: 'App',
//从外部引入的组件变成局部组件
components: {
HelloWorld
}
//数据要使用工厂函数
data(){
return {
msg:'hello app'
}
}
}
4.项目文件夹下有一个 main.js 文件
- 是整个应用程序的入口文件
//main.js是整个应用程序的入口文件
import Vue from 'vue' //引入Vue这个类
import App from './App.vue' //引入App这个根组件
Vue.config.productionTip = false //生产环境不提示
new Vue({
render: h => h(App),
}).$mount('#app') //创建Vue的实例 并挂载在#app上
- style 样式 存放当前组件的样式 scoped 表示当前样式只对自己起作用
014、Vue - 路由
一、要区分后端路由与前端路由
-
后端路由:应用程序如何去处理不同的url地址发送过来的请求。
POST localhost:3000/api/login GET localhost:3000/list
-
前端路由:不同的url地址要渲染不同的页面效果出来
1.1 GET localhost:8080/index.html 1.2 GET localhost:8080/list.html 1.3 GET localhost:8080/about.html //多页面应用 2.1 GET localhost:8080/index 2.2 GET localhost:8080/list 2.3 GET localhost:8080/about //单页面应用 3.1 GET localhost:8080/#/index 3.2 GET localhost:8080/#/list 3.3 GET localhost:8080/#/about //单页面应用 1.x 的是以往看到的最多的 2.x 与 1.x 的很类似, history 模式路由 3.x 修改hash的操作 hash 模式路由
-
后端路由与前端路由最本质的一个区别:
前端路由只处理 GET 请求
-
Vue 中 路由有两种不同的模式
- hash 模式 (默认模式)
- hisotry 模式
-
hash模式与history模式的区别
- url地址表现不同,hash模式有#号,history模式没有
- 实现原理不同
- hash模式是通过改变hash并监听hashchange事件
- history模式是基于最新的HTML5里面history相关的api进行的一些操作
- 兼容性不同
- hash模式都可以兼容
- history模式只能兼容到IE10及以上
- ps: vue的兼容性,是只能兼容到ie8
- history模式会出现404找不到页面这种问题。需要后端配置相关的处理才行。而hash模式没有这个问题
- history模式时,重定向会有些问题,需要页面的名字叫index.html才行
-
如何切换模式呢
- new VueRouter() 的时候,配置 mode 选项就可以切换模式
二、单页面应用
- SPA single page application
- 一个应用只有一个html页面,它就是 SPA.
- 页面的切换其实是组件的切换,并修改了url地址。
三、多页面应用
- MPA multe page application
- 一个应用是有多个html页面组成的,页面之间的跳转是通过a标签的方式跳转的
四、路由
一、安装
- 一个局部的路由的包
$ npm install vue-router
二、根目录下创建 router.js
//模块引入
import Vue from 'vue' //引入Vue这个类
import VueRouter from 'vue-router' //引入App这个根组件
//注册使用路由
Vue.use(VueRouter)
//引入组件
import Todolist from './views/Todolist.vue'
const router = new VueRouter({
//定义路由匹配规则
routes:[
{path:'todo',
component:Todolist},
]
})
// 把路由实例抛出去
exports default router
// 把路由实例抛出去以后,要在main.js里面接收一下
import router from './router'
//main.js接收完以后,还要在vue的实例中挂载一下
new Vue({
router:router
render: h => h(APP)
}).$mount('#app')
三、router-link
-
与 router-view 一样,都是引入了 vue-router 之后,提供的全局组件
-
router-link的作用就是实现路由的跳转(url的跳转)它本质上就是一个a标签。我们推荐使用它,而不是去用a标签来跳转
-
语法:
- home
- to 属性是必须的。用来设置点击时跳转到哪里。
-
使用
router-link
的好处?-
路径不需要去写 # 号了。它会根据当前路由的模式来决定最终渲染出去的a标签的href属性有没有# 号。
-
能够很方便的去实现高亮效果
默认情况下,当前的路由地址与某个
router-link
的to属性匹配时,会给渲染出去的a元素添加上两个classs-
router-link-exact-active
-
router-link-active
-
这两个类可以添加高亮效果
方案一、只需要去设置一下这两个类中的某个类的样式就可以了
方案二、设置 router-link 组件的 active-class 属性,属性值为某个class名字
- 希望默认的 router-link-active => active
active-class=“active”
- 希望默认的 router-link-exact-active => exact-active
exact-active-class=“exact-active”
-
-
四、重定向
{
path: "/",
// 重定向,值设置为一个要重定向到的路径
redirect: "/home",
},
五、路由别名
//路由规则中配置 alias 配置项
{
{
path: "/home",
component: home,
alias: "/a",
},
{
path: "/list",
component: list,
alias: "/b",
},
{
path: "/about",
component: about,
alias: "/c",
},
}
六、路由
- 路由规则中path选项的值形如 /home/:xxxx/:yyyy 这种的就是动态路由
- 动态路由的使用场景:一般是在详情页面的时候
七、路由与传参
在router.js里配置路由,代码如下:
```
{ path: '/detail/:abc/:title', component: Detail }
```
在编程式路由跳转,代码如下:
```
this.$router.push('/detail/'+id)
```
在详情页面中,如何接收动态参数?代码:
```
this.$route.params.id
八、懒加载
- 语法:
const Home = ()=>import('@/views/Home.vue')
- 作用:应用程序中组件进行异步加载,节省应用程序初始化的性能。
- 结合 Vue的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
九、跳转
声明式路由跳转 ,方便实现高亮样式
编程式路由跳转 this.$router.push() / replace() / back()
-
$router
-
使用 vue-router 之后,在 vue 的原型上提供了一个 $router 对象
-
使用这个对象的一些方法来进行路由的跳转
-
this.$router.push()
- 就相当于普通的 router-link
- 它的参数可以是:
- 字符串,要跳转到路由的path
- 对象,对象中配置 path 属性。
push效果:[空白页, home,] => push(’/list’) => [空白页, home, list]
// 使用编程式导航的方式来跳转到 /home 这个路由 //1. 传字符串的形式 this.$router.push("/home"); //2. 传对象的形式 this.$router.push({ name: "abc", //先给路由取个名字 params: { id: 1, },//params不能和path一起使用,要配合name一起使用 }); //3.传对象错误的演示 //this.$router.push({ //path: "/detail", // query: { // name: "张三", // age: 18, // }, // params: { // id: 1, // }, ///params不能和path一起使用,要配合name一起使用 //});
-
this.$router.replace()
- 就相当于带有 replace 属性 的 router-link
- 它的参数可以是:
- 字符串,要跳转到路由的path
- 对象,对象中配置 path 属性。
replace效果:[空白页, home,] => replace(’/list’) => [空白页, list ]
-
this.$router.back()
后退 history.back() -
this.$router.go(num)
前进或后退,根据传递参数是正数还是负数来决定 history.go() -
this.$router.forward()
前进 history.forward()
-
-
$route 与 $router 的区别
1.$route 是当前匹配的路由信息对象,可以通过他获取一些当前路由的一些信息。比如 params
+动态参数。vue 将 $route 挂到了每个组件的 data 数据里面。
2.$router 就是 new VueRouter() 生成的那个实例对象,vue 将他挂载到了 Vue 的原型上面
+所以每个组件都通过访问,然后使用来完成编程式导航的一些操作
-
十、嵌套路由
-
嵌套路由,一般情况工作中,到两级足够了。尽量不要超过两级。
router-link的to永远写的都是完整路径(端口号后端的全部路径)
{ path: "/home", component: home, children:[ { path: "cn", component: Cn, } ] },
十一、导航守卫
一、什么叫导航
- 导航就是路由正在发生变化
二、导航守卫、路由守卫、路由的钩子
- 路由在发生变化的时候会触发的一些函数
三、守卫有哪些
一、全局守卫(通过 new VueRouter() 生成的这个实例对象去使用)
-
全局前置守卫 beforeEach
router.beforeEach((to, from, next) => { //to 代表着将要去的路由的信息对象 //from 当前导航正要离开的路由 //next 是一个函数,必须要调用 //1. next() 进入下一个 //2. next('/') next({ path: '/list' }) 重定向到某个路由 })
-
全局后置守卫 afterEach
router.afterEach((to, from) => { //to 代表着将要去的路由的信息对象 //from 当前导航正要离开的路由 // afterEach 是在导航已经完成了触发。这时有 next 也没用。 })
-
全局解析守卫 beforeResolve
router.beforeResolve((to, from, next) => { //to 代表着将要去的路由的信息对象 //from 当前导航正要离开的路由 //next 是一个函数,必须要调用 //1. next() 进入下一个 //2. next('/') next({ path: '/list' }) 重定向到某个路由 })//用的比较少
二、路由独享 (路由规则中的一个配置项)
-
beforeEnter
new VueRouter({ routes: [ { path: '/hello', component: hello, beforeEnter: (to, from, next) => { next()//next执行才可进入/hello页面 } } ] })
三、路由组件中 (在组件的配置选项中定义的,也就是与那个 data 、props 一起的那个)
- beforeRouteLeave
- beforeRouteUpdate
- beforeRouteEnter
export default {
name: "Cinemas",
beforeRouteLeave(to, from, next) {
console.log("beforeRouteLeave");
next();
},
beforeRouteUpdate(to, from, next) {
console.log("beforeRouteUpdate");
next();
},
beforeRouteEnter(to, from, next) {
console.log("beforeRouteEnter");
next();
}
};
四、详细介绍
- beforeEach
- 全局前置
- afterEach
- 全局后置
- beforeEnter
- 路由独享
- 当进入到这个路由的时候,这个函数会触发,触发时机在 beforeEach 之后。在 beforeRouteEnter 之前
- beforeRouteEnter
- 组件内配置的。触发在 beforeEnter 之后
- befoerRouteUpdate
- 在动态路由的时候,参数发生变化时,触发
- beforeRouteLeave
- 当离开时第一个触发,在 beforeEach 之前
五、思考如下的导航,导航守卫触发的顺序
一、/home -> /list
- /home 组件中的 beforeRouteLeave
- 全局前置 beforeEach
- /list 的配置中触发 beforeEnter
- /list 组件中的 beforeRouteEnter
- 全局后置 afterEach
二、/detail/1 -> /detail/2
- 全局前置 beforeEach
- detail 组件中的 beforeRouteUpdate
- 全局后置 afterEach
六、需求1: 任何页面跳转时,需要在页面上出现滚动条显示
- 在全局前置的 beforeEach 中让进度条开始滚动
- 在全局后置的 afterEach 中让进度条完成
-
借助 nprogress 插件实现
安装指令
$ npm install --save nprogress 引入他的核心 import nprogress from 'nprogress ' import 'nprogress /nprogress .css' //配置不转圈 nprogress.configure({ showSpinner:false, }) nprogress.start() //开始 nprogress.done() //结束
七、需求2: 离开个人中心页面时,需要二次确认
- 在个人中心页面的 beforeRouteLeave 中处理即可
八、需求3:必须要有登录状态才能进入个人中心页面。身份认证
方案一、
对个人中心页面配置路由独享守卫 beforeEnter ,在里面判断是否有登录
new VueRouter({
routes: [
{
path: '/hello',
component: hello,
beforeEnter: (to, from, next) => {
if(window.isLogined){
next()
}else{
next("/login")
}
}
}
]
})
- 需求增加,登录成功之后,浏览器后退不要进入登录页面
- 在登录成功跳转的时候,不要使用 push 而是使用 replace 就ok了。
- 需求增加,登录成功之后要跳转回之前想要去的页面
- 在身份验证不通过,打回到登录页面时,将当前的地址携带在url地址上,通过?号传递过去
- 登录页面登录成功之后,通过url地址获取到之前要去的页面地址。然后跳转即可
- 推荐使用 to.fullPath 而不是 to.path 好在哪里?
- 比如我们去 /my 的时候,url 路由携带有 ?号参数。/my?type=1&hello=2.
- 这时使用 to.path 只能得到 /my
- 而用 to.fullPath 能得到完整的 /my?type=1&hello=2
方案二、
给需要身份认证的页面配置路由元信息 (meta),然后在全局前置守卫 beforeE ach 中处理即可。
元信息是给某个路由配置一些额外的信息
- 当前只有一个页面需要身份验证。如果多个的时候,每一个都去写 beforeEnter 是不是不合理?
这时请使用推荐的方案二
015、Vue - vuex
一、vuex简介
- vue官方提供的状态管理器。用于处理全局的数据共享问题的。
- 采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
二、为什么要有vuex
- 项目越写越大,功能越写越复杂,当 Vue.js 应用程序遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏
三、vuex的五大核心
state: 状态
getters: 对 state 的派生,可以理解为 store 的计算属性
mutations: 修改 state 的唯一操作就是提交 mutation 。
actions: 类似于 mutations , 用来处理异步。
modules: 对仓库 分割成 模块
四、如何使用vuex
- 项目中安装vuex
$ npm install vuex
-
创建 src/store.js 文件。这个文件用来生效仓库的实例
// 1. 引入 Vue import Vue from "vue"; // 2. 引入 Vuex // vuex 完整的取出赋值给 Vuex // vuex.Store 赋值给 Store import Vuex, { Store } from "vuex"; // 3. !!进行调用!! Vue.use(Vuex); // 4. 实例化 Vuex 的一个 仓库实例对象 const store = new Vuex.Store({ // 配置选项 // 1. state state: {}, // 2. getters 里面 设置 getter getters: {}, // 3. mutations 里面 设置 mutation mutations: {}, // 4. actions 里面 设置 action actions: {}, // 5. modules 里面 设置 module modules: {}, }); // end. 不要忘了暴露 store export default store;
-
getters
getters里是以key:value形式存在
key 一个getter的名字
value 是一个函数,进行getter计算函数,与computed一样,必须有返回值,并且会自动接收到一个state这个参数,参数的内容就是当前仓库的数据
-
-
需要在 src/main.js 文件的 new Vue() 的位置去配置 store 选项,选项值是上一个步骤中暴露出来的 store 的实例
import Vue from "vue"; import App from "./App.vue"; //引入store import store from "./store"; Vue.config.productionTip = false; new Vue({ store,//挂载在Vue实例上 render: (h) => h(App), }).$mount("#app");
五、组件中如何使用state与getter
- 方案一、使用挂载到 Vue原型上的
$store
对象。这个$store
就是new Vuex.Store()
生成的仓库实例对象
$store.state
$store.getters
-
方案二 (推荐)、使用辅助函数
-
组件中从 vuex 中引入你需要的辅助函数
-
调用辅助函数,并将其返回的值赋给组件的 computed 选项
<script> // 引入 辅助函数 。采用类似 解构赋值的方式 import { mapState } from 'vuex' export default { // mapState 返回值是一个 对象 computed: mapState(['curCity', 'cart']) } </script>
当本组件中也需要computed计算属性时?
- 使用…扩展运算符赋给computed
<script> // 引入 辅助函数 。采用类似 解构赋值的方式 import { mapState } from 'vuex' export default { // mapState 返回值是一个 对象 computed: { ...mapState(['curCity', 'cart']), a:function(){ }, } } </script>
mapState的语法和mapGetters的语法的区别
// mapState 接收一个数组做为参数,参数中的每一项,就是在仓库中的 state 数据 mapState([state1, state2, state3]) // mapGetters 接收一个数组做为参数,参数中的每一项,就是在仓库中的 getter 数据 mapGetters([getter1, getter2])
-
六、组件中如何修改仓库中的state和getter数据呢?
-
state可以修改,getter不能修改
步骤:
- 在仓库中提供对应的state修改的mutation函数
- 在组件中调用mutation
-
调用mutation的方案:
-
方案一:直接使用 vuex 绑定到 vue 原型上的 $store 这个对象的 commit() 方法
在store.js文件中
mutations:{ SETCURCITY(state,payload){ state.curCity=payload } }
在组建中
methods:{ fn1(){ this.$store.commit("SETCURCITY",'修改的内容') } }
-
方案二、使用 mapMutations 辅助函数
// mapMutations的语法 // 接收一个数组作为参数,数组中的每一项是一个 mutation 的名字 mapMutations([mutation1, mutation2, mutation3])
实例代码:
{ methods: mapMutations(["SETCURCITY", "ADDCART"]) }
转换代码:
{ methods: { SETCURCITY(payload) { this.$store.commit('SETCURCITY', payload) }, ADDCART(payload) { this.$store.commit('ADDCART', payload) } } }
-
七、使用 action 异步的修改 state 数据
-
首先需要知道,mutation 里面只允许同步的去修改 state 数据。(虽然在mutation中可以异步的去修改state数据不会报错,但是会导致时间旅行等机制没有效果)
-
如果异步的修改的化,有两个大方案
-
不涉及action。在组件上异步代码走完之后再去调用 mutation
-
使用 action,使用 action。首先需要在 actions 选项中定义 action 函数
注意:action 中不能直接去修改 state,要修改是通过 context.commit() 去执行某个 mutation 来修改
方案一、直接使用 vuex 绑定到 vue 原型上的 $store 这个对象的 dispatch() 方法
在store.js文件中
actions:{ SYNCSETCURCITY({commit},payload){ setTimeout(()=>{ commit("SETCURCITY",payload) },2000) } }
在组件中
methods:{ fn1(payload){ this.$store.dispatch(actionName, payload) } }
方案二、使用 mapActions 这个辅助函数
// mapActions 的语法 // 接收一个数组作为参数,数组中的每一项是一个 action 的名字 mapActions([action1, action2, action3])
示例代码
{ methods: mapActions(["SYNCSETCURCITY"]) }
转换之后如下所示
{ methods: { SYNCSETCURCITY(payload) { this.$store.dispatch('SYNCSETCURCITY', payload) } } }
-
八、vuex的module
一、什么时候需要在 vuex 中使用 module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成****模块(module)****。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
项目越做越大,功能点越写越多。需要使用 vuex 共享的数据越来越庞大时,就需要使用 module 来进行仓库模块拆分啦。
二、示例代码
// 拆分的仓库子模块A
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... },
// 仓库子模块也可以继续去做拆分,但是没必要搞这么复杂
modules: {
aa,
ab,
}
}
// 拆分的仓库子模块B
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
// 仓库根模块
const store = new Vuex.Store({
// 通过 modules 选项配置子模块
modules: {
// key: value
// key - 仓库子模块的名字
// value - 对应的仓库子模块的对象
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
三、仓库拆分子模块之后,没有设置命名空间。有一些问题的
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的
- 多个仓库子模块中的 getter 不能同名,否则会报错
- 多个仓库子模块中的 mutation 如果同名的化,组件调用这个 mutation 时。都会被触发, 会有污染的风险
- 多个仓库子模块中的 action 如果同名的化,组件调用这个 action 时。都会被触发, 会有污染的风险
四、上面的这种问题存在,所以推荐仓库子模块都设置上命名空间。
1. 如何设置呢?
给 仓库子模块的 那个对象配置一个 namespaced 属性。属性值为 true
2. 设置之后的改变是什么?
模块内部的 action、mutation 和 getter 是注册在自己命名空间的
3. 设置了命名空间之后如何在组件中使用呢
基本使用步骤不变,只是要处理处理命名空间
下面的 xx 代表着某个仓库子模块的名字
1. 获取某个仓库子模块中的 state
// 1. 直接通过 $store
this.$store.state.xx
// 2. computed
computed: {
name () {
return this.$store.state.xx.name
}
}
// 3. mapState
computed: {
...mapState('xx', [state1, state2, state3])
}
// 4. mapState 的转换
computed: {
state1() {
return this.$store.state.xx.state1
}
}
// !如果要在组件中同时拿到多个仓库子模块的同名state数据,不要使用 mapState 请使用方案二 !
2. 获取 某个仓库子模块中的 getter
// 1. 直接通过 $store
this.$store.getters['ma/firstName']
// 2. computed
computed: {
firstName() {
return this.$store.getters['ma/firstName']
}
}
// 3. mapGetters
computed: {
...mapGetters('xx', [getter1, getter2])
}
// 4. mapGetters 的转换
compouted: {
getter1() {
return this.$store.getters['xx/getter1']
}
}
3. 提交 某个仓库子模块中的 mutation
// 1. 直接通过 $store
this.$store.commit('ma/SET_NAME', payload)
// 2. methods
methods: {
SET_NAME(payload) {
this.$store.commit('ma/SET_NAME', payload)
}
}
// 3. mapMutations
methods: {
...mapMutations('xx', [mutation1, mutation2])
}
// 4. mapMutations 的转换
methods: {
mutation1(payload) {
this.$store.commit('xx/mutation1', payload)
}
}
4. 派发 某个仓库子模块中共的 action
// 1. 直接通过 $store
this.$store.dispatch('ma/SYNC_SET_NAME', payload)
// 2. methods
methods: {
SYNC_SET_NAME(payload) {
this.$store.dispatch('ma/SYNC_SET_NAME', payload)
}
}
// 3. mapActions
methods: {
...mapActions('xx', [action1, action2])
}
// 4. mapActions 的转换
methods: {
action1(payload) {
this.$store.dispatch('xx/action1', payload)
}
}
五、仓库模块的局部状态
- 首先要知道,做了仓库模块的拆分之后,getter与mutation中的第一个参数state是当前模块的局部state。
- action中的第一个参数context。context中的state也是当前模块的局部state
- 有时候我们需要在getter中action中去获取到其余的模块的state数据
getter语法
getters:{
//state - 当前模块的state
//getters - 当前模块的getters
//rootState - 跟模块的State数据,根据他可以去获取到其余模块的state数据
getter1(state,getters,rootState){
}
}
action语法
action:{
//context是一个对象,这个对象中有一些属性
// state - 当前模块的局部state
// getters - 当前模块的getters
// commit - 提交mutation的方法
// dispatch- 派发action的方法
// rootState- 跟模块的State数据,根据他可以去获取到其余模块的state数据
action(context,payload){
}
}