Vue2
1.快速入门
- 注意代码顺序,要求div在前,script在后,否则无法绑定数据
- 先创建一个vue对象,构造器传入含有el和data属性的对象
- el表示关联的html元素,可以使用多种选择器绑定html元素
- data表示该vue对象的数据池,只有被vue对象绑定的html元素才能使用到数据池中的数据
- 使用插值表达式(两个大括号)给html标签绑定数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue快速入门</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<!--
这里的两个大括号嵌套是插入表达式,
通过id到对应vue对象中的数据池中匹配数据
-->
<h1>欢迎{{message}}-{{name}}</h1>
</div>
<script type="text/javascript">
//创建一个vue对象,构造器传入含有el和data属性的对象
//el表示关联的html元素的id
//data表示该vue对象的数据池
let vue = new Vue({
el:"#app",
data:{//这里的data也是一个对象,data对象的属性保存着数据池数据
message:"Hello,Vue",
name:"杨逸"
}
});
console.log("vue=",vue);
//获取数据池中数据
console.log(vue._data.message);
console.log(vue._data.name);
//直接获取
console.log(vue.name);
console.log(vue.message);
</script>
</body>
</html>
2.单向数据渲染(v-bind)
- 单向数据渲染就是数据池data中数据的变化会影响到页面view展示的数据,而页面数据的变化不会影响到数据池的变化
- 插值表达式是给标签绑定数据的,不能给标签的属性绑定数据
- 给标签的属性绑定数据,需要使用单向数据绑定(v-bind)
- 使用的方式是给属性加上"v-bind:"前缀,然后 在属性值的位置写上数据池中的属性
- 单向数据绑定还可以简写成一个冒号加属性名
- 应用实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>单向数据渲染(v-bind)的使用</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<!--插值表达式是给标签绑定数据的-->
<h1>{{message}}</h1>
<!--单行数据渲染(v-bind)是给标签的属性绑定数据的-->
<!--单向数据渲染就是数据池data中数据的变化会影响到页面view展示的数据,而页面数据的变化不会影响到数据池的变化-->
<!--使用的方式是给属性加上"v-bind:"前缀,然后 在属性值的位置写上数据池中的属性-->
<img v-bind:src="img_src" v-bind:width="img_width">
<!--单向数据渲染还可以简写成一个冒号加属性名-->
<img :src="img_src" :width="img_width">
</div>
<script>
let vue = new Vue({
el:"#app",
data:{
message:"单向数据绑定的使用",
img_src:"../img/1.jpg",
img_width:"200px"
}
})
</script>
</body>
</html>
3.双向数据渲染(v-model=)
-
双向数据渲染就是数据池data中数据的变化会影响到页面view展示的数据,页面数据的变化也会影响到数据池中数据的变化
-
使用方式是"v-model="后面直接写上数据池中的属性
-
双向数据绑定没有简写
-
修饰符
- number:将输入转换为数字,
v-model.number="age"
- trim:去掉字符串开头和结尾的空格字符,
v-model.trim="username"
- lazy:当失去焦点时,再更新数据池中的数据,
v-model.lazy="remark"
- number:将输入转换为数字,
-
应用实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>双向数据渲染的使用</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<h1>{{message}}</h1>
<!--使用双向数据渲染,给下面表单控件输入新的值时,会影响到数据池的hobby数据-->
<input name="hobby" v-model="hobby"><br><br>
<input name="hobby" :value="hobby">
<p>你的爱好是{{hobby}}</p>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
message:"请输入你的爱好",
hobby:"吃瓜"
}
});
</script>
</html>
- 应用实例:收集表单的各种数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用双向数据绑定收集表单的各种控件的数据</title>
<script src="../js/vue.js"></script>
</head>
<body>
<form @submit.prevent="submit" id="app">
用户名:<input type="text" v-model.trim="user.usernmae"><br><br>
密码:<input type="password" v-model="user.password"><br><br>
性别:<input type="radio" v-model="user.sex" value="男">男<input type="radio" v-model="user.sex" value="女">女<br><br>
年龄:<input type="number" v-model="user.age"><br><br>
爱好:<br>
学习<input type="checkbox" v-model="user.hobby" value="学习">
唱歌<input type="checkbox" v-model="user.hobby" value="唱歌">
写代码<input type="checkbox" v-model="user.hobby" value="写代码">
<br><br>
城市:
<select v-model="user.city">
<option>选择城市</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select><br><br>
个人介绍:<br><br>
<textarea v-model.lazy="user.introduce"></textarea><br><br>
<input type="checkbox" v-model="user.agree">同意条款<a href="baidu.com">服务条款</a><br><br>
<button>提交</button>
</form>
</body>
<script>
const vm = new Vue({
el:'#app',
data:{
user:{
usernmae:'',
password:'',
age:0,
sex:'',
hobby:[],
city:'',
introduce:'',
agree:''
}
},
methods:{
submit(){
const user = JSON.stringify(this.user);
console.log(user);
}
}
});
</script>
</html>
4.事件绑定
- 使用
v-on:时间名="vue对象方法池中的方法"
绑定事件 - 使用
methods
属性定义vue对象的方法池 - 如果浏览器支持,事件绑定可以简写,当绑定的方法没有参数时,可以省略形参列表(小括号)
- 如果浏览器支持.
v-on:
可以使用@
符号代替 - 如果绑定的方法只有一条语句,可以将语句直接写到事件绑定的地方,如
<button @click="count += 2">次数+2</button>
- 应用案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件绑定</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<h1>vue的事件绑定</h1>
<!--在vue中,时间绑定使用的是"v-on:事件名='方法池中的绑定的方法'"-->
<button v-on:click="sayHi()">点击事件一</button>
<button v-on:click="sayOk()">点击事件二</button>
<!--事件绑定的简写,如果方法没有参数可以省略小括号(形参列表)-->
<!--可以使用"@"符号代替"v-on:",需要浏览器支持-->
<button v-on:click="sayHi">点击事件三</button>
<button @click="sayOk">点击事件四</button>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
},
methods:{//vue对象的方法池,事件调用的方法都在这里定义
sayHi(){
console.log("hi,银角大王");
},
sayOk(){
console.log("ok,金角大王");
}
}
});
</script>
</html>
5.修饰符
- 修饰符(Modifiers)是以()指明的后缀,指出某个指令以特殊方式绑定。
- 例如,
.prevent
修饰符告诉v-on
指令对于触发的事件调用event.preventDefault()
即阻止事件原本的默认行为 - 使用方式,在对应事件后加上要使用的修饰符
- 应用实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件修饰符的基本使用</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<!--这里使用".prevent"事件修饰符,阻止事件的默认机制,使用我们自定义的方法进行处理-->
<form action="http://www.baidu.com/" @submit.prevent="mySubmit">
<h1>事件修饰符的基本使用</h1>
妖怪的名字:<input type="text" v-model="monster.name"><br>
<input type="submit" value="提交">
</form>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
monster:{
}
},
methods:{
mySubmit(){
console.log("自定义的提交方法被调用");
if (this._data.monster.name){
console.log("表单提交,表单的信息",this.monster);
}else {
console.log("妖怪名不能为空");
}
}
}
});
</script>
</html>
事件修饰符
.stop
,阻止事件冒泡,阻止单击事件继续传播.prevent
,阻止事件的默认处理,提交事件不再重载页面.capture
,先执行事件的默认操作,再执行回调函数;内部元素触发的事件先在此处理,然后才交由内部元素进行处理.self
,只当在 event.target 是当前元素自身时触发处理函数.once
,事件将只会触发一次.passive
,不阻止默认事件的发生
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
按键修饰符
.enter
.tab
.delete
(捕获“删除”和“退格”键).esc
.space
.up
.down
.left
.right
更多细节在Vue的官方文档
6.计算属性(computed)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>计算属性</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
姓:<input type="text" v-model='firstName'><br>
名:<input type="text" v-model='lastName'><br>
全名:{{fullName}}
</div>
</body>
<script>
const vm = new Vue({
el:'#app',
data:{
firstName:'张',
lastName:'三'
},
computed:{
// 通过计算得到完整的姓名,get函数用于返回计算后的属性,set用于响应该属性变化的回调函数
// fullName:{
// get(){
// return this.firstName.slice(0,2) + '-' + this.lastName.slice(0,2);
// },
// set(val){
// }
// }
//简写,如果只需要获取属性而不需要修改
fullName(){
return this.firstName.slice(0,2) + '-' + this.lastName.slice(0,2);
}
}
});
</script>
</html>
7.监视属性(watch)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>监听属性</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h3>今天天气{{weather}}</h3>
<button @click="isHot=!isHot">切换天气</button>
</div>
</body>
<script>
const vm = new Vue({
el:'#app',
data:{
isHot:true
},
computed:{
weather(){
return this.isHot ? '炎热' : '凉爽';
}
},
watch:{
isHot:{
//初始化时是否调用一下
immediate:true,
//是否开启生成监听
deep:true,
//当isHot属性发生变化的时候调用
handler(newValue,oldValue){
console.log('属性发生变化',oldValue,'-->',newValue);
}
}
}
});
</script>
</html>
8.条件渲染
v-if
,当表达式为true时创建组件,为false时不创建v-show
,当表达式为true时,显示组件,组件默认创建,只是可能不显示- 条件渲染的参数都是到数据池中匹配的,并不是简单的字符串
- v-if的应用案例
v-if
是通过组件的创建和销毁实现显示控制的,如果频繁切换对性能的影响比较大
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue的条件渲染v-if</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<!--使用双向渲染关联到数据池中的数据-->
<!--checked属性默认是true-->
<input type="checkbox" v-model="sel">是否同意条款[使用v-if实现]
<!--"v-if"的参数,实际上是到数据池中去匹配-->
<!--"v-if"是通过组件的创建和销毁实现显示控制的,如果频繁切换对性能的影响比较大-->
<h2 v-if="sel">你同意条款</h2>
<h2 v-else>你不同意条款</h2>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
sel:false
}
});
</script>
</html>
- v-show应用实例
v-show
实际上是通过控制display属性进行显示的切换的,没有子组件的创建和销毁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用v-show实现显示控制</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<!--使用双向渲染关联到数据池中的数据-->
<!--checked属性默认是true-->
<input type="checkbox" v-model="sel">是否同意条款[使用v-show实现]
<!--"v-show"的参数,实际上是到数据池中去匹配-->
<!--"v-show"实际上是通过控制display属性进行显示的切换的,没有子组件的创建和销毁-->
<h2 v-show="sel">你同意条款</h2>
<h2 v-show="!sel">你不同意条款</h2>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
sel:false
}
});
</script>
</html>
- v-else-if的使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>条件渲染作业</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<h1>演示条件渲染</h1>
请输入你的成绩:<input type="text" v-model="score" @blur="judge">
<p>你当前的成绩是:{{score}}</p>
<p v-if="score >= 90">优秀</p>
<p v-else-if="score >= 70">良好</p>
<p v-else-if="score >= 60">及格</p>
<p v-else>不及格</p>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
score:0
},
methods:{
judge(){
if (this.score > 100){
this.score = 100;
}else if (this.score < 0){
this.score = 0;
}
}
}
});
</script>
</html>
9.列表渲染
- 可以取出数组里的值,也可以取出数组的值的索引
- 可以取出对象属性的值,也可以取出对象属性的名称,还可以取出对象属性的索引
- 取值的循序是有要求的,先取值,然后取属性名,最后索引
- v-for的应用实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表渲染</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<ul>
<!--渲染的目标可以是一个数字,默认从1渲染到指定的数字-->
<li v-for="value in 3">{{value}}</li>
</ul>
<ul>
<!--渲染列表的值-->
<li v-for="x in list1">{{x}}</li>
</ul>
<ul>
<!--渲染列表的值的索引-->
<li v-for="(value,index) in list1">{{index}}:{{value}}</li>
</ul>
<ul>
<!--渲染对象的属性值,属性名,属性索引-->
<!--使用列表渲染,取数据的循序是固定的,先值,然后属性名,最后索引,如果没有的就跳过-->
<li v-for="(value,name,index) in monster">{{index}}:{{name}}:{{value}}</li>
</ul>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
list1:[1,2,3,4,5],
monster:{
name:"牛魔王",
age:12,
skill:["牛啊","牛逼"]
}
}
});
</script>
</html>
- v-for列表循环作业
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表渲染作业</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<table border="1px">
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>score</th>
</tr>
<tr v-if="x.score >= 60" v-for="x in student" >
<td>{{x.id}}</td>
<td>{{x.name}}</td>
<td>{{x.age}}</td>
<td>{{x.score}}</td>
</tr>
</table>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
student:[
{id:1,name:"jack",age:20,score:90},
{id:2,name:"tom",age:20,score:30},
{id:3,name:"kk",age:20,score:60}]
}
});
</script>
</html>
10.组件化编程
- 组件的创建需要创建vue对象之前,否则vue对象关联不到挂组件
全局组件
- 调用Vue.component()方法创建组件,第一个参数是组件名,第二个参数是组件对象,该组件是属于所有vue对象
- 创建的组件也是一个vue对象
- 组件的template属性表示的是组件的具体html内容
- 组件的数据池与常规的vue对象的数据池是不一样的
- 组件里的数据池是一个方式,方法里根据组件里的数据名,返回一个数据对象
- 使用vue创建的组件,通过标签的形式使用,标签名就是组件的名称
- 每使用一个组件标签,就创建一个新的vue对象,它们之间的数据是独立的
- 全局组件应用实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>全局组件</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<h1>全局组件化编程</h1>
<!--
使用vue创建的组件,通过标签的形式使用,标签名就是组件的名称
每使用一个标签,就创建一个新的vue对象,它们之间的数据是独立的
-->
<counter></counter><br>
<counter></counter><br>
<counter></counter>
</div>
</body>
<script type="text/javascript">
//使用组件化编程,调用Vue.component()方法创建组件,第一个参数是组件名,第二个参数是组件对象
//创建的组件也是一个vue对象
Vue.component("counter",{
//template属性表示的是组件的具体html内容
template:`<button @click="click">点击次数{{count}}[全局组件化编程]</button>`,
//组件里的数据池是一个方式,方法里根据组件里的数据名,返回一个数据对象
//这里的数据池与常规的vue对象的数据池是不一样的
data(){
return {
count:0
}
},
methods:{
click(){
this.count++;
}
}
});
let vue = new Vue({
el:"#app"
});
</script>
</html>
局部组件
-
在vue对象挂载的元素中使用组件池中的组建,这种组件称为局部组件
-
先创建组件,然后在vue对象的组件池中加入创建好的组件
-
vue组件池里的组件只有在vue对象挂载的元素才能使用,其他位置不能使用
-
也可以导入外部js文件里的组件,然后再加入组件池中
-
局部组件应用实例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../js/vue.js"></script>
<title>非单文件组件</title>
</head>
<body>
<div id="app">
<!-- 第三步:使用组件 -->
<school></school>
</div>
</body>
<script>
// 第一步:创建组件
const school = Vue.extend({
name:'myComponent',
//组件的结构
template:`
<div>
<p>学校名称:{{name}}</p>
<p>学校地址:{{address}}</p>
</div>`,
//组件的数据池,必须使用函数的形式
data(){
return {
name:'广科',
address:'珠海'
}
}
});
// 全局注册组件,参数一是组件的名称,参数二是组件实例对象
Vue.component('school',school);
const vm = new Vue({
el:'#app',
//第二步:注册组件(局部组件)
components:{
school
}
});
</script>
</html>
-
局部组件应用实例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>局部组件</title> <script type="text/javascript" src="../script/vue.js"></script> </head> <body> <div id="app"> <h1>局部组件的使用</h1> <!--在vue对象挂载的元素中使用组件池中的组建,这种组件称为局部组件--> <my_counter></my_counter> <my_counter></my_counter> </div> </body> <script type="text/javascript"> //创建一个组件对象,组件对象里的属性是固定的 let counter = { //template属性表示的是组件的具体html内容 template:`<button @click="click">点击次数{{count}}[局部组件化编程]</button>`, //组件里的数据池是一个方式,方法里根据组件里的数据名,返回一个数据对象 //这里的数据池与常规的vue对象的数据池是不一样的 data(){ return { count:0 } }, methods:{ click(){ this.count++; } } }; //创建一个vue对象,并挂载 let vue = new Vue({ el:"#app", components:{//在vue对象的组件池中加入创建好的组件 //vue组件池里的组件只有在vue对象挂载的元素才能使用,其他位置不能使用 my_counter:counter } }); </script> </html>
单文件组件
- 组件一
<template>
<!-- 组件的结构 -->
<div>
<p>学校名称:{{name}}</p>
<p>学校地址:{{address}}</p>
</div>
</template>
<script>
// 组件的交互
// 导出组件
export default Vue.extend({
name:'School',
data(){
return {
name:'广科',
address:'珠海'
}
}
});
</script>
<style>
/* 组件的样式 */
</style>
- 组件二
<template>
<div>
<p>学生姓名:{{ name }}</p>
<p>学生年龄{{ age }}</p>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:20
}
}
};
</script>
- 组件整合
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
import School from './School.vue'
import Student from './Student.vue'
export default{
name:'App',
components:{
School,
Student
}
}
</script>
- 使用组件和程序的入口
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单文件组件入口</title>
<script src="../js/vue.js"></script>
<script type="module" src="./main.js"></script>
</head>
<body>
<div id="app">
<App></App>
</div>
</body>
</html>
import App from './App.vue'
new Vue({
el:'#app',
components:{
App
}
});
通过prop配置项给组件传递数据
- 定义组件
- 组件接收传递的参数需要配置props指定需要传递的参数
- props中的数据是只读的,不可修改
<template>
<div>
<h1>{{ msg }}</h1>
<p>学生姓名:{{ name }}</p>
<!-- 传递的值不能直接修改,可以通过数据池里的中间变量进行修改 -->
<p>学生年龄:{{ myAge+1 }}</p>
<p>学生性别:{{ sex }}</p>
<button @click="myAge++">点我年龄加1</button>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
msg:'学生信息',
myAge:this.age
}
},
//需要接收参数要在组件定义时配置
// 简单的配置
// props:['name','age','sex']
//限制接受数据类型的配置
// props:{
// name:String,
// sex:String,
// age:Number
// }
// 限制数据类型,限制数据必要性,能设置数据默认值的配置方式
//type限制数据类型,required限制传参的必要性,default设置默认值
props:{
name:{
// 限制数据的类型
type:String,
// 必须传递该参数
required:true
},
sex:{
// 限制数据的类型
type:String,
// 必须传递该参数
required:true
},
age:{
type:Number,
// 设置默认值
default:99
}
}
};
</script>
- 使用组件
- 向组件传递数据直接在组件标签上写对应的属性即可
<template>
<div id="app">
<School></School>
<!-- 使用v-bind简写的方式向组件传递数值 -->
<Student name="张三" sex="男" :age="18"></Student>
</div>
</template>
<script>
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name: 'App',
components: {
School,
Student
}
}
</script>
通过mixin配置项复用相同的配置
-
mixin配置对象和组件对象同时具有的配置以组件配置的为准,但生命周期的钩子函数例外,两个地方的配置都生效
-
创建mixin混合配置对象
// 创建一个混合配置对象,使用混合对象复用相同的配置
export const mixin = {
// vue中的配置对象都可以在这里配置,
methods:{
showName(){
alert(this.name);
}
}
};
- 导入mixin混合配置对象并使用
<template>
<div>
<h1>{{ msg }}</h1>
<p @click="showName">学生姓名:{{ name }}</p>
<!-- 传递的值不能直接修改,可以通过数据池里的中间变量进行修改 -->
<p>学生年龄:{{ myAge+1 }}</p>
<p>学生性别:{{ sex }}</p>
<button @click="myAge++">点我年龄加1</button>
</div>
</template>
<script>
// 引入混合配置对象
import {mixin} from '@/utils/mixin.js'
export default {
name:'Student',
data(){
return {
msg:'学生信息',
myAge:this.age
}
},
// 使用mixin混合配置对象,复用相同的配置
mixins:[mixin]
};
</script>
<template>
<!-- 组件的结构 -->
<div>
<p @click="showName">学校名称:{{name}}</p>
<p>学校地址:{{address}}</p>
</div>
</template>
<script>
import {mixin} from '@/utils/mixin'
// 组件的交互
// 导出组件
export default {
name:'School',
data(){
return {
name:'广科',
address:'珠海'
}
},
mixins:[mixin]
};
</script>
<style>
/* 组件的样式 */
</style>
vue的plugs插件
- 编写插件
// 编写vue的插件
export default {
// 插件的入口,必需有这个install函数
// 第一个参数是Vue的原型对象,剩下的参数是使用插件时传入的自定义参数
install(Vue){
// 获取到Vue的原型对象,我们就可以在Vue的原型上添加额外的功能
// 这个函数由在创建Vue时调用
console.log(Vue);
}
}
- 使用插件
import Vue from 'vue'
import App from './App.vue'
// 导入插件
import plugs from '@/utils/plugs'
Vue.config.productionTip = false;
// 使用插件
Vue.use(plugs);
new Vue({
// 渲染vue的template
render: h => h(App),
}).$mount('#app');
组件的自定义事件
- 定义和绑定自定义事件
- 直接在组件上绑定组件自定义事件
- 通过组件对象的
$on
函数绑定组件自定义事件 - 绑定原生事件需要使用
.native
修饰符
<template>
<div id="app">
<!-- 第一张方式:给组件绑定myEvent自定义事件 -->
<School @myEvent="getSchoolName" ref="demo"></School>
<!-- 给组件绑定原生的事件需要使用.native修饰符 -->
<Student name="张三" sex="男" :age="18" @click.native="nativeClickEvent"></Student>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {
name: 'App',
components: {
HelloWorld,
School,
Student
},
methods:{
getSchoolName(name){
console.log('App接受到数据:',name);
},
nativeClickEvent(){
alert('原始的click事件被触发');
}
},
mounted(){
// 第二种方式:通过ref给组件绑定自定义事件
// this.$refs.demo.$on('myEvent',this.getSchoolName);
// 绑定只触发一次的自定义事件
// this.$refs.demo.$once('myEvent',this.getSchoolName);
}
}
</script>
- 触发自定义事件
- 触发事件需要在组件对象中调用
$emit
函数使用组件自定义事件
- 触发事件需要在组件对象中调用
<template>
<!-- 组件的结构 -->
<div>
<h1>{{ msg }}</h1>
<p @click="showName">学校名称:{{name}}</p>
<p>学校地址:{{address}}</p>
<button @click="diyHanlder">点我触发组件的自定义事件</button>
<button @click="deathHanlder">点我解绑组件的自定义事件</button>
</div>
</template>
<script>
import {mixin} from '@/utils/mixin'
// 组件的交互
// 导出组件
export default {
name:'School',
data(){
return {
name:'广科',
address:'珠海',
msg:'学校信息'
}
},
methods:{
diyHanlder(){
// 手动触发组件的自定义事件
this.$emit('myEvent',this.name);
},
deathHanlder(){
// 解绑组件一个自定义事件
this.$off('myEvent');
// 解绑多个组件自定义事件,使用数组的形式传参
// this.$off(['myEvent','myEvent2']);
// 解绑所有组件自定义事件
// this.$off();
}
},
mixins:[mixin]
};
</script>
<style>
/* 组件的样式 */
</style>
- 解除自定义事件
deathHanlder(){
// 解绑组件一个自定义事件
this.$off('myEvent');
// 解绑多个组件自定义事件,使用数组的形式传参
// this.$off(['myEvent','myEvent2']);
// 解绑所有组件自定义事件
// this.$off();
}
11.生命周期
- 生命周期流程图
- beforeCreate()
- 组件创建遇到的第一个生命周期函数,这个阶段data和methods以及dom结构都未被初始化,也就是获取不到data的值,不能调用methods中的函数
- created()
- 这个阶段组件的data和methods中的方法已初始化结束,可以访问,但是dom结构未初始化,页面未渲染
- 在这个阶段经常发起ajax请求
- beforeMount()
- 当模板在内存中编译完成,此时内存中的模板结构还未渲染至页面上,看不到真实的数据
- mounted()
- 此时,页面渲染好,用户看到的是真实的页面数据,生命周期创建阶段完毕,进入到了运行中的阶段
- beforeUpdate()
- 当执行此函数,数据池的数据新的,但是页面是旧的
- updated()
- 页面已经完成了更新,此时,data数据和页面的数据都是新的
- beforeUnmount()
- 当执行此函数时,组件即将被销毁,但是还没有真正开始销毁,比时组件的data、methods数据或方法还可以被使用
- unmounted()
- 组件已经完成了销毁
- 生命周期函数(钩子函数)使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>生命周期函数(钩子函数)的使用</title>
<script type="text/javascript" src="../script/vue.js"></script>
</head>
<body>
<div id="app">
<span id="num">{{name}}被点赞{{count}}次</span>
<button @click="count++">点赞</button>
</div>
</body>
<script type="text/javascript">
let vue = new Vue({
el:"#app",
data:{
count:0,
name:"杨逸"
},
methods:{
show(){
console.log("show函数被调用",this.count," ",this.name);
}
},
beforeCreate(){//生命周期函数
console.log("=====beforeCreate=====创建前");
console.log("data数据池的数据能否被使用?[no]",this.count," ",this.name);
// console.log("methods方法池的方法能否被使用?[no]",this.show());
console.log("用户dom页面是否被加载?[no]",document.getElementById("num"));
console.log("用户dom页面是否被渲染?[no]",document.getElementById("num").innerText);
},
created(){
console.log("=====create=====创建后");
console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
console.log("methods方法池的方法能否被使用?[yes]",this.show());
console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
console.log("用户dom页面是否被渲染?[no]",document.getElementById("num").innerText);
},
beforeMount(){
console.log("=====beforeMount=====挂载前");
console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
console.log("methods方法池的方法能否被使用?[yes]",this.show());
console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
console.log("用户dom页面是否被渲染?[no]",document.getElementById("num").innerText);
},
mounted(){
console.log("=====mounted=====挂载后");
console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
console.log("methods方法池的方法能否被使用?[yes]",this.show());
console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
console.log("用户dom页面是否被渲染?[yes]",document.getElementById("num").innerText);
},
beforeUpdate(){
console.log("=====beforeUpdate=====创数据更新前");
console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
console.log("methods方法池的方法能否被使用?[yes]",this.show());
console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
console.log("用户dom页面是否被渲染成新数据?[no]",document.getElementById("num").innerText);
},
updated(){
console.log("=====updated=====数据更新后");
console.log("data数据池的数据能否被使用?[yes]",this.count," ",this.name);
console.log("methods方法池的方法能否被使用?[yes]",this.show());
console.log("用户dom页面是否被加载?[yes]",document.getElementById("num"));
console.log("用户dom页面是否被渲染成新数据?[yes]",document.getElementById("num").innerText);
}
});
</script>
</html>
- 生命周期函数对比
生命周期函数 | data数据池的数据能否使用 | methods方法池的方法能否使用 | 用户dom页面是否加载 | 用户dom页面是否被渲染 |
---|---|---|---|---|
beforeCreate | no | no | no | no |
created | yes | yes | yes | no |
beforeMount | yes | yes | yes | no |
mounted | yes | yes | yes | yes |
beforeUpdate | yes | yes | yes | no |
updated | yes | yes | yes | yes |
12.自定义指令
- 在vue实例中的directives中配置自定义指令
- 自定义指令生命周期有三个回调函数
- bind
- inserted
- update
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自定义指令</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<p>n的值:<span v-text="n"></span></p>
<!-- 使用自定义指令 -->
<p>n放大十倍的值:<span v-big="n"></span></p>
<button @click="n++">点我n加一</button><br><br>
<!-- 自定义指令获取自动焦点事件 -->
<input type="text" v-fbind="n">
</div>
</body>
<script>
const vm = new Vue({
el:'#app',
data:{
n:1
},
// 自定义指令
// 自定义指令的this是Window
directives:{
// 参数一:dom对象,参数二表达式对象,在vue模板解析时会调用该函数
//简写
big(elemnet,binding){
elemnet.innerText = binding.value*10;
// console.log(elemnet,binding);
},
// 完整写法
fbind:{
//与dom绑定的回调函数
bind(elemnet,binding){
elemnet.value = binding.value;
},
// 渲染插入dom的回调函数
inserted(elemnet,binding){
// 在插入dom后获取焦点事件
elemnet.focus();
},
// 模板重新渲染的回调函数
update(elemnet,binding){
elemnet.value = binding.value;
}
}
}
});
</script>
</html>
13.Vue中的动画和过渡
动画
- 使用transition标签包裹需要的动画的元素
- 定义一个关键帧动画
hello-enter-active
样式写上进入的动画,使用定义好的关键帧动画hello-leave-active
样式写上离开的动画,使用定义好的关键帧动画
<template>
<div>
<button @click="isShow=!isShow">显示/隐藏</button>
<!-- 使用vue中的动画标签 ,appear属性表示第一次加载页面时也播放动画-->
<transition name="hello" appear>
<h1 v-show="isShow">关键帧动画的案例</h1>
</transition>
</div>
</template>
<script>
export default {
name:'transaction1',
data(){
return{
isShow:true
}
}
}
</script>
<style>
/* 使用关键帧动画 */
.hello-enter-active{
animation: test 1s;
}
.hello-leave-active{
animation: test 1s reverse;
}
/* 定义一个关键帧 */
@keyframes test{
from{
transform: translateX(-100%);
}
to{
transform: translateX(0);
}
}
</style>
过渡
-
使用transition标签包裹需要的动画的元素
-
进入的过渡动画
v-enter
:进入的起点v-enter-active
:进入的过渡动画v-enter-to
:进入的终点
-
离开的过渡动画
v-leave
:离开的起点v-leave-active
:离开的过渡动画v-leave-to
:离开的终点
<template>
<div>
<button @click="isShow=!isShow">显示/隐藏</button>
<!-- 使用vue中的动画标签 ,appear属性表示第一次加载页面时也播放动画,多个标签需要动画时使用transition-group,并加上key属性-->
<transition-group name="hello" appear>
<h1 v-show="isShow" key="1">过渡动画的案例1</h1>
<h1 v-show="!isShow" key="2">过渡动画的案例2</h1>
</transition-group>
</div>
</template>
<script>
export default {
name:'transaction2',
data(){
return{
isShow:true
}
}
}
</script>
<style>
h1{
background-color: orange;
}
/* 使用过渡动画 */
/* 起点和终点是同一个地方,我们可以简写 */
/* 进入的起点,离开的终点 */
.hello-enter,.hello-leave-to{
transfrom:translateX(-100%);
}
/* 进入过程的动画,离开过程的动画 */
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点,离开的起点 */
.hello-enter-to,.hello-leave{
transfrom:translateX(0);
}
</style>
多个元素需要过渡和动画
- 使用
transition-group
包裹需要动画的多个元素,并给每个元素带上不同值的key属性
<!-- 使用vue中的动画标签 ,appear属性表示第一次加载页面时也播放动画,多个标签需要动画时使用transition-group,并加上key属性-->
<transition-group name="hello" appear>
<h1 v-show="isShow" key="1">过渡动画的案例1</h1>
<h1 v-show="!isShow" key="2">过渡动画的案例2</h1>
</transition-group>
14.Vue中配置请求代理
- 修改vue.config.js文件
- 简单代理
// 简单的代理配置
devServer:{
proxy:'http:localhost:8080'
}
- 完整代理
// 完整的代理配置
devServer:{
proxy:{
// 匹配所以以/api为前缀的请求
'/api1':{
// 代理目标的基础路径
target:'http://localhost:8080',
// 是否支持webSctock,默认为true
ws:true,
// 是否在转发时修改host,true即修改为目标主机的host,false即保持原host
changeOrigin:true,
// 配置路径重写
pathRewrite:{
'^/api1':''
}
},
// 可以配置多个代理
'/api2':{
target:'http://localhost:8081',
ws:true,
changeOrigin:true,
pathRewrite:{
'^/api2':''
}
}
- 完整vue.config.js文件
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false, /*关闭语法检查*/
// 简单的代理配置
// devServer:{
// proxy:'http:localhost:8080'
// }
// 完整的代理配置
devServer:{
proxy:{
// 匹配所以以/api为前缀的请求
'/api1':{
// 代理目标的基础路径
target:'http://localhost:8080',
// 是否支持webSctock,默认为true
ws:true,
// 是否在转发时修改host,true即修改为目标主机的host,false即保持原host
changeOrigin:true,
// 配置路径重写
pathRewrite:{
'^/api1':''
}
},
// 可以配置多个代理
'/api2':{
target:'http://localhost:8081',
ws:true,
changeOrigin:true,
pathRewrite:{
'^/api2':''
}
}
}
})
15.vue组件的插槽机制
- 默认插槽
<template>
<div>
<h1>{{title}}分类</h1>
<ul>
<li v-for="(data,index) of slotData" :key="index">{{data}}</li>
</ul>
<!-- 默认插槽,没有名称 -->
<!-- 使用组件时,放到组件标签内的元素会替换到Slot标签 -->
<slot>没有使用插槽时,会显示的默认信息</slot>
</div>
</template>
<script>
export default {
name:'Slot1',
props:[
'slotData',
'title']
}
</script>
<style scoped>
div{
width: 30%;
}
</style>
- 具名插槽
<template>
<div>
<!-- 具名插槽 -->
<!-- 使用组件时,放到组件标签内的元素会替换到Slot标签 -->
<!-- 程序员可以在插槽使用具体的元素进行替换 -->
<slot name="content">没有使用插槽时,会显示的默认信息</slot>
<slot name="foot">没有使用插槽时,会显示的默认信息</slot>
</div>
</template>
<script>
export default {
name:'Slot2',
}
</script>
<style scoped>
div{
width: 30%;
}
</style>
- 作用域插槽
<template>
<div>
<!-- 作用域插槽,数据放在组件中,结构和样式放在使用者身上 -->
<!-- 使用作用域插槽,向组件使用者传递组件内部的数据 -->
<slot name="content" :SlotData="SlotData">没有使用插槽时,会显示的默认信息</slot>
<slot name="foot">没有使用插槽时,会显示的默认信息</slot>
</div>
</template>
<script>
export default {
name:'Slot3',
data(){
return{
SlotData:['牛肉面','叉烧饭','泡面']
}
}
}
</script>
<style scoped>
div{
width: 30%;
}
</style>
- 使用插槽
<template>
<div>
<!-- 默认插槽 -->
<div class="defaultSlot">
<Slot1 :slotData="foods" title="美食">
<h3>使用默认插槽,可以由使用者自定义内容</h3>
</Slot1>
<Slot1 :slotData="games" title="游戏">
<h3>使用默认插槽,游戏分类的插槽使用</h3>
</Slot1>
<Slot1 :slotData="films" title="电影">
<h3>使用默认插槽,电影分类的插槽使用</h3>
</Slot1>
</div>
<!-- 具名插槽 -->
<div class="hasName">
<Slot2>
<template slot="content">
<h1>电影分类</h1>
<ul>
<li v-for="(data,index) of films" :key="index">{{data}}</li>
</ul>
</template>
<template v-slot:foot>
<h3>使用具名插槽,电影分类的插槽使用</h3>
</template>
</Slot2>
<Slot2>
<template slot="content">
<h1>美食分类</h1>
<ul>
<li v-for="(data,index) of foods" :key="index">{{data}}</li>
</ul>
</template>
<template v-slot:foot>
<h3>使用具名插槽,美食分类的插槽使用</h3>
</template>
</Slot2>
<Slot2>
<template slot="content">
<h1>游戏分类</h1>
<ul>
<li v-for="(data,index) of games" :key="index">{{data}}</li>
</ul>
</template>
<template v-slot:foot>
<h3>使用具名插槽,游戏分类的插槽使用</h3>
</template>
</Slot2>
</div>
<!-- 作用域插槽 -->
<div class="scopeSlot">
<Slot3>
<template slot="content" scope="ComponentData">
<h1>美食分类</h1>
<ul>
<!-- 使用组件内部的数据,数据使用组件内部的,结构和样式在组件的使用处定义 -->
<li v-for="(data,index) of ComponentData.SlotData" :key="index">{{data}}</li>
</ul>
</template>
<template v-slot:foot>
<h3>使用作用域插槽,美食分类的插槽使用</h3>
</template>
</Slot3>
<Slot3>
<!-- 使用对象解构的语法获取组件内部的数据 -->
<template slot="content" scope="{SlotData}">
<h1>美食分类</h1>
<ol>
<!-- 使用组件内部的数据,数据使用组件内部的,结构和样式在组件的使用处定义 -->
<li v-for="(data,index) of SlotData" :key="index">{{data}}</li>
</ol>
</template>
<template v-slot:foot>
<h3>使用作用域插槽,美食分类的插槽使用</h3>
</template>
</Slot3>
<Slot3>
<template slot="content" scope="{SlotData}">
<h1>美食分类</h1>
<!-- 使用组件内部的数据,数据使用组件内部的,结构和样式在组件的使用处定义 -->
<h4 v-for="(data,index) of SlotData" :key="index">{{data}}</h4>
</template>
<template v-slot:foot>
<h3>使用作用域插槽,美食分类的插槽使用</h3>
</template>
</Slot3>
</div>
</div>
</template>
<script>
import Slot1 from '@/components/Slot1.vue'
import Slot2 from '@/components/Slot2.vue'
import Slot3 from '@/components/Slot3.vue'
export default {
name:'MySlot',
components:{
Slot1,
Slot2,
Slot3
},
data(){
return{
foods:['牛肉面','叉烧饭','泡面'],
games:['王者荣耀','荒野大镖客','GTA5'],
films:['流浪地球','唐人街探案','杀手不太冷']
}
}
}
</script>
<style>
div.defaultSlot,.hasName,.scopeSlot{
display: flex;
justify-content: space-around;
}
</style>
16.Vuex插件
创建Vuex实例并使用
- 导入Vue和Vuex,并在Vue使用Vuex
- 创建Vuex.store实例
- actions:存放store.dispatch的回调函数
- mutations:存放store.commit的回调函数
- state:存放共享的数据
- getters:存放由state中数据计算得来的计算数据
import Vue from 'vue'
import Vuex from 'vuex'
// 创建Vuex实例前在Vue中使用Vuex
Vue.use(Vuex);
// 创建Vuex的store实例,并导出
export default new Vuex.Store({
// Vuex.$store.dispatch()的回调函数池
actions:{
// 第一个参数是Vuex的上下文精简版的store,第二个参数开始是程序员的传参
incrementOdd(context,value){
if(context.state.sum%2){
// 调用commit函数进行具体数据处理
context.commit('increment',value);
}
},
incrementWait(context,value){
setTimeout(()=>{
context.commit('increment',value);
},500);
}
},
// Vuex.$store.commit()的回调函数池
mutations:{
// 第一个参数是当前Vuex实例的state数据池,第二个参数开始是程序员的传参
increment(state,value){
// console.log(state);
state.sum += value;
},
decrement(state,value){
state.sum -= value;
}
},
// 类似于Vue实例的data数据池
state:{
sum:0
},
// 类似与Vue实例中的computed数据
getters:{
// 参数是Vuex.store.state数据池
bigSum(state){
// console.log(state);
return state.sum*10;
}
}
});
在组件中使用Vuex.store实例共享数据
<template>
<div>
<h1>当前的和为:{{$store.state.sum}}</h1>
<h3>放大十倍:{{$store.getters.bigSum}}</h3>
<select v-model.number="value">
<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{
value:1,
}
},
methods:{
increment(){
// console.log(this.value);
//没有其他的复杂逻辑,直接调用Vuex.mutations中的回调函数
// 第一个参数是actions回调函数池中回调函数的名称
this.$store.commit('increment',this.value)
},
decrement(){
this.$store.commit('decrement',this.value)
},
incrementOdd(){
// 第一个参数是mutations回调函数池中毁掉函数的名称
this.$store.dispatch('incrementOdd',this.value);
},
incrementWait(){
this.$store.dispatch('incrementWait',this.value);
}
}
}
</script>
<style>
button{
margin: 10px;
}
</style>
使用简写方式
- 导入Vuex的简写依赖
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
使用mapState对获取State数据进行简写
- 需要给State中的数据起别名时使用对象的简写方式
- 使用与State中数据的名称时使用数组的简写方式
computed:{
// 对象的方式
// ...mapState({sum:'sum'})
//数组的方式
...mapState(['sum'])
}
使用mapGetters对获取store.getters中的计算数据进行简写
computed:{
// ...mapGetters({bigSum:'bigSum'})
...mapGetters(['bigSum'])
}
使用mapActions对dispatch的回调函数调用进行简写
// dispatch简写
// ...mapActions({incrementOdd:'incrementOdd',incrementWait:'incrementWait'})
...mapActions(['incrementOdd','incrementWait'])
使用mapMutations对commit回调函数调用进行简写
// commit简写
// ...mapMutations({increment:'increment',decrement:'decrement'}),
...mapMutations(['increment','decrement'])
完整简写
<template>
<div>
<h1>当前的和为:{{sum}}</h1>
<h3>放大十倍:{{bigSum}}</h3>
<select v-model.number="value">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increment(value)">+</button>
<button @click="decrement(value)">-</button>
<button @click="incrementOdd(value)">奇数时+</button>
<button @click="incrementWait(value)">等一等再加</button>
</div>
</template>
<script>
// 导入Vuex中map映射的依赖
import {mapState,mapGetters,mapActions,mapMutations} from 'vuex'
export default {
name:'Count',
data(){
return{
value:1,
}
},
methods:{
// increment(){
// // console.log(this.value);
// //没有其他的复杂逻辑,直接调用Vuex.mutations中的回调函数
// // 第一个参数是actions回调函数池中回调函数的名称
// this.$store.commit('increment',this.value)
// },
// decrement(){
// this.$store.commit('decrement',this.value)
// },
// commit简写
// ...mapMutations({increment:'increment',decrement:'decrement'}),
...mapMutations(['increment','decrement']),
// incrementOdd(){
// // 第一个参数是mutations回调函数池中毁掉函数的名称
// this.$store.dispatch('incrementOdd',this.value);
// },
// incrementWait(){
// this.$store.dispatch('incrementWait',this.value);
// }
// dispatch简写
// ...mapActions({incrementOdd:'incrementOdd',incrementWait:'incrementWait'})
...mapActions(['incrementOdd','incrementWait'])
},
computed:{
// 对象的方式
// ...mapState({sum:'sum'})
//数组的方式
...mapState(['sum']),
// ...mapGetters({bigSum:'bigSum'})
...mapGetters(['bigSum'])
}
}
</script>
<style>
button{
margin: 10px;
}
</style>
模块化+名称空间
-
目的:让代码更好维护,让多种数据分类更加明确。
-
修改
store.js
-
在每个模块都开启命名空间
-
使用模块化方式创建Vuex.Store实例
const countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } } const personAbout = { namespaced:true,//开启命名空间 state:{ ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { countAbout, personAbout } })
-
开启命名空间后,组件中读取state数据:
//方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: ...mapState('countAbout',['sum','school','subject']),
-
开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])
-
开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
-
开启命名空间后,组件中调用commit
//方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
17.Router插件
- 安装路由依赖
npm install vue-router
- 路由的切换会导致组件的销毁和创建,我们也可以使用
<keep-alive></keep-alive>
标签指定需要缓存的组件,避免路由切换导致的数据丢失
配置vue-router路由插件
- 导入vue和vue-router
- 在Vue原型上使用vue-router插件
- 导入需要使用的组件
- 创建路由实例
import VueRouter from 'vue-router'
import Vue from 'vue'
// 引入组件
import Home from '@/pages/Home.vue'
import About from '@/pages/About.vue'
import News from '@/pages/News.vue'
import Message from '@/pages/Message.vue'
// 使用路由插件
Vue.use(VueRouter)
// 创建VueRouter实例
export default new VueRouter({
routes:[
// 一级路由
{
// 路由的名称
name:'home',
// 路由的路径
path:'/home',
// 路由使用的组件
component:Home,
// 多级路由
children:[
//二级路由
{
name:'New',
path:'news',
component:News
},
{
name:'Message',
path:'Messages',
component:Message
}
]
},
{
name:'about',
path:'/about',
component:About
}
]
});
使用路由
- 使用一级路由
<template>
<div>
<Banner></Banner>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 路由跳转标签,to属性表示跳转的目标路由,active-class属性控制选中菜单的样式 -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link><br>
<router-link class="list-group-item" to="/home">Home</router-link>
</div>
</div>
</div>
<div class="row">
<!-- 路由组件的占位符 -->
<router-view></router-view>
</div>
</div>
</template>
<script>
import Banner from '@/components/Banner.vue'
export default {
name:'MyRouter',
components:{
Banner
}
}
</script>
<style>
</style>
- 使用二级路由
<template>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<div>
<h2>Home组件内容</h2>
<div>
<ul class="nav nav-tabs">
<li>
<!-- 二级路由,to属性需要带上完整路由路径 -->
<router-link class="list-group-item" active-calss="active" to="/home/News">News</router-link>
</li>
<li>
<router-link class="list-group-item" active-calss="active" to="/home/Messages">Message</router-link>
</li>
</ul>
</div>
</div>
<!-- 路由组件占位符 -->
<router-view></router-view>
</div>
</div>
</div>
</template>
<script>
export default {
name:'Home'
}
</script>
<style>
</style>
使用路由传参
- query式传参不需要在路由中进行配置
- params传参需要在路由中进行额外配置
- 传参组件
<template>
<div>
<ul>
<li v-for="m of messageList" :key="m.id">
<!-- query参数 -->
<!-- 路由传参第一种写法,字符串写法 -->
<!-- <router-link :to="`/home/messages/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> -->
<!-- 对象写法 -->
<!-- <router-link :to="{
path:'/home/messages/detail',
query:{
id:m.id,
title:m.title
}
}">{{m.title}}</router-link> -->
<!-- params参数,需要在路由中配置路径参数的名称 -->
<!-- 字符串写法,路径参数 -->
<!-- <router-link :to="`/home/messages/detail/${m.id}/${m.title}`">{{m.title}}</router-link> -->
<!-- 对象写法 -->
<!-- 路由使用params式对象传参,必须使用路由的名称,不能使用路由的路径 -->
<router-link :to="{
name:'Detail',
params:{
id:m.id,
title:m.title
}
}">{{m.title}}</router-link>
</li>
</ul>
<div>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name:'Message',
data(){
return{
messageList:[
{id:'001',title:'message1'},
{id:'002',title:'message2'},
{id:'003',title:'message3'},
]
}
}
}
</script>
<style>
</style>
- params传参的路由配置
import VueRouter from 'vue-router'
import Vue from 'vue'
// 引入组件
import Home from '@/pages/Home.vue'
import About from '@/pages/About.vue'
import News from '@/pages/News.vue'
import Message from '@/pages/Message.vue'
import Detail from '@/pages/Detail.vue'
// 使用路由插件
Vue.use(VueRouter)
// 创建VueRouter实例
export default new VueRouter({
routes:[
// 一级路由
{
// 路由的名称
name:'home',
// 路由的路径
path:'/home',
// 路由使用的组件
component:Home,
// 多级路由
children:[
// 二级路由
{
name:'New',
path:'news',
component:News
},
{
name:'Message',
path:'Messages',
component:Message,
children:[
// 三级路由
{
name:'Detail',
//params传参的路由配置
path:'detail/:id/:title',
component:Detail
}
]
}
]
},
{
name:'about',
path:'/about',
component:About
}
]
});
- 接受参数组件
- 通过
$route对象可以获取路由的传递的参数
<template>
<div>
<h3>信息id:{{id}}</h3>
<h3>信息标题:{{title}}</h3>
</div>
</template>
<script>
export default {
name:'Detail',
data(){
return{
// $route对象中存放着路由的传参
id:this.$route.query.id||this.$route.params.id,
title:this.$route.query.id||this.$route.params.title
}
},
mounted(){
// 获取当前路由信息
console.log(this.$route);
}
}
</script>
<style>
</style>
路由使用props传参
- 路由需要声明传递的参数
- 组建需要使用props进行参数接收
import VueRouter from 'vue-router'
import Vue from 'vue'
// 引入组件
import Home from '@/pages/Home.vue'
import About from '@/pages/About.vue'
import News from '@/pages/News.vue'
import Message from '@/pages/Message.vue'
import Detail from '@/pages/Detail.vue'
// 使用路由插件
Vue.use(VueRouter)
// 创建VueRouter实例
export default new VueRouter({
routes:[
// 一级路由
{
// 路由的名称
name:'home',
// 路由的路径
path:'/home',
// 路由使用的组件
component:Home,
// 多级路由
children:[
// 二级路由
{
name:'New',
path:'news',
component:News
},
{
name:'Message',
path:'Messages',
component:Message,
children:[
// 三级路由
{
name:'Detail',
path:'detail/:id/:title',
component:Detail,
// 路由使用props传递参数
// 第一种方式,对象写法
// props:{id_prop:'001',title_prop:'通过路由prop传递的参数'}
// 第二种布尔值的写法,收集路由所有通过params传递的参数,进行prop进行传递
// props:true
// 第三种写法,函数的写法,该回调函数会将$route对象作为参数,函数的返回值作为传递的数据
props($route){
return{
id_prop:$route.params.id,
title_prop:$route.params.title
}
}
}
]
}
]
},
{
name:'about',
path:'/about',
component:About
}
]
});
- 组件声明接受路由通过prop传递的数据
<template>
<div>
<h3>信息id:{{id}}</h3>
<h3>信息标题:{{title}}</h3>
<h3>prop传递的id:{{id_prop}}</h3>
<h3>prop传递的标题:{{title_prop}}</h3>
</div>
</template>
<script>
export default {
name:'Detail',
// 接受路由通过prop传递的数据
props:['id_prop','title_prop'],
data(){
return{
// $route对象中存放着路由的传参
id:this.$route.query.id||this.$route.params.id,
title:this.$route.query.id||this.$route.params.title
}
},
mounted(){
// 获取当前路由信息
console.log(this.$route);
}
}
</script>
<style>
</style>
route-link
标签的replace属性
- 通过给route-link标签添加replace属性,可以让vue-router跳转时使用新的路由地址替换历史路由栈栈顶的记录
- 默认是使用push向历史路由栈压入一条新的路由记录
编程式路由
- 通过
$router
路由器对象的push或replace函数向路由历史记录栈中添加一条路由记录实现路由的跳转
<template>
<div>
<ul>
<li v-for="m of messageList" :key="m.id">
<!-- query参数 -->
<!-- 路由传参第一种写法,字符串写法 -->
<!-- <router-link :to="`/home/messages/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> -->
<!-- 对象写法 -->
<!-- <router-link :to="{
path:'/home/messages/detail',
query:{
id:m.id,
title:m.title
}
}">{{m.title}}</router-link> -->
<!-- params参数,需要在路由中配置路径参数的名称 -->
<!-- 字符串写法,路径参数 -->
<!-- <router-link :to="`/home/messages/detail/${m.id}/${m.title}`">{{m.title}}</router-link> -->
<!-- 对象写法 -->
<!-- 路由使用params式对象传参,必须使用路由的名称,不能使用路由的路径 -->
<router-link :to="{
name:'Detail',
params:{
id:m.id,
title:m.title
}
}">{{m.title}}</router-link>
<!-- 编程式路由 -->
<button @click="pushShow(m)">push实现路由跳转</button><br>
<button @click="replaceShow(m)">replace实现路由跳转</button>
</li>
</ul>
<div>
<router-view></router-view>
</div>
</div>
</template>
<script>
export default {
name:'Message',
data(){
return{
messageList:[
{id:'001',title:'message1'},
{id:'002',title:'message2'},
{id:'003',title:'message3'},
]
}
},
methods:{
pushShow(m){
// 使用路由器的$route.push()跳转路由
this.$router.push({
// 指定路由的名称
name:'Detail',
params:{
id:m.id,
title:m.id
}
});
},
replaceShow(m){
// 使用路由器的$router.replace()跳转路由
this.$router.replace({
// 指定路由的名称
name:'Detail',
params:{
id:m.id,
title:m.id
}
});
}
}
}
</script>
- 其他路由器函数,实现编程式路由
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退
路由中的新生命周期函数
activated
路由组件被激活时触发deactivated
路由组件失活时触发
全局(前置/后置)路由守卫
- 全局前置路由守卫
beforeEach((to,from,next)=>{})
在进入路由前调用 - 全局后置路由守卫
afterEach(to,from)
在进入路由后调用
// 全局前置路由守卫
router.beforeEach((to,from,next)=>{
// to是目标路由对象,from当前路由对象
console.log(to,from);
// 通过to和from对象,我们可以在这里进行权限校验等逻辑操作
// next是放行函数,不调用则不会切换路由
next();
});
//全局后置路由守卫
router.afterEach((to,from)=>{
// to是目标路由对象,from当前路由对象
console.log(to,from);
// 后置路由守卫没有next函数,因为已经切换路由,不需要进行放行
// 我们可以在这里进行一些后续的善后操作,比如设置切换路由后网页的标题
});
独享路由守卫
- 在路由中配置
beforeEnter()
函数
{
name:'about',
path:'/about',
component:About,
// 我们可以在路由meta中设置程序员需要的自定义数据
meta:{
// 自定义一个字段,标识需要进行鉴权
isAthu:true
},
// 在路由中配置独享路由守卫,参数与全局路由守卫一致
beforeEnter(to,from,next){
// 我们也可以自定义鉴权等逻辑操作
if(to.meta.isAthu){
// 需要鉴权则不允许访问
return;
}
next();
}
}
组件内路由守卫
<template>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<h2>我是About的内容</h2>
</div>
</div>
</div>
</template>
<script>
export default {
name:'About',
// 组件内进入路由守卫
beforeRouteEnter(to,from,next){
//方向
next();
},
// 组件内离开路由守卫
beforeRouteLeave(to,from,next){
//方向
next();
}
}
</script>
<style>
</style>
- 完整路由配置
import VueRouter from 'vue-router'
import Vue from 'vue'
// 引入组件
import Home from '@/pages/Home.vue'
import About from '@/pages/About.vue'
import News from '@/pages/News.vue'
import Message from '@/pages/Message.vue'
import Detail from '@/pages/Detail.vue'
// 使用路由插件
Vue.use(VueRouter)
// 创建VueRouter实例
const router = new VueRouter({
routes:[
// 一级路由
{
// 路由的名称
name:'home',
// 路由的路径
path:'/home',
// 路由使用的组件
component:Home,
// 多级路由
children:[
// 二级路由
{
name:'New',
path:'news',
component:News
},
{
name:'Message',
path:'Messages',
component:Message,
children:[
// 三级路由
{
name:'Detail',
path:'detail/:id/:title',
component:Detail,
// 路由使用props传递参数
// 第一种方式,对象写法
// props:{id_prop:'001',title_prop:'通过路由prop传递的参数'}
// 第二种布尔值的写法,收集路由所有通过params传递的参数,进行prop进行传递
// props:true
// 第三种写法,函数的写法,该回调函数会将$route对象作为参数,函数的返回值作为传递的数据
props($route){
return{
id_prop:$route.params.id,
title_prop:$route.params.title
}
}
}
]
}
]
},
{
name:'about',
path:'/about',
component:About,
// 我们可以在路由meta中设置程序员需要的自定义数据
meta:{
// 自定义一个字段,标识需要进行鉴权
isAthu:true
},
// 在路由中配置独享路由守卫,参数与全局路由守卫一致
beforeEnter(to,from,next){
// 我们也可以自定义鉴权等逻辑操作
if(to.meta.isAthu){
// 需要鉴权则不允许访问
return;
}
next();
}
}
]
});
// 全局前置路由守卫
router.beforeEach((to,from,next)=>{
// to是目标路由对象,from当前路由对象
console.log(to,from);
// 通过to和from对象,我们可以在这里进行权限校验等逻辑操作
// next是放行函数,不调用则不会切换路由
next();
});
//全局后置路由守卫
router.afterEach((to,from)=>{
// to是目标路由对象,from当前路由对象
console.log(to,from);
// 后置路由守卫没有next函数,因为已经切换路由,不需要进行放行
// 我们可以在这里进行一些后续的善后操作,比如设置切换路由后网页的标题
});
export default router;
路由的两种工作模式
- 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
- hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
- hash模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
- history模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
- 配置路由工作模式
// 创建VueRouter实例
const router = new VueRouter({
//路由工作模式
mode:'hash',//hash或者history
routes:[
//路由配置
{},
{}
]
})
Vue3
1.main.js
入口文件变化
- 创建实例使用createApp函数,而不是使用
new Vue()
的方式
// 引入vue工厂函数
import { createApp } from 'vue'
// 引入组件
import App from './App.vue'
// 使用vue工厂函数创建组件并挂载
createApp(App).mount('#app')
2.setup()
配置项
- vue3添加配置vue实例的方式
- 我们可以在其中配置vue2中几乎所有的数据配置项,包括data数据池,methods方法池等等
// vue3的新配置项
setup(){
// 简单的数据使用ref()函数创建,返回的是refImpl对象
let name = ref('杨逸');
let age = ref('20');
// console.log(name);
// 使用ref()函数定义对象,底层会去调用reactive()函数帮助创建,返回的是Proxy对象
const user = ref({
name:'李四',
job:'安卓开发'
});
// console.log(user);
// 不仅可以定义数据,还可以定义函数
function showInfo(){
console.log('定义在setup中的函数被调用');
};
// 修改定义的普通数据
function update(){
// 因为创建数据时使用ref()函数返回的是refImpl对象,所以修改时需要使用.value的形式进行修改
name.value = '修改后的名字';
age.value = 200;
console.log('修改ref创建的普通数据');
};
//修改对象类型的数据
function updateObject(){
// 因为创建对象类型数据使用的reactive()函数,使用修改时只需要使用一层.value就可以拿到对象的数据进行修改
user.value.name = '对象修改后的name';
user.value.job = '对象修改后的job';
console.log('修改reactive创建的对象数据');
}
// 最后返回将数据暴露出去
return{
name,
age,
user,
showInfo,
update,
updateObject
};
//第二种返回值,返回reader函数渲染的结果,使用这种方式会替代组件模板中的内容
// return h => h('渲染内容');
}
- vue2中的data和methods配置项同时也可以使用,没有被废除
data(){
return{
job:'java开发工程师',
salary:20000
}
},
methods:{
// 这里是可以通过this访问到setup中的数据,而setup不能访问到外面的数据
showMessage(){
console.log('methods()中的函数被调用');
}
}
- 完整代码
<template>
<div>
<h1>Vue3的测试页面</h1>
<h3>setup()中的普通数据{{ name }}</h3>
<h3>setup()中的普通数据{{ age }}</h3>
<h3>setup()中的对象数据{{ user.name }}-{{ user.job }}</h3>
<button @click="update">修改setup中的普通数据</button><br><br>
<button @click="updateObject">修改setup中的对象数据</button><br><br>
<button @click="showInfo">setup()中的函数</button>
<hr>
<h3>data()中的数据{{ job }}</h3>
<h3>data()中的数据{{ salary }}</h3>
<button @click="showMessage">methods()中的函数</button>
</div>
</template>
<script>
// 导入reader的渲染函数h
// import {h} from 'vue'
//导入ref()函数
import {ref,reactive} from 'vue'
export default {
name:'Test',
data(){
return{
job:'java开发工程师',
salary:20000
}
},
methods:{
// 这里是可以通过this访问到setup中的数据,而setup不能访问到外面的数据
showMessage(){
console.log('methods()中的函数被调用');
}
},
// vue3的新配置项
setup(){
// 简单的数据使用ref()函数创建,返回的是refImpl对象
let name = ref('杨逸');
let age = ref('20');
// console.log(name);
// 使用ref()函数定义对象,底层会去调用reactive()函数帮助创建,返回的是包含Proxy对象的refImpl对象
// const user = ref({
// name:'李四',
// job:'安卓开发'
// });
// 使用reactive()函数直接创建,返回的是Proxy对象
const user = reactive({
name:'李四',
job:'安卓开发'
});
console.log(user);
// 不仅可以定义数据,还可以定义函数
function showInfo(){
console.log('定义在setup中的函数被调用');
};
// 修改定义的普通数据
function update(){
// 因为创建数据时使用ref()函数返回的是refImpl对象,所以修改时需要使用.value的形式进行修改
name.value = '修改后的名字';
age.value = 200;
console.log('修改ref创建的普通数据');
};
//修改对象类型的数据
function updateObject(){
// 因为创建对象类型数据使用的reactive()函数,使用修改时只需要使用一层.value就可以拿到对象的数据进行修改
// user.value.name = '对象修改后的name';
// user.value.job = '对象修改后的job';
//使用reactive()创建的对象是Proxy对象,可以直接使用对应的属性
user.name = '对象修改后的name';
user.job = '对象修改后的job';
console.log('修改reactive创建的对象数据');
}
// 最后返回将数据暴露出去
return{
name,
age,
user,
showInfo,
update,
updateObject
};
//第二种返回值,返回reader函数渲染的结果,使用这种方式会替代组件模板中的内容
// return h => h('渲染内容');
}
}
</script>
<style>
</style>
ref()
函数和refImpl对象
- 导入
ref()
函数
import {ref} from 'vue'
ref()
通过数据劫持实现响应式数据- 在vue3使用ref()函数定义普通响应式数据
// 简单的数据使用ref()函数创建,返回的是refImpl对象
let name = ref('杨逸');
let age = ref('20');
// 使用ref()函数定义对象,底层会去调用reactive()函数帮助创建,返回的是包含Proxy对象的refImpl对象
const user = ref({
name:'李四',
job:'安卓开发'
});
reactive()
函数和Proxy对象
- 导入
reactive()
函数
import {reactive} from 'vue'
reactive()
通过代理和反射实现响应式数据- 在vue3中使用reactive()函数定义对象(数组)响应式数据
// 使用reactive()函数直接创建,返回的是Proxy对象
const user = reactive({
name:'李四',
job:'安卓开发'
});
3.Vue3.0中的响应式原理
vue2.x的响应式
-
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
-
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
Vue3.0的响应式
-
实现原理:
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
-
MDN文档中描述的Proxy与Reflect:
-
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
-
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom'
-
-
4.reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
。
- ref定义的数据:操作数据需要
5.setup的两个注意点
-
setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
-
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
6.计算属性与监视
1.computed函数
-
与Vue2.x中computed配置功能一致
-
写法
import {computed} from 'vue' setup(){ ... //计算属性——简写 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //计算属性——完整 let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) }
2.watch函数
-
与Vue2.x中watch配置功能一致
-
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
3.watchEffect函数
-
watch的套路是:既要指明监视的属性,也要指明监视的回调。
-
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
-
watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })
7.生命周期
1
- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
8.自定义hook函数
-
什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
-
类似于vue2.x中的mixin。
-
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
9.toRef
-
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
-
语法:
const name = toRef(person,'name')
-
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
-
扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
三、其它 Composition API
1.shallowReactive 与 shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
-
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
2.readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
3.toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
4.customRef
-
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
-
实现防抖效果:
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 return customRef((track,trigger)=>{ return{ get(){ track() //告诉Vue这个value值是需要被“追踪”的 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告诉Vue去更新界面 },delay) } } }) } let keyword = myRef('hello',500) //使用程序员自定义的ref return { keyword } } } </script>
5.provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
6.响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
四、Composition API 的优势
1.Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
2.Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
五、新的组件
1.Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
2.Teleport
-
什么是Teleport?——
Teleport
是一种能够将我们的组件html结构移动到指定位置的技术。<teleport to="移动位置"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport>
3.Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹组件,并配置好default
与fallback
<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
-
六、其他
1.全局API的转移
-
Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
2.其他改变
-
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。