Vue学习笔记
文章目录
- Vue学习笔记
- @[toc]
- 一、数据代理实现
- 二、事件相关
- 1.事件修饰符
- 2.键盘事件
- 三、计算属性与监视
- 1.计算属性-computed
- 2.监视-watch
- 四、条件渲染
- 1.v-show
- 2.v-if,v-else-if
- 五、循环遍历
- 1.v-for语法
- 2.key的作用与原理
- 六、内置指令
- 1.v-cloak指令(没有值)
- 2.v-once指令
- 3.v-pre指令
- 七、组件
- 1.为什么要使用组件
- 2.组件用法
- 2.1局部注册
- 2.2全局注册
- 3.VueComponent
- 4.ref使用
- 5.prop使用
- 单向数据流: props是单向绑定的
- 6.$parent
- 八、mixins(混入)
- 1.为什么要使用mixin
- 2.mixin用法
- 3.插件
- ==九、父子组件传值==
- 1.使用prop传值(传递一个函数)
- 2.通过给组件绑定自定义事件传值
- 3.通过ref来绑定触发事件(更灵活)
- 4.通过自组件中$parent来触发传值(不推荐)
- 5.解绑自定义事件
- 十、全局事件总线
- 1. Vue 原型对象上包含事件处理的方法
- 2.为什么要使用全局事件总线
- 3.全局事件总线使用
- 4.消息订阅与发布
- 十一、$nextTick
- 十二、动画效果
- 1.Vue的两个状态切换
- 2.触发顺序
- 3.transition 与 transition-group
- 4.引入动画库
- 十三、配置代理
- 十四、插槽
-
- ==十五、Vuex==
- vuex官方解释
- 什么时候我们该使用它
- 1.安装
- 2.配置
- ==3.核心概念==
- 3.1 State
- 3.2 Mutation
- 3.3 Action
- 3.4 Getter
- 3.5 Module
- ==十六、Vue Router==
- 1.SPA页面
- 2.准备工作
- 3.基本标签
- 3.1 **router-link**
- 3.2 **router-view**
- 3.3 **注意点**
- 4.嵌套路由
- 5.路由的query参数
- 6.命名路由
- 7.路由的param参数
- 8.路由的props配置
- 9. **< router-link >**的replace属性
- 10.编程式路由导航
- 11.缓存路由组件
- 12.路由激活与失活的生命周期钩子
- ==13.路由守卫==
- 1.全局路由守卫
- 2.路由独享守卫
- 3.组件内的路由钩子
- 14.路由器的两种工作模式
文章目录
- Vue学习笔记
- @[toc]
- 一、数据代理实现
- 二、事件相关
- 1.事件修饰符
- 2.键盘事件
- 三、计算属性与监视
- 1.计算属性-computed
- 2.监视-watch
- 四、条件渲染
- 1.v-show
- 2.v-if,v-else-if
- 五、循环遍历
- 1.v-for语法
- 2.key的作用与原理
- 六、内置指令
- 1.v-cloak指令(没有值)
- 2.v-once指令
- 3.v-pre指令
- 七、组件
- 1.为什么要使用组件
- 2.组件用法
- 2.1局部注册
- 2.2全局注册
- 3.VueComponent
- 4.ref使用
- 5.prop使用
- 单向数据流: props是单向绑定的
- 6.$parent
- 八、mixins(混入)
- 1.为什么要使用mixin
- 2.mixin用法
- 3.插件
- ==九、父子组件传值==
- 1.使用prop传值(传递一个函数)
- 2.通过给组件绑定自定义事件传值
- 3.通过ref来绑定触发事件(更灵活)
- 4.通过自组件中$parent来触发传值(不推荐)
- 5.解绑自定义事件
- 十、全局事件总线
- 1. Vue 原型对象上包含事件处理的方法
- 2.为什么要使用全局事件总线
- 3.全局事件总线使用
- 4.消息订阅与发布
- 十一、$nextTick
- 十二、动画效果
- 1.Vue的两个状态切换
- 2.触发顺序
- 3.transition 与 transition-group
- 4.引入动画库
- 十三、配置代理
- 十四、插槽
- ==十五、Vuex==
- vuex官方解释
- 什么时候我们该使用它
- 1.安装
- 2.配置
- ==3.核心概念==
- 3.1 State
- 3.2 Mutation
- 3.3 Action
- 3.4 Getter
- 3.5 Module
- ==十六、Vue Router==
- 1.SPA页面
- 2.准备工作
- 3.基本标签
- 3.1 **router-link**
- 3.2 **router-view**
- 3.3 **注意点**
- 4.嵌套路由
- 5.路由的query参数
- 6.命名路由
- 7.路由的param参数
- 8.路由的props配置
- 9. **< router-link >**的replace属性
- 10.编程式路由导航
- 11.缓存路由组件
- 12.路由激活与失活的生命周期钩子
- ==13.路由守卫==
- 1.全局路由守卫
- 2.路由独享守卫
- 3.组件内的路由钩子
- 14.路由器的两种工作模式
一、数据代理实现
let app = new Vue({
el: "#app",
data: {
name: '张三',
sex: "男"
}
我们new出来的vue对象,他的data对象中一般存放我们需要绑定的值,而这些值我们可以在html中利用 {{name}}
的方式打印出来,他的实现过程如图
步骤大概如下,我们再data里面存放的数据,vue会将它存放在vue实例的_data属性下,同时利用 Object.defineProperty
设置了get和set方法。
在控制台中可以利用===可以看出他们的对象是相同的
app._data.name === app.name
true
那为什么还要在对象外再挂一层对象呢?
其实就是为了编程的方便,在外面挂载了对象,我们在代码里面就可以直接写{{name}}就可以访问到了,而不需要去写{{_data.name}},其实他们的结果是一样的。
二、事件相关
1.事件修饰符
Vue中的事件修饰符:
- 1.prevent: 阻止默认事件(常用)
- 2.stop: 阻止事件冒泡(常用)
- 3.once: 事件只触发一次(常用)
- 4.capture: 使用事件的捕获模式
- 5.self: 只有event.target是当前操作元素才触发事件
- 6.passive: 事件的默认行为立即执行,无需等待事件回调
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../js/vue.min.js"></script>
<style>
* {
margin: 20px;
}
.wai {
width: 300px;
height: 200x;
background-color: blue;
display: inline-block;
}
.nei {
width: 100px;
height: 100px;
background-color: pink;
}
.ulClass{
display: inline-block;
width: 80px;
height: 150px;
overflow-y: auto;
background-color: bisque;
}
.ulClass>li{
height: 50px;
margin: 0px;
}
</style>
</head>
<body>
<div>
Vue中的事件修饰符:
<li>1.prevent: 阻止默认事件(常用)</li>
<li>2.stop: 阻止事件冒泡(常用)</li>
<li>3.once: 事件只触发一次(常用)</li>
<li>4.capture: 使用事件的捕获模式</li>
<li>5.self: 只有event.target是当前操作元素才触发事件</li>
<li>6.passive: 事件的默认行为立即执行,无需等待事件回调</li>
</div>
<div id="app">
<h2>欢迎来到{{name}}学习</h2>
<div>
<h3>1.阻止默认事件</h3>
<a href="../01_数据代理/1.回顾Object.defineProperty方法.html" @click="fun1">一个连接</a><br>
<a href="../01_数据代理/1.回顾Object.defineProperty方法.html" @click.prevent="fun1">一个连接,加.prevent</a>
<li>a标签它的默认行为就是点击后,会跳转到href的网址,如果你在Click后面增加Prevent的修饰符,它就会取消去跳转的行为</li>
</div>
<div>
<h3>2.阻止事件冒泡(常用)</h3>
<div class="wai" @click = "fun('外部')">外部
<div class="nei" @click = "fun('内部')">内部</div>
</div>
<div class="wai" @click = "fun('外部')">外部内部阻止冒泡
<div class="nei" @click.stop = "fun('内部')">内部,增加stop</div>
</div>
<li>stop就是用来阻止事件冒泡,阻止它的事件从内而外传播</li>
</div>
<div>
<h3>3.事件只触发一次(常用)</h3>
<button @click="fun1">按钮</button><br>
<button @click.once="fun1">增加了once修饰的按钮</button>
<li>once会让事件只会触发一次,触发过后就不会再被触发</li>
</div>
<div>
<h3>4.capture: 使用事件的捕获模式</h3>
<div class="wai" @click = "fun('外部')">外部
<div class="nei" @click = "fun('内部')">内部</div>
</div>点击内部 触发内部->外部
<div class="wai" @click.capture = "fun('外部')">外部使用事件捕获触发
<div class="nei" @click = "fun('内部')">内部</div>
</div> 点击内部 触发外部->内部
<li>事件的触发机制,先是捕获阶段,捕获阶段是从外到内,然后开始事件冒泡,事件冒泡是从内而外,用了capture修饰符可以让绑定的事件在捕获阶段就触发</li>
</div>
<div>
<h3>5.self: 只有event.target是当前操作元素才触发事件</h3>
<div class="wai" @click = "fun('外部')">外部
<div class="nei" @click = "fun('内部')">内部</div>
</div>
<div class="wai" @click.self = "fun('外部')">外部使用事件捕获触发
<div class="nei" @click = "fun('内部')">内部</div>
</div>
<li>因为事件冒泡,点击内部也会触发外部,但是触发的时候带着event.target,如果是冒泡到外层,e.target==内层元素,加了self的话,只能当e.target等于自身方法才会触发,也可以阻止事件传播(外层阻止)</li>
</div>
<div>
<h3>6.passive: 事件的默认行为立即执行,无需等待事件回调</h3>
<h4>scroll是给滚动条加事件,wheel是给是给鼠标滚轮加事件</h4>
<ul @wheel="fun2" class="ulClass">
<li v-for="item in 10">{{"序号"+item}}</li>
</ul>
<ul @wheel.passive="fun2" class="ulClass">
<li v-for="item in 10">{{"序号"+item}}</li>
</ul>
<ul @scroll="fun2" class="ulClass">
<li v-for="item in 10">{{"序号"+item}}</li>
</ul>
<ul @scroll.passive="fun2" class="ulClass">
<li v-for="item in 10">{{"序号"+item}}</li>
</ul>
<li>我们如果绑定的是滚轮事件的话,因为绑定的函数十分耗时,他会在执行完函数后再执行页面的滚动效果,从而给人非常卡的感觉,
但如果使用了passive的话,它会优先执行默认行为,不需要等待事件执行完成
</li>
</div>
</div>
</body>
<script type="text/javascript">
let app = new Vue({
el: "#app",
data: {
name: "事件修饰符"
},
methods: {
fun1() {
alert("事件被触发")
console.log("事件被触发");
},
fun(msg) {
alert(msg)
console.log("事件被触发");
},
fun2() {
for(var i = 0 ;i<100000;i++){
console.log(i);
}
},
},
})
</script>
</html>
2.键盘事件
- Vue中的常用按键别名
- 回车 => enter
- 删除 => delete(捕获删除和空格键)
- 退出 => esc
- 空格 => space
- 换行 => tab(特殊,必须配合其他案件)
- 上 => up
- 下 => down
- 左 => left
- 右 => right
- Vue未提供别名的按键可以使用按钮原始的key值去绑定,但是要注意多个单词连接 如 caps-lock(短横行命名)e.key是按下的按键名,e.keyCode是按下按键的Code
- 系统修饰键(用法特殊):ctrl、alt、shift、meta
- 配合keyup使用:按下修饰键同时,再按下其他键,随后释放其他键,事件才会被触发(配合的键不会被输入)
- 配合keydown使用,正常触发事件
- 也可以使用keyCode去指定具体的按键(不推荐MDN后续废除) @keyup.13
- Vue.config.keyCodes.自定义按键名 = 键码,可以定制按键别名,如:
Vue.config.keyCodes.huiche = 13;
@keyup.huiche=“fun”
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../js/vue.min.js"></script>
</head>
<body>
<div id="app">
<h1>{{name}}</h1>
<input type="text" placeholder="按下回车提示输入" @keyup.huiche="fun" />
<ol>
<li>Vue中的常用按键别名
<ol>
<li>回车 => enter</li>
<li>删除 => delete(捕获删除和空格键)</li>
<li>退出 => esc</li>
<li>空格 => space</li>
<li>换行 => tab(特殊,必须配合其他案件)</li>
<li>上 => up</li>
<li>下 => down</li>
<li>左 => left</li>
<li>右 => right</li>
</ol>
</li>
<li>Vue未提供别名的按键可以使用按钮原始的key值去绑定,但是要注意多个单词连接 如 caps-lock(短横行命名)e.key是按下的按键名,e.keyCode是按下按键的Code</li>
<li>系统修饰键(用法特殊):ctrl、alt、shift、meta
<ol>
<li>配合keyup使用:按下修饰键同时,再按下其他键,随后释放其他键,事件才会被触发(配合的键不会被输入)</li>
<li>配合keydown使用,正常触发事件</li>
</ol>
</li>
<li>也可以使用keyCode去指定具体的按键(不推荐MDN后续废除) @keyup.13</li>
<li>Vue.config.keyCodes.自定义按键名 = 键码,可以定制按键别名,如:<br>
Vue.config.keyCodes.huiche = 13;<br>
@keyup.huiche="fun"
</li>
</ol>
</div>
</body>
<script type="text/javascript">
Vue.config.keyCodes.huiche = 13;
let app = new Vue({
el: "#app",
data: {
name: "键盘事件"
},
methods: {
fun(e) {
console.log(e.key, e.keyCode);
console.log(e.target.value);
}
},
})
</script>
</html>
小技巧:事件修饰符和键盘事件可以可以连写,如@keyup.ctrl.y
(按下ctrl+y才触发)或@click.stop.prevent
(阻止默认行为也阻止事件冒泡)
三、计算属性与监视
1.计算属性-computed
-
要显示的数据不存在,要通过计算得来。
-
在 computed 对象中定义计算属性。
-
在页面中使用{{方法名}}来显示计算的结果。
- 原理:底层还是借助了Object。defineproperty方法提供getter和setter
- get函数什么时候执行
- 最开始初始化的时候读取一次
- 当依赖的数据发生了改变的时候,会被再次调用
- 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
- 备注:
- 计算属性最后会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,切set中要引起依赖数据发生改动。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="../js/vue.js"></script> <style> </style> </head> <body> <div id="app"> <h2>欢迎来到{{name}}学习</h2> <div> <label>姓:</label> <input type="text" v-model="xi"> <br> <label>名:</label> <input type="text" v-model="ming"> <br> 全名:{{xi+'.'+ming}} <br> 全名使用computed:{{xingming}} </div> </div> </body> <script type="text/javascript"> let app = new Vue({ el: "#app", data: { name: "计算属性computed", xi: "", ming: "" }, computed: { //完整写法 xingming:{ get(){ return this.xi + '.' + this.ming; }, set(value){ const arr = value.split("."); this.xi = arr[0]; this.ming = arr[1]; } } //简写 如果只考虑属性读取的话可以这样写 // xingming() { // return this.xi + '.' + this.ming; // }, }, methods: { }, }) </script> </html>
2.监视-watch
- 通过通过 vm 对象的$watch()或 watch 配置来监视指定的属性
- 当属性变化时, 回调函数自动调用, 在函数内部进行计算
- 默认是只监听一层的,可以配置deep来实现深层监视
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../js/vue.js"></script>
<style>
</style>
</head>
<body>
<div id="app">
<h2>欢迎来到{{name}}学习</h2>
<h3>今天天气很{{ganjue}}</h3>
<br>
<h3>(使用watch)今天天气很{{ganjue}}</h3>
<br>
<button @click="changeWatch">点击</button>
<hr>
<h3>a的值是{{numbers.a}}</h3>
<button @click="numbers.a++">a的值+1</button>
<h3>b的值是{{numbers.b}}</h3>
<button @click="numbers.b++">b的值+1</button>
</div>
</body>
<script type="text/javascript">
let app = new Vue({
el: "#app",
data: {
name: "监视属性Watch",
isHot: true,
numbers:{
a:1,
b:1,
}
},
computed: {
ganjue() {
return this.isHot ? "炎热" : "凉爽";
}
},
watch: {
//完整写法
// ganjue:{
// immediate:true,//在初始化的时候就会调用一次
// handler(newD,old){
// console.log("ganjue发生了改变",newD,old);
// }
// },
//简单写法
// isHot(newD,old){
// console.log("isHot发生了改变",old,newD);
// }
numbers:{
deep:true,//深度监视
handler(newD,old){
console.log("numbers发生了改变",newD,old);
}
},
// "numbers.a"(newD,old){
// console.log("numbers.a发生了改变",newD,old);
// }
},
methods: {
changeWatch() {
this.isHot = !this.isHot;
}
},
})
//这种方式适合需要后续绑定的情况
app.$watch("ganjue", {
immediate: true,//在初始化的时候就会调用一次
handler(newD, old) {
console.log("ganjue发生了改变", newD, old);
}
})
</script>
</html>
总结:
watch看上去比computed要麻烦,但是watch里面可以做一些异步操作
四、条件渲染
1.v-show
v-show后面跟上条件表达式,它控制的是节点的显示和隐藏,是可以有缓存值的,不会移除节点,适合做频繁的显隐藏操作
2.v-if,v-else-if
v-if是真正的显示或移除,适合不频繁操作。
五、循环遍历
1.v-for语法
V-for循环遍历数组时推荐使用of,语法格式为(item,index)
- item:迭代时不同的数组元素的值
- index:当前元素的索引
V-for循环遍历对象时推荐使用in,语法格式为(item,name,index)
- item:迭代时对象的键名键值
- name:迭代时对象的键名
- index:当前元素的索引
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../js/vue.js"></script>
<style>
</style>
</head>
<body>
<div id="app">
<h2>欢迎来到{{name}}学习</h2>
<h2>遍历数组</h2>
<ul>
<li v-for = "(item,index) in personList" :key="index">
{{item.name+' '+item.age}}
</li>
</ul>
<h2>遍历对象</h2>
<ul>
<li v-for = "(item,name,index) in car" :key="index">
{{item+' '+name+' '+index}}
</li>
</ul>
<h2>遍历字符串</h2>
<ul>
<li v-for = "(item,index) in 'abcd'" :key="index">
{{item+' '+index}}
</li>
</ul>
<h2>遍历数字</h2>
<ul>
<li v-for = "(item,index) in 5" :key="index">
{{item+' '+index}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
let app = new Vue({
el: "#app",
data:{
name:"v-for",
personList:[
{id:1,name:"小明",age:19},
{id:2,name:"小红",age:20},
{id:3,name:"小张",age:21},
],
car:{
name:"奥迪A8",
price:"1000万",
remark:"四驱"
}
}
})
</script>
</html>
2.key的作用与原理
- key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据[新数据]生成[新的虚拟DOM],随后Vue进行[新虚拟DOM]与[旧虚拟DOM]的差异比较
- 对比规则
旧虚拟DOM中找到了新虚拟DOM相同的key:
- 若虚拟DOM中内容没变,直接使用之前的的真实DOM!
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面之前的真实DOM.
旧虚拟DOM未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到页面
用index作为key可能会引发的问题:
- 若对数据进行逆序添加,逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新==>界面效果没有问题,但效率低
- 如果结构中还包含输入类DOM,会产生错误DOM更新==>界面有问题
-
key比较原理图
-
开发中如何选择key
- 最好使用每条数据的唯一标识作为key.比如id,手机号,身份证号,学号等唯一值
- 如果不存在对数据的逆序添加,逆序删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index是作为key是没有问题的.
六、内置指令
1.v-cloak指令(没有值)
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
- 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题
2.v-once指令
- v-once所在的节点初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
3.v-pre指令
- 跳过其所在节点的编译过程
- 可以利用它跳过没有使用指令的语法,没有使用插值语法的节点,加快编译
七、组件
1.为什么要使用组件
vue组件的作用是提高重用性,让代码可复用
2.组件用法
组件需要注册才能使用。组件有全局注册和局部注册两种方式。
2.1局部注册
const student = Vue.extend({
template: `
<div>
<h2>学生名称:{{ studentName }}</h2>
<h2>学生年龄:{{ age }}</h2>
</div>
`,
data() {
/**
* 使用return的方式是为了重复使用的时候,指向的都是一个全新数据,否则会有依赖关系
*/
return {
studentName: "苏州大学",
age: 18
}
}
})
let app = new Vue({
el: "#app",
components:{student}, //局部注册
data: {
name: "组件"
}
})
2.2全局注册
//全局注册
const hello = Vue.extend({
template: `
<div>
<h2>你好啊!{{ Name }}</h2>
</div>
`,
data() {
/**
* 使用return的方式是为了重复使用的时候,指向的都是一个全新数据,否则会有依赖关系
*/
return {
Name: "奥特曼",
}
}
})
Vue.component("hello",hello);
注册局部组件,该组件只能再该实例作用域下有效;全局注册组件,在任何Vue实例都可以使用。
**注意:**若局部注册的组件名和全局注册的组件名重复,局部注册的组件会覆盖全局注册的组件
Vue组件的模板在某些情况下会受到HTML的限制,比如
内规定只允许、、 | 等这些表格元素,所以在内直接使用组件是无效的。这种情况下,可以使用特殊的is属性来挂载组件。示例代码:
|
---|
<div id="app">
<table>
<tbody is="my-component"></tbody>
</table>
</div>
<script>
Vue.component('my-component', {
template: '<div>这里是组件的内容</div>'
});
var app = new Vue({
el: '#app'
})
</script>
tbody在渲染时,会被替换为组件的内容,常见的限制元素还有
-
、
- 、。
提示:如果使用的是字符串模板,是不受限制的。
除了template选项外,组件中还可以像Vue实例那样使用其他选项,比如data、computed、methods等。但是在使用data时,和实例稍有区别,data必须时函数,然后将数据return出去
3.VueComponent
-
组件的本质是一个名为VueComponent的构造函数,并且不是程序员定义的,而是Vue.extend函数生成的。
并且每次生成的都是不一样的VueComponent的构造函数
-
每当我们使用组件标签(写组件标签时,),vue解析到组件标签时,会帮我们使用VueComponent构造函数创建一个VueComponent对象,帮我们执行 new VueComponent(options)
-
在组件配置中:
data函数,methods中配置的函数,watch中配置的函数,computed中配置的函数的this指向的都是VueComponent组件对象。
在vue实例配置中:
data函数,methods中配置的函数,watch中配置的函数,computed中配置的函数的this指向的都是vue对象。 -
一个重要的内置关系:
VueComponent.prototype.proto===Vue.prototype
这么做是为了让组件实例对象vc可以访问到Vue原型上的属性方法,已达到复用,因为Vue和VueComponent在很大程度上都是相同的(95%),所以像 m o u n t 和 mount和 mount和watch方法,定义在Vue的原型对象上,然后VueComponent的原型对象的原型对象指向Vue的原型对象,VueComponent和Vue的实例就可以使用同一份方法和属性,而不用写两份一样的。
4.ref使用
大家在使用原生JS对DOM进行操作时肯定第一步是需要获取DOM元素的,比如通过id获取document.getElementById(“idName"),或者使用jQuery获取 jQuery对象$("#idName”),vue对此也实现了比较方便的获取操作DOM的用法 — ref属性。
语法:
//在需要ref管理的组件上 增加ref标签
<hello ref="hello"></hello>
<school ref="school"></school>
//获取对应dom(普通标签获取的是dom)
//vueComponent(组件上获取的vc对象)
this.$refs.school
//可以修改子组件的值
this.$refs.school.schoolName = 2
5.prop使用
props
是子组件访问父组件数据的唯一接口。一个组件可以直接在模板里面渲染data里面的数据(双花括号)。
子组件不能直接在模板里面渲染父元素的数据。
如果子组件想要引用父元素的数据,那么就在prop里面声明一个变量(比如a),这个变量就可以引用父元素的数据。然后在模板里渲染这个变量(前面的a),这时候渲染出来的就是父元素里面的数据。
语法:
// 1.在需要传入值的组件上 设置需要传入的prop
<school name="苏州大学" addres="虎丘区" :agenum="agenum"></school>
// 2.在组件中接收对应的prop(三种写法)
//第一种 prop写法 ,简单 无限制
// props: ["name", "addres", "agenum"]
//第二种 prop写法 ,对类型进行限制
// props:{
// name:String,
// addres:String,
// agenum:Number,
// }
//第三种 prop写法 ,对类型进行限制,同时对是否可传,默认值进行限制
props: {
name: {
type: String,
require: true,//必要选项
},
agenum: {
type: Number,
default: 0,//默认值
},
addres: {
type: String,
required: true
}
}
单向数据流: props是单向绑定的
当父组件的属性变化时,将传导给子组件,但是反过来不会。
每次父组件更新时,子组件的所有 prop 都会更新为最新值。
不要在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。
在两种情况下,我们很容易忍不住想去修改 prop 中数据:
- Prop 作为初始值传入后,子组件想把它当作局部数据来用;
- Prop 作为原始数据传入,由子组件处理成其它数据输出。
对这两种情况,正确的应对方式是:
1.定义一个局部变量,并用 prop 的值初始化它:(解决子组件想用数据)
data() {
return {
//prop的值最好是不修改的,可以在data中利用其他名称获取这个值
schoolName: this.name,
address: this.addres,
}
},
props: ["name", "addres", "agenum"]
2.定义一个计算属性,处理 prop 的值返回:(解决传入数据,处理后输出)
computed: {
age() {
return this["agenum"];
}
},
6.$parent
$parent 也可以用来访问父组件的数据。
而且子组件可以通过$parent 来直接修改父组件的数据,不会报错!
可以使用props的时候,尽量使用props显式地传递数据(可以很清楚很快速地看出子组件引用了父组件的哪些数据)。
另外在一方面,直接在子组件中修改父组件的数据是很糟糕的做法,props单向数据流就没有这种顾虑了。
八、mixins(混入)
1.为什么要使用mixin
Mixin
提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项(如data、methods、mounted等等)。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项
2.mixin用法
2.1定义一个mixin
export default {
data(){
return {
mxData1:"mixin"
}
},
methods:{
funMixin(){
console.log('@@Mixins')
}
},
created() {
this.funMixin()
}
}
2.2 在需要引入的vue组件中引用 (局部引入)
import Mixin from "@/mixins/mixin.js"
export default {
name: "PropComp",
mixins:[Mixin],
}
2.3 全局混入
混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。使用恰当时,这可以用来为自定义选项注入处理逻辑。
在**main.js**中通过Vue.mixin()引入混入对象即可全局使用(作用于该Vue实例下的所有组件)
import Mixin from "@/mixins/mixin";
Vue.mixin(Mixin)
注意:同名的生命周期钩子进行了合并,合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
3.插件
vue插件的作用:主要是用于增强功能,可以把他看作是一个工具库,可以提供很多强大的功能,比如一些强大的自定义指令,一些强大的工具方法,过滤器等。
插件是一种能为Vue添加全局功能的工具代码。
一个插件可以是一个拥有install方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装他的应用实例和传递给**app.use()**的额外选项作为参数
install方法的第一个参数是Vue构造函数,第二个参数及其以后的参数是插件使用者传递的数据
定义插件
import Mixin from "@/mixins/mixin";
export default {
install(Vue){
console.log("@@install",Vue)
//Vue是当前Vue的一个构造函数,我们可以在这里写一些功能增强,例如将一些东西挂载到Vue上
//定义全局混入
Vue.mixin(Mixin)
//定义全局过滤器
Vue.filter("mySlice",(value)=>{
return value.slice(0,4)+"|过滤器生效"
})
//定义全局指令
Vue.directive("fbind",{
bind(element,binding){
console.log("一上来")
element.value = binding.value ;
console.log(element)
},
//指令所在元素被插入页面时
inserted(element,binding){
console.log("插入页面")
element.focus();
console.log(element)
},
//指令所在的模板被重新解析
update(element,binding){
console.log("重新解析")
element.vaule = binding.value;
console.log(element.vaule,binding.value)
}
})
//Vue原型上增加一个方法
Vue.prototype.hello=()=>{
console.log("全局方法,hello")
}
}
}
使用插件 Vue.use(plugins);
import plugins from "@/plugins/plugins";
Vue.use(plugins);
九、父子组件传值
vue中的父子组件传值,值得注意的是要遵守单向数据流原则。所谓单向数据流原则,简单的说就是父组件的数据可以传递给子组件,子组件也可以正常获取并使用由父组件传过来的数据;但是,子组件中不能直接修改父组件传过来的数据,必须要向父组件传递一个事件来父组件需要修改数据,即通过子组件的操作,在父组件中修改数据;这就是
单项数据流
。
首先,需要构建一个组件的父子关系
父组件 app.vue
<template>
<div id="app">
<School/>
</div>
</template>
<script>
import School from "@/components/School.vue";
export default {
name: 'App',
components: {
School
},
methods: {
getSchoolName(value) {
console.log("获取方法被触发了", value)
}
}
}
</script>
<style>
</style>
子组件
<template>
<div>
<h2>学校名称:{{ schoolName }}</h2>
<h2>学校地址:{{ address }}</h2>
<h2>校龄:{{ age }}</h2>
<button>组件内向外传递schoolName</button>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
schoolName: "苏州大学",
address: "苏州工业园区",
age: 22
}
},
watch: {
},
computed: {},
methods: {
},
}
</script>
<style scoped>
</style>
1.使用prop传值(传递一个函数)
父组件利用prop传入一个修改自己data中值的方法
父组件
//给子组件prop传值,传递一个update-prent-data的值,在父组件中定义为一个修改函数
<school ref="school" :agenum="agenum" :update-prent-data="updatePrentData"></school>
//传入的方法
updatePrentData(value) {
console.log('传入进去的方法被调用', value)
this.agenum = value //修改的this是父组件
}
子组件
//1.接受prop传过来的值
props: {
agenum: {
type: Number,
default: 0,//默认值
},
updatePrentData: {
type: Function,//传递的方法
required: true
}
}
//2.设置需要修改值的时机(我这里就设置的点击触发)
<button @click="fun">组件内按钮 +1</button>
//绑定的函数
fun() {
this.age++;
//在这里回调传入进来的方法,就可以触发了
this.updatePrentData(this.age)
}
2.通过给组件绑定自定义事件传值
父组件给子组件绑定一个自定义事件,传入一个修改函数,子组件触发这个事件的时候,把值传出来
父组件
//绑定自定义事件(这两种方式都可以)
<school @actfun="getSchoolName"></school>
<school v-on:actfun="getSchoolName"></school>
//传入的函数
getSchoolName(value){
console.log("获取方法被触发了",value)
}
子组件
//1.还是定义一个按钮触发传值
<button @click="sendSchoolName">组件内向外传递schoolName</button>
//2.绑定的事件
sendSchoolName(){
//通过this.$emit触发自定义事件,后面可以传入参数,也可以实现子向父传值
this.$emit("actfun",this.schoolName);
}
3.通过ref来绑定触发事件(更灵活)
与设置自定义事件类似,父组件给子组件设置一个ref,后续利用ref绑定一个事件,传入一个修改函数,子组件触发这个事件的时候,把值传出来
父组件
this.$refs.绑定的ref值.$on("绑定的事件名",需要绑定的触发函数);
$once可以绑定一次性事件
//设置一个ref
<school ref="school"></school>
//可以在mounted函数中绑定事件(这种方式更灵活)
mounted(){
//这种方式更加灵活,如等待某个请求返回后再绑定事件
// this.$refs.school.$on("actfun",this.getSchoolName); //绑定自定义事件
this.$refs.school.$once("actfun",this.getSchoolName); //绑定一次性的自定义事件
//可以延时绑定
// setTimeout(()=>{
// console.log("事件绑定了")
// this.$refs.school.$on("actfun",this.getSchoolName);
// },3000)
}
子组件
//1.还是定义一个按钮触发传值
<button @click="sendSchoolName">组件内向外传递schoolName</button>
//2.绑定的事件
sendSchoolName(){
//通过this.$emit触发自定义事件,后面可以传入参数,也可以实现子向父传值
this.$emit("actfun",this.schoolName);
}
4.通过自组件中$parent来触发传值(不推荐)
这种方式采用的是双向数据流
- 父组件访问子组件:使用
$children
- 子组件访问父组件:使用
$parent
子组件修改父组件值 this.$parent.可以访问到父组件中的方法和值
//1.还是定义一个按钮触发传值
<button @click="sendSchoolName">组件内向外传递schoolName</button>
//2.绑定的事件
sendSchoolName(){
this.$parent.getSchoolName(this.schoolName)
}
5.解绑自定义事件
有一些事件,我们使用完后,需要解绑自定的事件,我们可以使用
this.$off()
用法
//解绑一个自定义事件
this.$off("get-school-name")
//解绑多个自定义事件
this.$off(["get-school-name","getSchoolName"])
// 解绑所有自定义事件
// this.$off();
使用
//1.定义一个按钮触发解绑
<button @click="unBind"> 子组件解除绑定的自定义事件</button>
//2.绑定的函数
unBind(){
console.log("子解绑事件触发")
this.$off("get-school-name") //解绑一个自定义事件
}
十、全局事件总线
1. Vue 原型对象上包含事件处理的方法
- $on(eventName, listener): 绑定自定义事件监听
- $emit(eventName, data): 分发自定义事件
- $off(eventName): 解绑自定义事件监听
- $once(eventName, listener): 绑定事件监听, 但只能处理一次
2.为什么要使用全局事件总线
原因:我们项目中的任意两个组件之间需要传递数据,做一些联动处理
3.全局事件总线使用
-
1.要让该对象在所有组件中都能被访问到,我们将它挂载在Vue对象上
-
2.这个对象必须要拥有Vue原型上所有的事件处理方法
Vue.prototype.bus = new Vue()
-
3.实现
-
3.1 在main.js中指定总线对象
new Vue({ render: h => h(App), beforeCreate() {//将app实例在创建前挂载到Vue的prototype上 Vue.prototype.$bus = this } }).$mount('#app')
-
3.2 绑定事件
在需要改变值的组件里面绑定,传入的是事件触发后的回调函数
this.定义的总线名称.$on("自定义事件名",事件触发后的回调函数)
this.$bus.$on("deleteTodo",this.getSchoolName)
-
3.3 触发事件
在需要修改绑定事件组件值的地方调用
this.定义的总线名称.$emit("需要触发的事件名", 回调函数的参数)
this.$bus.$emit("deleteTodo", this.compData)
-
3.4 解绑事件
因为这是一个全局的对象,我们尽量不要什么方法都放上去,会导致对象过大,所以在我们绑定事件的组件被销毁的时候,我们可以解绑当前绑定的事件
//事件解绑,必须有值在里面,否则将清除所有的事件 this.$bus.$off("deleteTodo")
最好在beforeDestroy钩子中,用$off去解绑当前组件用到的事件
beforeDestroy() { this.$bus.$off("deleteTodo") }
-
4.消息订阅与发布
与总线有类似的地方,但是这里是直接引入第三方包
这是一种组件间通信的方式,适用于任意组件间通信
使用:
-
安装pubsub:
npm i pubsub-js
-
引入:
import PubSub from 'pubsub-js'
-
接收数据:A组件想接收数据,则应该在A组件中订阅消息,订阅的回调函数留在A组件自身
PubSub.publish("订阅的名称",收到信息的回调)
注意:回调函数第一个参数为消息名称,后面才是数据
fun3(){ console.log("开始订阅事件fun") ; //这个pid要留下来,后续解绑需要使用 this.pid = PubSub.subscribe("psFun",this.psFun) },
-
发布端:提供数据
PubSub.publish("发布的名称",需要传入的函数参数)
PubSub.publish("psFun",this.compData)
-
在使用完毕后,需要取消订阅,可以在beforeDestroy钩子中
PubSub.unsubscribe(订阅时的Pid)
注意:这种方式不能重复订阅,重复订阅,后面的回调会被调用多次,最好可以放在钩子函数中,只触发一次
十一、$nextTick
1.语法:this.$nextTick(回调函数)
2.作用:在下一次DOM更新结束后调用其指定的回调
3.什么时候使用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所制定的回调函数中执行
十二、动画效果
Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果”,即Vue的动画是作用是让元素在改变前添加一些过度效果。这样可以让用户在视觉和交互上有更好的体验
1.Vue的两个状态切换
2.触发顺序
对于v-show
来说,当他的条件由false转为true为进入(v-if
也一样),true转为false为离开
对于进入触发的css类为(默认):v-enter
、v-enter-to
、v-enter-active
,其中v-enter-active
是包括了v-enter
、v-enter-to
两个过程,一般用于设置过度时间。
对于退出触发的css类为(默认):v-leave
、v-leave-to
、v-leave-active
,其中v-leave-active
是包括了v-leave、v-leave-to
两个过程,一般用于设置过度时间。
3.transition 与 transition-group
Vue动画为我们提供了一个<transition></transition>
、<transition-group></transition-group>
组件用于包裹要添加过度动画的元素和一套规则。标签、动画进和出的两个过程及对应其动画类名的定义
/* 进入的起点、离开的终点 */
.hello-enter {
transform: translate(-100%, -100%);
}
.hello-leave-to {
transform: translate(100%, -100%);
}
.hello-enter-active, .hello-leave-active {
transition: 0.3s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to, .hello-leave {
transform: translate(0%, 0%);
}
标签部分
<button @click="flag1=!flag1">修改flag1</button>
<button @click="flag2=!flag2">修改flag2</button>
<transition name="hello" appear>
<CompB v-show="flag1"></CompB>
</transition>
<transition name="hello" appear>
<CompC v-show="flag2"></CompC>
</transition>
<transition-group name="hello" appear>
<CompB v-show="flag1" :key="1"></CompB>
<CompC v-show="flag2" :key="2"></CompC>
</transition-group>
效果
4.引入动画库
Animate.css
安装:npm install animate.css --save
引入:import 'animate.css';
使用:
Animate官网
可以在官网上选择需要的动画效果
//enter-active-class="animate__bounceIn" 进入动画
//leave-active-class="animate__bounceOut" 离开动画
<transition name="animate__animated animate__bounce" appear
enter-active-class="animate__bounceIn"
leave-active-class="animate__bounceOut"
>
<CompB v-show="flag1"></CompB>
</transition>
<transition name="animate__animated animate__bounce" appear
enter-active-class="animate__flip"
leave-active-class="animate__flipOutY">
<CompC v-show="flag2"></CompC>
</transition>
十三、配置代理
当我们使用axios来发起请求的时候,因为不是同源
同源:协议名一致,ip一致,端口号一致
所以请求会报跨域错误,这个我们可以在中间设立Nginx服务器,也可以利用vue-cli中的代理服务配置来跨域通信。
错误截图:
使用:
-
修改根目录下的vue.config.js
devServer: { host: '0.0.0.0', // 当前vue项目运行的端口 port: 5009, // proxy:"http://localhost:8082" //代理简单写法 proxy: { '/redisDemo': {//前缀 target: 'http://localhost:8082', // 后台接口地址 ws: false, //如果要代理 websockets,配置这个参数 secure: false, // 如果是https接口,需要配置这个参数 changeOrigin: true, //用于控制请求中的host值,是否改变成你代理的host,可以用来跨域 // pathRewrite:{ // 重写路径,可以请求转发的时候消除前缀 // '^/redisDemo':'' // } } } },
vue项目里,我的所有接口的地址都是/redisDemo开头的,所以前面用 ‘/redisDemo’
例如:我的请求地址 /redisDemo/xxxx/xxx
当node服务器 遇到 以 ‘/redisDemo’ 开头的请求,就会把 target 字段里的值加上,那么请求地址就为变成了http://localhost:8082/redisDemo/xxxx/xxxpathRewrite
: 简单来说,pathRewrite是使用proxy进行代理时,对请求路径进行重定向以匹配到正确的请求地址,例如我的请求http://localhost:8082/redisDemo/test/add ,如果配置了pathRewrite中的
'^/redisDemo':''
那么请求地址就会被转发到http://localhost:8082/test/add 这个地址上 -
配置后重新启动项目,可以看到请求已经可以正常访问。
-
如果有多个地址需要代理,可以再增加
devServer: { host: '0.0.0.0', // 生产环境端口 port: 5009, // proxy:"http://localhost:8082" proxy: { '/redisDemo': { //前缀 target: 'http://localhost:8082', // 后台接口地址 ws: false, secure: false, changeOrigin: true, }, '/appDemo': { target: 'http://localhost:8084', // 后台接口地址 ws: false, secure: false, changeOrigin: true, } } },
缺点:因为这是一个代理服务器,他首先拿到请求会去本地的pulic文件夹中找一下,如果pulic文件夹下有的话,他就不再转发给后台服务器了。(可以通过加前缀的方式解决)
十四、插槽
作用:插槽
是在子组件中某个位置插入父组件的自定义html结构和data数据
1.默认插槽
定义:将父组件的自定义html和data插入子组件的默认对应位置
特点: 父组件决定结构和数据
父组件
<template>
<div class="ccc">
<category title="美食">
<img src="https://img1.baidu.com/it/u=319510654,3848329935&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800">
</category>
<category title="游戏">
<ul>
<li v-for="(item,index) in gameList" :key="index">{{ item }}</li>
</ul>
</category>
<category title="电影">
<slot><video controls src="/aa.mov"></video></slot>
</category>
</div>
</template>
<script>
import Category from "@/components/slot/Category.vue";
export default {
name: "CategoryMain",
components: {Category},
data() {
return {
gameList: ["英雄联盟", "守望先锋", "红警"]
}
}
}
</script>
<style scoped>
.ccc {
display: flex;
justify-content: space-between;
margin: 0px 150px;
}
</style>
子组件
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<!--设置一个默认插槽,用来存放同值-->
<slot></slot>
</div>
</template>
<script>
export default {
name: "Category",
props:['title']
}
</script>
<style scoped>
h3{
text-align: center;
width: 100%;
height: 35px;
background-color: #f88000;
}
.category{
height: 400px;
width: 300px;
background-color: #0C8ED9;
}
ul{
display: block;
}
img{
width: 100%;
}
video{
width: 100%;
}
</style>
2.具名插槽
定义: 具名插槽简单地说就是具有名字的插槽,具名插槽可以有多个插入位置,根据名字来识别对应的插槽
特点: 父组件决定结构和数据
父组件
<template>
<div class="ccc">
<category title="美食">
<img slot="content"
src="https://img1.baidu.com/it/u=319510654,3848329935&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800">
<a href="" slot="footer">更多美食</a>
</category>
<category title="游戏">
<ul slot="content">
<li v-for="(item,index) in gameList" :key="index">{{ item }}</li>
</ul>
<div class="footer" slot="footer">
<a href="">单机游戏</a>
<a href="">网络游戏</a>
</div>
</category>
<category title="电影">
<video slot="content" controls src="/aa.mov"></video>
<!--这种方式会多一层div的结构,这种结构是没有必要的,可以用template来代替-->
<!-- <div slot="footer">-->
<!-- <div class="footer">-->
<!-- <a href="">单机游戏</a>-->
<!-- <a href="">网络游戏</a>-->
<!-- </div>-->
<!-- <h3>欢迎来观影</h3>-->
<!-- </div>-->
<!--当使用template来包裹需要插入的元素时,slot属性可以写成 v-slot:footer(更推荐)-->
<template v-slot:footer>
<div class="footer">
<a href="">单机游戏</a>
<a href="">网络游戏</a>
</div>
<h3>欢迎来观影</h3>
</template>
</category>
</div>
</template>
<script>
import Category from "@/components/slot/Category.vue";
export default {
name: "CategoryMain",
components: {Category},
data() {
return {
gameList: ["英雄联盟", "守望先锋", "红警"]
}
}
}
</script>
<style scoped>
.ccc {
display: flex;
justify-content: space-around;
margin: 0px 150px;
}
.footer {
display: flex;
justify-content: space-around;
}
</style>
子组件
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<!--设置一个默认插槽,用来存放同值-->
<slot name="content"></slot>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: "Category",
props: ['title']
}
</script>
<style scoped>
h3 {
text-align: center;
width: 100%;
height: 35px;
background-color: #f88000;
}
.category {
height: 400px;
width: 300px;
background-color: #0C8ED9;
}
ul {
display: block;
}
img {
width: 100%;
}
video {
width: 100%;
}
</style>
3.作用域插槽
定义: 作用域插槽的data数据固定写在子组件中,数据的html结构根据父组件传入的html结构来决定
特点: 子组件决定数据,父组件决定结构
父组件
<template>
<div class="ccc">
<category title="游戏">
<template scope="{games}" slot="content">
<ul>
<li v-for="(item,index) in games" :key="index">{{ item }}</li>
</ul>
</template>
</category>
<category title="游戏">
<template scope="{games}" slot="content">
<ol>
<li v-for="(item,index) in games" :key="index">{{ item }}</li>
</ol>
</template>
</category>
<!--推荐这这写法-->
<category title="游戏">
<template v-slot:content="{games}">
<h4 v-for="(item,index) in games" :key="index">{{ item }}</h4>
</template>
</category>
</div>
</template>
<script>
import Category from "@/components/slot/Category.vue";
export default {
name: "CategoryMain",
components: {Category},
data() {
return {}
}
}
</script>
<style scoped>
.ccc {
display: flex;
justify-content: space-around;
margin: 0px 150px;
}
.footer {
display: flex;
justify-content: space-around;
}
</style>
子组件
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<slot name="content" :games="gameList"> 没有值展示默认slot</slot>
</div>
</template>
<script>
export default {
name: "Category",
props: ['title'],
data(){
return{
gameList: ["英雄联盟", "守望先锋", "红警"]
}
}
}
</script>
<style scoped>
h3 {
text-align: center;
width: 100%;
height: 35px;
background-color: #f88000;
}
.category {
height: 400px;
width: 300px;
background-color: #0C8ED9;
}
ul {
display: block;
}
img {
width: 100%;
}
video {
width: 100%;
}
</style>
如果是 作用域插槽 同时又是 具名插槽
Vue 2.0写法:<template scope="{games}" slot="content">
Vue 3.0写法:<template v-slot:content="{games}">
注意: scope
, slot
,slot-scope
将在vue 3.0被废弃,现在更推荐使用v-slot
。
十五、Vuex
vuex官方解释
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
什么时候我们该使用它
Vuex 可以帮助我们管理共享状态,并附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
1.安装
vue 2 安装vuex 3 ,vue 3安装 vuex 4
npm install vuex@3 --save
2.配置
2.1 建立一个单独的js文件 ,导出一个Vuex.Store对象
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
//数据,相当于data
state: {
},
getters: {
},
//里面定义方法,操作state方法
mutations: {
},
// 操作异步操作mutation
actions: {
},
modules: {}
})
2.2 在main.js中创建引入
store就是我们刚才导出的对象
import Vue from 'vue'
import App from './App.vue'
import store from "@/store/index"
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store,
}).$mount('#app')
3.核心概念
vuex中一共有五个状态 State Getter Mutation Action Module
3.1 State
提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data
在vuex中state中定义数据,可以在任何组件中进行调用
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
//数据,相当于data
state: {
num: 0,
addNum: 1,
personList: [{name: "李四", age: "123"}]
}
})
调用:
方法一:
在标签中直接使用
{{$store.state.name}}
方法二:
this.$store.state.全局数据名称
方法三:
从vuex中按需导入mapState函数
import { mapState } from "vuex";
computed: {
...mapState(["num", "personList"]),
},
注意::当前组件需要的全局数据,映射为当前组件computed属性
3.2 Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
在vuex中定义:
其中参数state参数是必须的,也可以自己传递一个参数,如下代码,进行计数器的加减操作,传递一个参数进来,来改变addNum的值
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
//数据,相当于data
state: {
addNum: 1,
},
//里面定义方法,操作state方法
mutations: {
updateAddNum(state, value){
console.log("修改AddNum" + state.addNum, state, value);
state.addNum += value;
},
},
})
组件中使用
定义两个按钮进行加减操作
<button>+</button>
<button>-</button>
方法一:
注意:使用commit触发Mutation操作
<button @click="$store.commit('updateAddNum',1)">+</button>
<button @click="$store.commit('updateAddNum',-1)">-</button>
方法二:
从vuex中按需导入mapMutations函数
<button @click="updateAddNum(1)">+</button>
<button @click="updateAddNum(-1)">-</button>
import { mapMutations } from "vuex";
methods: {
...mapMutations(["updateAddNum"])
}
注意::当前组件需要的全局数据,映射为当前组件methods属性
3.3 Action
Action和Mutation相似,一般不用Mutation 异步操作,若要进行异步操作,使用Action
原因:为了方便devtools打个快照存下来,方便管理维护。所以说这个只是规范,而不是逻辑的不允许,只是为了让这个工具能够追踪数据变化而已
增加一个延迟增加的方法
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
//数据,相当于data
state: {
num: 0,
addNum: 1,
},
getters: {
personLength(state){
return state.personList.length;
}
},
//里面定义方法,操作state方法
mutations: {
addNumFun(state, value) {
console.log("num增加" + state.addNum, state, value);
state.num += state.addNum;
},
},
// 操作异步操作mutation
actions: {
waitAdd(store, event){
// console.log("等待 num增加" , store, event,this);
setTimeout(()=>{
this.commit("addNumFun",10)
},500)
}
},
})
组件中使用
定义一个个按钮进行触发
<button @click="waitAdd">等一等再加</button>
方法一:
直接使用 dispatch触发Action函数
<button @click="$store.dispatch('waitAdd')">等一等再加</button>
方法二:
从vuex中按需导入mapActions函数
<button @click="waitAdd">等一等再加</button>
import { mapActions } from "vuex";
methods: {
...mapActions(["waitAdd"]),
}
注意::当前组件需要的全局数据,映射为当前组件methods属性
3.4 Getter
类似于vue中的computed,进行缓存,对于Store中的数据进行加工处理形成新的数据
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
//数据,相当于data
state: {
personList: [{name: "李四", age: "123"}]
},
getters: {
personLength(state){
return state.personList.length;
}
},
})
使用
方法一:
<h2>Person组件的总人数:{{$store.getters.personLength}}</h2>
this.$store.getters.全局数据名称
方法二:
从vuex中按需导入mapGetters函数
import { mapGetters } from "vuex";
computed: {
...mapGetters(["personLength"])
},
注意::当前组件需要的全局数据,映射为当前组件computed属性
3.5 Module
当遇见大型项目时,数据量大,store就会显得很臃肿
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
1.改写模块化配置文件
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
// 模块化改造
const personOptions = {
namespaced: true, state: {
personList: [{name: "李四", age: "123"}],
}, getters: {
personLength(state) {
return state.personList.length;
}
}, //里面定义方法,操作state方法
mutations: {
addPerson(state, value) {
console.log("新增Person 增加" + state.personList, state, value);
state.personList.unshift(value)
}
}, // 操作异步操作mutation
actions: {
waitAdd(store, event) {
// console.log("等待 num增加" , store, event,this);
setTimeout(() => {
this.commit("addNumFun", 10)
}, 500)
}
}
}
const sumOptions = {
namespaced: true,
state: {
num: 1, addNum: 1,
}, getters: {}, //里面定义方法,操作state方法
mutations: {
updateAddNum(state, value) {
console.log("修改AddNum" + state.addNum, state, value);
state.addNum += value;
}, addNumFun(state, value) {
console.log("addNumFun num增加" + state.addNum, state, value);
state.num += state.addNum;
}, oddAdd(state, value) {
console.log("奇数 num增加" + state.addNum, state, value);
if (state.num % 2 !== 0) {
state.num += state.addNum;
}
},
}, // 操作异步操作mutation
actions: {
waitAdd(store, event) {
// console.log("等待 num增加" , store, event,this);
setTimeout(() => {
this.commit("sumAbout/addNumFun", 10)
}, 500)
}
},
}
export default new Vuex.Store({
modules: {
personAbout: personOptions, sumAbout: sumOptions
}
})
2.是否开启命名空间
namespaced: true
: 开启命名空间,不开的话会合并对应数据,开启会按照你的模块变量名归类。
3.开启命名空间后,组件中读取state数据
//方式一 原生读取
this.$store.state.personAbout.list
//方式二 借助mapState读取
...mapState("personAbout",[ "personList"]),
4.开启命名空间后,组件中读取getters数据
//方式一 原生读取
this.$store.getters["personAbout/personLength"]
//方式二 借助mapGetters读取
...mapGetters("personAbout",["personLength"])
5.开启命名空间后,组件中调用Mutation方法
//方式一 原生调用
this.$store.dispatch("personAbout/addPerson",person)
//方式二 借助mapMutations读取
...mapMutations("personAbout",["addPerson"])
6.开启命名空间后,组件中调用Action方法
//方式一 原生调用
this.$store.commit("personAbout/add_Person",person)
//方式二 借助mapMutations读取
...mapMutations("personAbout",["addPerson"])
默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
十六、Vue Router
用 Vue + Vue Router 创建单页应用非常简单:通过 Vue.js,我们已经用组件组成了我们的应用。当加入 Vue Router 时,我们需要做的就是将我们的组件映射到路由上,让 Vue Router 知道在哪里渲染它们。
1.SPA页面
SPA:单页富应用
整个网页只有一个HTML网页。
2.准备工作
2.1 安装
vue 2 安装vue-router@3 ,vue 3安装 vue-router@4
npm install vue-router@3 --S
2.2 配置
安装完成后,在src文件夹下新建router文件夹,在里面新建index.js文件
import Vue from 'vue'
import VueRouter from 'vue-router'
import DongHua from "@/components/DongHua.vue";
Vue.use(VueRouter) //Vue中使用router插件
//路由对应表
const pageRoutes = [
{ path: '/', component: DongHua },
{
path: '/dongHua',
name: 'dongHua',
//懒加载
component: () => import('../components/DongHua.vue')
},
{
path: '/proxyComp',
name: 'proxyComp',
component: () => import('../components/PropComp.vue')
},
{
path: '/slotComp',
name: 'slotComp',
component: () => import('../components/slot/CategoryMain.vue')
},
]
const router = new VueRouter({
routes: pageRoutes,
mode: 'history'
});
export default router //导出路由实例,在main.js中导入使用
在main.js中引入
import Vue from 'vue'
import App from './App.vue'
import router from "@/router/index";
Vue.config.productionTip = false
const a = new Vue({
render: h => h(App),
router, //在vue实例中使用router
}).$mount('#app')
2.3利用router标签测试跳转
<template>
<div id="app">
<router-link to="/proxyComp">proxyComp</router-link>
<router-link to="/dongHua">dongHua</router-link>
<router-link to="/slotComp">slotComp</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
3.基本标签
3.1 router-link
组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 <a>
标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。
<router-link to="/routerName">
to 表示目标路由的链接。当被点击后,内部会立刻把 to 的值传到 router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象。
active-class 属性 ,默认值: “router-link-active”
设置链接激活(选中)时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。
3.2 router-view
<router-view>
组件是一个 functional 组件,渲染路径匹配到的视图组件。<router-view>
渲染的组件还可以内嵌自己的 <router-view>
(嵌套路由),根据嵌套路径,渲染嵌套组件。
name 属性 默认值: “default”
如果 <router-view>
设置了名称,则会渲染对应的路由配置中 components 下的相应组件。
3.3 注意点
- pages文件夹一般放路由组件,components文件夹一般放页面组件
- 路由切换会导致对应的组件被销毁
- 如果是路由组件,会在vueComponent对象上增加两个对象
- $route 配置的对应路由规则,如path属性“/home”等数据
- $router 整个应用的路由器,有且只有一个
4.嵌套路由
嵌套路由又称为子路由,在实际应用中,通常由多层嵌套的组件组合而成。同样地,URL中各段动态路景观也按某种结构对应嵌套的各层组件
4.1 编写一级路由组件
因为会嵌套二级路由,所以内部也会有一个router-view标签
<template>
<div id="app">
<router-link to="/parentComp">parentComp</router-link>
<router-view class="viewD"></router-view>
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
.viewD{
height: 100%;
}
html,body,#app{
height: 100%;
overflow: hidden;
}
</style>
4.2 编写二级路由组件
<template>
<div class="d1">
<h1>ParentComp</h1>
<div class="d2">
<h2>children view</h2>
<router-link to="/parentComp/childrenA">proxyComp</router-link>
|
<router-link to="/parentComp/childrenB">dongHua</router-link>
|
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name: "ParentComp"
}
</script>
<style scoped>
.d1 {
width: 100%;
height: 100%;
background-color: #00C0EF;
padding: 20px;
text-align: center;
}
.d2 {
width: 60%;
height: 60%;
background-color: #1b6d85;
margin-left: 20%;
}
</style>
4.3 编写二级路由表
利用路由表对象children
属性,可以在这个属性下增加二级路由
import Vue from 'vue'
import VueRouter from 'vue-router'
import DongHua from "@/components/DongHua.vue";
Vue.use(VueRouter) //Vue中使用router插件
//路由对应表
const pageRoutes = [
{ path: '/', component: DongHua },
{
path: '/dongHua',
name: 'dongHua',
//懒加载
component: () => import('../components/DongHua.vue')
},
{
path: '/parentComp',
name: 'parentComp',
//懒加载
component: () => import('../components/router/ParentComp.vue'),
children:[
{path: '/',},
{
path: 'childrenA',
name: 'childrenA',
//懒加载
component: () => import('../components/router/ChildrenA.vue')
},{
path: 'childrenB',
name: 'childrenB',
component: () => import('../components/router/ChildrenB.vue')
}
]
},
]
const router = new VueRouter({
routes: pageRoutes,
mode: 'history'
});
export default router //导出路由实例,在main.js中导入使用
5.路由的query参数
1.传递参数
<!--跳转并携带query参数,to的字符串写法-->
<router-link v-for="(item,index) in detailList" :key="index"
:to="'/parentComp/detail?id='+item.id+'&msg='+item.msg">
{{ ' detail ' + item.id + ' |' }}
</router-link>
<!--跳转并携带query参数,to的对象写法-->
<router-link v-for="(item,index) in detailList" :key="index"
:to="{
path:'/parentComp/detail',
query:{
id:item.id,
msg:item.msg
}}">
{{ ' detail ' + item.id + ' |' }}
</router-link>
2.接收参数
// js中使用
this.$route.query.参数数据
//插值语法中使用
{{$route.query.参数数据}}
6.命名路由
1.作用:可以简化路由的跳转
2.使用:
-
2.1 给路由命名
给路由配置上加上
name
属性 -
2.2 简化跳转
<!--简化前,需要写完整的路径--> <router-link to="/parentComp/childrenA">childrenA</router-link> <!--简化后,直接通过名字跳转--> <router-link :to="{name:'childrenA'}">childrenA</router-link> <!--跳转并携带query参数,name写法--> <router-link :to="{ name:'detail', query:{ id:666, msg:'你好' } }" >跳转</router-link>
7.路由的param参数
1.设置路由占位符
在path中修改path后面用:变量名
的方式进行占位
{
path: 'detail/:id/:msg',
name: 'detail',
component: () => import('../components/router/DetailComp.vue')
}
2.设置路由跳转
<!--路由跳转,path 携带params参数(不推荐)-->
<router-link :to="{
path:'/parentComp/detail/'+item.id+'/'+item.msg
}"
>跳转</router-link>
<!--路由跳转,name 携带params参数(推荐)-->
<router-link :to="{
name:'detail',
params:{
id:66,
msg:'你好'
}}"
>跳转</router-link>
3.接收参数
// js中使用
this.$route.params.参数数据
//插值语法中使用
{{$$route.params.参数数据}}
8.路由的props配置
1.作用:让路由组件更方便的收到参数
2.使用:
-
2.1第一种写法:props值为对象
该对象中所有的key-value的组合最终都会通过props传给Detail组件
缺点:值是常量,没办法修改
{
path: 'detail',
name: 'detail',
component: () => import('../components/router/DetailComp.vue'),
props:{a:900} //缺点:值是常量,没办法修改
}
//组件内接收参
props:["a"],
-
2.2 第二种写法:props值为布尔值
该对象中所有的key-value的组合最终都会通过props传给Detail组件
缺点:只能利用params参数
//配置 { path: 'detail', name: 'detail', component: () => import('../components/router/DetailComp.vue'), props:true } //传参 :to="{ name:'detail', params:{ id:item.id, msg:item.msg } }" //组件内接参 props:["id","msg"],
-
2.3 第三种写法:props值为函数
该函数返回的对象中每一组key-value都会
通过props传递给Detail
组件//配置 props($route) { //传参注意:query属性每次都会进入刷新,但params同页面跳转不刷新 return { id: $route.query.id, msg: $route.query.msg } } //传参 :to="{ name:'detail', query:{ id:item.id, msg:item.msg } }" //组件内接参 props:["id","msg"],
9. **< router-link >**的replace属性
1.作用:控制路由跳转时操作浏览器历史记录的模式
2.浏览器的历史记录有两种写入方式:分别为 push
和 replace
,push
是追加历史记录,replace
是替换当前记录,路由跳转时候默认为 push
(有记录模式)
3.如何开启 replace
模式: <router-link replace ......>News</router-link>
存储历史记录的数据结构为栈结构
push
就是点击一个网址会压栈一条记录进去
replace
就是替换掉栈顶的一条数据
10.编程式路由导航
1.作用:不借助 < router-link >
实现路由跳转,让路由跳转更加灵活
2.具体编码:
// 路由跳转,有历史记录
this.$router.push({
name: 'detail',
query: {
id: item.id,
msg: item.msg
}
});
// 路由跳转,无历史记录
this.$router.replace({
name: 'detail',
query: {
id: item.id,
msg: item.msg
}
})
// 路由回退到上一条记录
this.$router.back()
// 路由前进到上一条记录
this.$router.forward()
// 路由前进或后退,当num为负数表示后退几条记录,正数则表示前进几条记录
this.$router.go(num)
11.缓存路由组件
1.作用:让不展示的组件保持挂载,不被销毁
2.具体实现:
通过在 < router-view >
外层包裹 < keep-alive >
标签,并用 include
指出需要缓存的 组件名
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
如果include里面要指定多个组件名的话
<keep-alive :include="['NewsTwo','News']">
<router-view></router-view>
</keep-alive>
12.路由激活与失活的生命周期钩子
1.作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态
2.具体名字:
activated
路由组件被激活时触发。deactivated
路由组件失活时被触发
13.路由守卫
作用:对路由进行权限控制
分类:全局守卫,独享守卫,组件内守卫
meta参数:路由表配置项中可以用来存储需要的值,已便后续去使用。
1.全局路由守卫
全局前置守卫(beforeEach
):初始化时执行,每次路由切换执行 通常用来做全局的路由挑战权限验证
全局后置守卫(afterEach
):路由切换成功后会执行,可以用来做一些收尾工作,记录日志等
//路由守卫
//全局路由前置守卫,初始化时执行,每次路由切换执行 通常用来做全局的路由挑战权限验证
router.beforeEach((to, from, next) => {
// to 需要切换到的地址
// from 来自那一个地址
// next可以通过传递不同的参数,给路由重定向或者放行
console.log("全局前置守卫", to, from);
// 判断是否需要权限验证
if (to.meta.isAuth) { //判断当前路由是否需要进行权限控制
if (localStorage.getItem("role") === "管理员") {
next() //放行
} else {
alert("暂无权限查看")
next({path: "/proxyComp"}) //可以让他强制跳转到登陆页操作
}
} else {
next();
}
})
//全局路由后置守卫,路由切换成功后会执行,可以用来做一些收尾工作,记录日志等
router.afterEach((to, from) => {
console.log("全局后置守卫", to, from);
console.log("记录日志成功,页面跳转到"+to.name)
})
2.路由独享守卫
路由独享守卫(beforeEnter
):通过在路由表中利用beforeEnter
关键字来指定需要执行的守卫函数,它会在路由跳转该组件的时候被执行。功能与前置守卫类似,但是它更适合一些需要特殊路由处理的组件。
{
path: '/parentComp',
name: 'parentComp',
//接收一个函数,也可以接收一个函数数组
//beforeEnter:[fun1,fun2],
beforeEnter:(to,from,next)=>{
console.log("路由独享守卫",to,from);
next();
},
}
3.组件内的路由钩子
- 组件路由进入守卫
beforeRouteEnter
:通过路由规则,进入当前组件时调用 - 组件路由离开守卫
beforeRouteLeave
:通过路由规则,离开当前组件时调用 - 组件路由更新守卫
beforeRouteUpdate
:通过路由规则,使当前组件发生更新时调用,例如获取不同的参数重新进入当前页面渲染时
通过路由规则:指的是通过路由跳转进入页面,而不是直接在页面上引用。
beforeRouteEnter(to,from,next){
console.log("组件内进入守卫",to,from)
next();
},
beforeRouteLeave(to,from,next){
console.log("组件内离开守卫",to,from)
next();
},
beforeRouteUpdate(to,from,next){
console.log("组件内更新守卫",to,from)
next();
}
14.路由器的两种工作模式
1.hash模式
- 1 对于url来说,#及其后面的内容就是hash值;
- 2 hash值不会包含在http请求中,即:hash值不会带给服务器。
- 3 hash模式的特点:url地址中永远带着#号,不美观。但兼容性好
2.history模式
- 1 地址干净,美观
- 2 兼容性比hash模式相比略差。
- 3 应用部署上线时,刷新页面服务端会报404的问题,不部署在相同的端口是没问题的。
router中修改模式
修改 router 中的mode
参数
const router = new VueRouter({
routes: pageRoutes,
mode: 'history'
//mode:"hash"
});