Vue2学习
视频教程来自尚硅谷:尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili,vue2文档:介绍 — Vue.js
1. vue基础知识和原理
1.1 初识vue
想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
app容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
app容器里的代码被称为【Vue模板】
Vue实例和容器是一一对应的,不能一对多和多对一
真实开发中只有一个Vue实例,并且会配合着组件一起使用
- 是Vue的语法:插值表达式,可以读取到data中的所有属性
一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新(Vue实现的响应式)
代码:这里是直接引入开发版本的vue.js,可以去官网下载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>01</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--
容器和vue实例是一对一的,不能一对多和多对一
-->
<!--准备好一个容器-->
<div id="app">
<h1>Hello,{{name}} {{op}} {{wifu}}</h1>
</div>
<script type="text/javascript">
<!--关闭生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
name: 'ZQG',
op: 'BC',
wifu: 'YM'
}
});
</script>
</body>
</html>
1.2 模板语法
Vue模板语法有2大类:
- 插值语法:
功能:用于解析标签体内容
写法:,xxx是js表达式,且可以直接读取到data中的所有属性
- 指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板语法</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>插值语法</h1>
<h2>Hello,{{name}}</h2>
<hr/>
<h1>指令语法</h1>
<a v-bind:href="link.url">去{{link.title}}</a>
<!-- v-bind简写 -->
<br/>
<a :href="link.url">去{{link.title}}2</a>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
name: 'ZQG',
link:{
title: '百度',
url: 'https://www.baidu.com'
}
}
});
</script>
</body>
</html>
1.3 数据绑定
Vue中有2种数据绑定的方式:
- 单向绑定(v-bind):数据只能从data流向页面
- 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
注意:
1.双向绑定一般都应用在表单类元素上(如:input、select等)
2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板语法</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="app">
单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name"><br/>
<!-- 简写-->
单向数据绑定2:<input type="text" :value="name"><br/>
<!-- 简写 v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值-->
双向数据绑定2:<input type="text" v-model="name"><br/>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
name: 'ZQG'
}
});
</script>
</body>
</html>
1.4 el与data的两种写法
el有2种写法
- new Vue时候配置el属性
- 先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值
data有2种写法
对象式
函数式
注意:在组件中,data必须使用函数式。一个重要原则:由Vue管理的函数不能是箭头函数,因为这样写this就不再是Vue实例了。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>el和data的两种写法</title> <script type="text/javascript" src="js/vue.js"></script> </head> <body> <!--准备好一个容器--> <div id="app"> <h1>Hello,{{name}} </h1> </div> <script type="text/javascript"> Vue.config.productionTip = false; //el的两种写法 /* const vm = new Vue({ // el: '#app',//第一种写法 data: { //数据,给el 容器中的元素提供数据 name: 'ZQG' } }); console.log(vm); vm.$mount('#app') //第二种写法,挂载容器 */ // data的两种写法 const vm2 = new Vue({ el: '#app', // data: { //第一种写法,对象 // name: 'ZQG' // } // 第二种写法,函数式 // data: function () {} data() { console.log(this) return { name: 'ZQG' } } }); </script> </body> </html>
1.5 MVVM模型
M:模型(Model) :data中的数据
V:视图(View) :模板代码
- VM:视图模型(ViewModel):Vue实例

data中的所有属性都在vm中。
vm的所有属性,包括Vue原型上所有属性,Vue模板都可以直接使用。
1.6 数据代理
通过一个对象代理对另一个对象中的属性的操作(读/写)。
主要通过js方法:Object.defineProperty()实现的,需要先去了解Object.defineProperty()相关知识,属性标志,属性描述符,getter,setter.
简单介绍一下:
属性标志:
对象属性(properties),除 value 外,还有三个特殊的特性(attributes),也就是所谓的“标志”
writable — 如果为 true,则值可以被修改,否则它是只可读的,默认为false
enumerable — 如果为 true,则表示是可以遍历的,可以在for… .in Object.keys()中遍历出来,默认为false
configurable — 如果为 true,则此属性可以被删除,这些特性也可以被修改,否则不可以,默认为false
<script type="text/javascript">
let num = 18;
let person = {
name: 'ZQG',
//age: 18,
sex: '男'
};
Object.defineProperty(person, 'age',{
value: num,
// enumerable: true, // 是否可枚举,默认为false
// configurable: true,// 是否可以删除,默认为false
// writable: true// 能否修改,默认为false
})
console.log(person);
</script>
如图:

如果不将相应的配置设置为true,修改不起作用,删除也返回false,也无法枚举。
使用getter和setter时:
<script type="text/javascript">
let num = 18;
let person = {
name: 'ZQG',
//age: 18,
sex: '男'
};
Object.defineProperty(person, 'age',{
//value: num,
// enumerable: true, // 是否可枚举,默认为false
// configurable: true,// 是否可以删除,默认为false
// writable: true// 能否修改,默认为false
get() {
console.log('读取age属性');//person.age 起作用
return num;
},
set(value) {
console.log('设置age属性,值是:', value);//person.age = 20 起作用
num = value;
}
})
console.log(person);
</script>

每次访问person.age时,会触发getter函数,返回num值;当设置person.age = xx值时,会把值设置给num,这样再次读取会返回最新的num值,(直接设置num值,获取age值也是返回设置后的num值)这样就实现了数据代理。
Vue中的数据代理
Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
Vue中数据代理的好处:更加方便的操作data中的数据
基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
我们代码中的data实际和Vue实例中的_data是相等的,所以取值,Vue为了代码写的方便,通过数据代理将_data的属性值,直接放在vm中,所以直接取值用就可以了。
验证代码:为了验证data和我们代码中的数据(person)是一样的,所以把person定义在外面,这样可以直接可以_data==person作比较
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据代理</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>Hello,{{name}},{{age}},{{sex}}</h1>
</div>
<script type="text/javascript">
let person = {
name: 'ZQG',
age: 18,
sex: '男'
};
const vm = new Vue({
el: '#app',
data: person
});
console.log(vm)
</script>
</body>
</html>



1.7 事件处理
事件的基本使用:
- 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数不要用箭头函数,这会导致this不再是vm了
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象
- @click=”show” 和@click=”show($event)”,效果一致,但是后者可以传参
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件处理</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>Hello,{{name}},欢迎来到{{address}}</h1>
<button v-on:click="showInfo">点我</button>
<button @click="showInfo">点我2(不传参)</button>
<button @click="showInfo3($event,666)">点我3(传参)</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: {
name: 'ZQG',
address: '支配剧场'
},
methods: {
showInfo: function (event) {
alert('Hello,Vue!');
console.log(event);// event对象
console.log(this);// this指向当前Vue实例
},
showInfo3(event, num) {
console.log(event.target.innerText);// event对象
//console.log(this);// this指向当前Vue实例
alert(num)
}
}
});
</script>
</body>
</html>
Vue中的事件修饰符
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- once:事件只触发一次(常用)
- capture: 使用事件的捕获模式
- self: 只有event.target是当前的操作元素时才触发事件
- passive: 事件的默认行为立即执行,无需等待事件回调完毕
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件处理</title>
<script type="text/javascript" src="js/vue.js"></script>
<style type="text/css">
* {
margin-top: 10px;
}
.dd{
width: 100px;
height: 80px;
background-color: red;
}
.aa{
width: 100px;
height: 60px;
background-color: skyblue;
}
.bb{
width: 100px;
height: 20px;
background-color: green;
}
.ll{
width: 150px;
height: 100px;
background-color: orange;
overflow: auto;
}
li{
height: 50px;
}
</style>
</head>
<body>
<div id="app">
<h1>Hello,{{name}},欢迎来到{{address}}</h1>
<!-- 阻止事件-->
<a href="https://www.baidu.com" @click.prevent="showInfo">点我跳转</a>
<div class="dd" @click="showInfo">
<!-- 阻止事件冒泡 -->
<button @click.stop="showInfo">点我</button>
<!-- 修饰符可以连续写-->
<a href="https://www.baidu.com" @click.prevent.stop="showInfo">点我跳转</a>
</div>
<!-- 事件只触发一次-->
<button @click.once="showInfo">点我</button>
<!-- 原本: 事件捕获=》事件冒泡(调用),所以打印2再打印1. 使用capture后:在捕获的时候就会调用,此时先打印1后打印2 -->
<div class="aa" @click.capture="showMsg(1)">
div1
<div class="bb" @click="showMsg(2)">
div2
</div>
</div>
<div class="dd" @click.self="showInfo">
<!-- 只有event.target是当前的操作元素时才触发事件 -->
<button @click="showInfo">点我</button>
</div>
<!-- @scroll 滚动条,@wheel 鼠标滚轮-->
<ul @wheel.passive="work" class="ll">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: {
name: 'ZQG',
address: '支配剧场'
},
methods: {
showInfo: function (event) {
alert('Hello,Vue!');
console.log(event);// event对象
console.log(this);// this指向当前Vue实例
},
showMsg: function (msg) {
alert(msg);
},
work: function (event) {
for (let i = 0; i < 10000; i++) {
console.log('#');
}
}
}
});
</script>
</body>
</html>
1.8 键盘事件
键盘事件语法糖:@keydown,@keyup
1.Vue中常用的按键别名:
- 回车 => enter
- 删除 => delete(捕获删除和退格键)
- 退出 => esc
- 空格 => space
- 换行 => tab (特殊,必须配合keydown去使用)
- 上 => up
- 下 =>down
- 左 =>left
- 右 =>right
2.Vue未提供别名的键,可以使用按键的原始key值去绑定,但是要注意转为kebab-case(短横线命名)
3.系统修饰键(用法特殊):ctrl,alt,shift,meta
3.1配合keyup使用:按下修饰键的同时,再按下其他键,随后释放,事件才会触发
3.2配合keydown使用:正常触发
4.可以使用keyCode去指定具体的按键(不推荐)
5.Vue.config.keyCode.自定义键名=键码,可以定制按键别名
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>键盘事件</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>Hello,{{name}}</h1>
<input type="text" placeholder="按下回车提示输入" @keyup="showInfo">
<input type="text" placeholder="按下tab提示输入" @keydown.tab="showInfo">
<!-- 连写 ctrl + y -->
<input type="text" placeholder="按下ctrl提示输入" @keyup.ctrl.y="showInfo">
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
Vue.config.keyCodes.dsb = 13;//自定义按键,dsb替换成enter @keyup.dsb="",不推荐使用
let person = {
name: 'ZQG'
};
new Vue({
el: '#app',
data: person,
methods: {
showInfo(e) {
console.log(e.key,e.keyCode,e.target.value)
}
}
});
</script>
</body>
</html>
1.9 计算属性
定义:要用的属性不存在,要通过已有属性计算得来。
原理:底层借助了Objcet.defineProperty方法提供的getter和setter
get函数什么时候执行?
(1).初次读取时会执行一次
(2).当依赖的数据发生改变时会被再次调用
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
备注:
计算属性最终会出现在vm上,直接读取使用即可
如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>姓名案例</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
姓: <input type="text" v-model="firstName"/><br>
名:<input type="text" v-model="lastName"/><br>
全名:<span>{{fullName}}</span><br>
全名:<span>{{fullName2}}</span>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
const vm = new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
firstName: 'ZQG',
lastName: 'YM'
},
computed:{
fullName:{
//初次读取fullName时,get方法会调用一次
// 所依赖的数据发生变化时,get方法会调用一次
//其他情况,会读取缓存的数据
get(){
console.log('get调用');
console.log(this);
return this.firstName + '-' + this.lastName;
},
//set方法不是必须写的,如果计算属性确定没有修改的场景,可以不写set
// 当fullName被修改时,set方法会调用一次
set(value){
const names = value.split('-');
this.firstName = names[0];
this.lastName = names[1];
}
},
//简写,不包含set方法
fullName2() {
return this.firstName + '-' + this.lastName;
}
}
});
</script>
</body>
</html>
1.10 监视属性
监视属性watch:
- 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
- 监视的属性必须存在,才能进行监视
- 监视的两种写法:
- (1).new Vue时传入watch配置
- (2).通过vm.$watch监视
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>天气案例</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--
容器和vue实例是一对一的,不能一对多和多对一
-->
<!--准备好一个容器-->
<div id="app">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
const vm = new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽';
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
watch: {
isHot: {
// 监听isHot属性的变化
handler(newValue, oldValue) {
console.log('isHot', newValue, oldValue);
},
immediate: true, //初始化时执行handler,默认false
deep: true //深度监听
},
//简写,不需要immediate,deep时
// 监听isHot属性的变化
isHot(newValue, oldValue) {
console.log('isHot', newValue, oldValue);
}
//info,计算属性也可以被监听
// info: {
// // 监听info属性的变化
// handler(newValue, oldValue) {
// console.log('info', newValue, oldValue);
// },
// immediate: true, //初始化时执行handler,默认false
// deep: true //深度监听
// }
}
});
// 监听isHot属性的变化
/* vm.$watch('isHot', {
handler(newValue, oldValue) {
console.log('isHot', newValue, oldValue);
},
immediate: true, //初始化时执行handler,默认false
deep: true //深度监听
});
*/
//简写,不需要配置immediate,deep时
vm.$watch('isHot',function (newValue, oldValue) {
console.log('isHot改变了', newValue, oldValue);
})
</script>
</body>
</html>
(1).Vue中的watch默认不监测对象内部值的改变(一层)
(2).配置deep:true可以监测对象内部值改变(多层)
备注:
(1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
(2).使用watch时根据数据的具体结构,决定是否采用深度监视
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>天气案例-深度监视</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--
容器和vue实例是一对一的,不能一对多和多对一
-->
<!--准备好一个容器-->
<div id="app">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
<hr>
<h2>a的值是{{num.a}}</h2>
<button @click="num.a++">点我a+1</button>
<hr>
<h2>b的值是{{num.b}}</h2>
<button @click="num.b++">点我b+1</button>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
const vm = new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
isHot: true,
num:{
a:1,
b:2
}
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽';
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
watch: {
isHot: {
// 监听isHot属性的变化
handler(newValue, oldValue) {
console.log('isHot', newValue, oldValue);
},
immediate: true, //初始化时执行handler,默认false
deep: true //深度监听
},
// 监听多级结构中某个属性,num.a属性的变化
'num.a':{
handler(newValue, oldValue) {
console.log('num.a改变了', newValue, oldValue);
},
},
// 监听多级结构中所有属性的变化
num:{
handler(newValue, oldValue) {
console.log("num改变了");
},
deep: true//深度监听
}
}
});
</script>
</body>
</html>
computed和watch之间的区别:
computed能完成的功能,watch都可以完成
watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
两个重要的小原则:
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>姓名案例-watch写法</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
姓: <input type="text" v-model="firstName"/><br>
名:<input type="text" v-model="lastName"/><br>
全名:<span>{{fullName}}</span>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
firstName: 'ZQG',
lastName: 'YM',
fullName: 'ZQG-YM'
},
watch:{
firstName(newValue,oldValue){
setTimeout(()=>{
this.fullName = newValue +'-'+ this.lastName;
},1000)
},
lastName(newValue,oldValue){
this.fullName = this.firstName +'-'+ newValue;
}
}
});
</script>
</body>
</html>
1.11 绑定样式
class样式
写法::class=“xxx” xxx可以是字符串、对象、数。
所以分为三种写法,字符串写法,数组写法,对象写法
字符串写法
字符串写法适用于:类名不确定,要动态获取。
数组写法
数组写法适用于:要绑定多个样式,个数不确定,名字也不确定。
对象写法
对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
style样式
有两种写法,对象写法,数组写法.
:style = “{forntSize:xxx}” ,xxx时动态值
:style=”[a,b]” a,b是样式对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>绑定样式</title>
<script type="text/javascript" src="js/vue.js"></script>
<style>
.basic{
text-align: center;
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy{
background-color: yellow;
border: 4px solid red;
}
.sad{
border: 4px solid blue;
background-color: gray;
}
.normal{
background-color: skyblue;
}
.zqg1{
background-color: yellowgreen;
}
.zqg2{
font-size: 30px;
text-shadow:2px 2px 10px red;
}
.zqg3{
border-radius: 20px;
}
</style>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
<!-- 字符串写法,适用于:类名不确定,要动态获取-->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
<br/>
<!-- 数组写法,适用于:要绑定多个样式,个数不确定,名字也不确定-->
<div class="basic" :class="arr">{{name}}</div><br/>
<!-- 对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用-->
<div class="basic" :class="classObj">{{name}}</div><br/>
<div class="basic" :style="styleObj">{{name}}</div><br/>
<div class="basic" :style="styleArr">{{name}}</div><br/>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
name: 'ZQG',
mood:'normal',
arr:['zqg1','zqg2','zqg3'],
classObj:{
zqg1:true,
zqg2:false,
zqg3:true
},
styleObj:{
color:'red',
fontSize:'35px',
backgroundColor:'orange'
},
styleArr:[{color:'blue', fontSize: '45px'},{backgroundColor:'gray'}]
},
methods: {
changeMood: function () {
const arr = ['happy', 'sad','normal'];
this.mood = arr[Math.floor(Math.random() * arr.length)];
}
}
});
</script>
</body>
</html>
1.12 条件渲染
v-if
写法:
(1).v-if=“表达式”
(2).v-else-if=“表达式”
(3).v-else=“表达式”
适用于:切换频率较低的场景
特点:不展示的DOM元素直接被移除
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”
v-show
- 写法:v-show=“表达式”
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(display:none)
备注:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到;v-if 是实打实地改变dom元素,v-show 是隐藏或显示dom元素
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>条件渲染</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
<h2 v-show="condition">你好,{{name}}</h2>
<br/>
<h2 v-show="1===1">你好,{{name}}</h2>
<hr>
<h2 v-if="1===1">你好,{{name}}</h2>
<hr>
<h2>当前i的值是:{{i}}</h2>
<button @click="i++">点我++</button>
<div v-if ="i === 1">Angular</div>
<div v-else-if ="i === 2 ">React</div>
<div v-else-if="i === 3">Vue</div>
<div v-else>666</div>
<!-- template只能和v-if配合使用 -->
<template v-if="i === 4">
<h2>zqg</h2>
<h2>bc</h2>
<h2>ym</h2>
</template>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
name: 'ZQG',
condition: true,
i: 0
}
});
</script>
</body>
</html>
1.13 列表渲染
v-for指令
- 用于展示列表数据
- 语法:v-for=“(item, index) in xxx” :key=“yyy”
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表渲染</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
<h2>人员列表</h2>
<ul>
<li v-for="p in persons" :key="p.id">{{p.name}}-{{p.age}}</li>
<hr/>
<li v-for="(p,index) in persons" :key="index">{{p.name}}-{{p.age}}</li>
</ul>
<hr/>
<h2>汽车信息</h2>
<li v-for="(value,key,index) in car" :key="key">
{{index}}-{{key}}-{{value}}
</li>
<hr/>
<h2>字符串信息</h2>
<li v-for="(value,index) in str" :key="index">
{{index}}--{{value}}
</li>
<hr>
<h2>遍历次数</h2>
<li v-for="(value,index) in 5" :key="index">
{{index}} -- {{value}}
</li>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
persons: [
{id: '001', name: 'zqg', age: 18},
{id: '002', name: 'bc',age: 19},
{id: '003', name: 'ym',age: 20},
],
car: {
name: '奔驰',
price: '1000000',
color: '黑色'
},
str: 'hello world'
}
});
</script>
</body>
</html>
key的原理
可以先了解一下虚拟DOM和真实DOM,以及它们之间的差异:深入剖析:Vue核心之虚拟DOM使用 Vue 做项目也有两年时间了,对 Vue 的 api也用的比较得心应手了,虽然对 - 掘金
虚拟DOM中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,如id,身份证号,手机号,学号等。
- 如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表展示,可以使用index作为key。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表渲染-key的原理</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
<h2>人员列表</h2>
<button @click.once="add">添加一个老张</button>
<ul>
<li v-for="p in persons" :key="p.id">{{p.name}}-{{p.age}} <input type="text"></li>
<hr/>
<li v-for="(p,index) in persons" :key="index">{{p.name}}-{{p.age}} <input type="text"></li>
</ul>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
persons: [
{id: '001', name: 'zqg', age: 18},
{id: '002', name: 'bc',age: 19},
{id: '003', name: 'ym',age: 20},
]
},
methods: {
add() {
const p = {id: '004', name: '老张', age: 30};
this.persons.unshift(p);
}
}
});
</script>
</body>
</html>
Vue中key原理图:
index作为key时:注意,如果不指定key,Vue会默认用index作为key。

因为老刘被插到第一个,重刷了 key 的值,vue Diff 算法根据 key 的值判断虚拟DOM 全部发生了改变,然后全部重新生成新的真实 DOM(key所对应的input输入框,算法比对是一样的,所以直接复用了,这导致页面上输入框对不上)。实际上,张三,李四,王五并没有发生更改,是可以直接复用之前的真实 DOM,而因为 key 的错乱,导致要全部重新生成,造成了性能的浪费。
使用唯一标识作为key时:

1.14 列表过滤
watch和computed都可以实现,computed更简单方便
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表过滤</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
<h2>人员列表</h2>
<input type="text" v-model="keyword" placeholder="请输入名字">
<ul>
<li v-for="p in filterPersons" :key="p.id">{{p.name}}-{{p.age}}</li>
</ul>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
persons: [
{id: '001', name: '马冬梅', age: 18},
{id: '002', name: '周冬雨',age: 19},
{id: '003', name: '周杰伦',age: 20},
{id: '004', name: '温兆伦',age: 25}
],
//filterPersons:[],
keyword: ''
},
computed: {
filterPersons() {
return this.persons.filter(p => p.name.indexOf(this.keyword) !== -1);
}
},
/*
watch: {
keyword:{
immediate: true,
handler(newValue, oldValue) {
this.filterPersons = this.persons.filter(p => p.name.indexOf(newValue) !== -1);
}
}
}
*/
});
</script>
</body>
</html>
1.15 列表排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表排序</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
<h2>人员列表</h2>
<input type="text" v-model="keyword" placeholder="请输入名字">
<button @click="sortType=1">年龄升序</button>
<button @click="sortType=2">年龄降序</button>
<button @click="sortType=0">原顺序</button>
<ul>
<li v-for="p in filterPersons" :key="p.id">{{p.name}}-{{p.age}}</li>
<hr/>
</ul>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
persons: [
{id: '001', name: '马冬梅', age: 28},
{id: '002', name: '周冬雨',age: 19},
{id: '003', name: '周杰伦',age: 20},
{id: '004', name: '温兆伦',age: 25}
],
keyword: '',
sortType:0 //0:原顺序,1:升序,2:降序
},
computed: {
filterPersons() {
return this.persons.filter(p => p.name.indexOf(this.keyword) !== -1).sort(
(p1, p2) => {
if (this.sortType === 1) {
return p1.age - p2.age;
} else if (this.sortType === 2) {
return p2.age - p1.age;
} else {
return 0;
}
}
);
}
},
});
</script>
</body>
</html>
1.16 Vue监测数据的原理
监测对象数据:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>更新时的问题</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!--准备好一个容器-->
<div id="app">
<h2>人员列表</h2>
<button @click="updateYm">更新ym</button>
<ul>
<li v-for="p in persons" :key="p.id">{{p.name}}-{{p.age}}</li>
</ul>
<button @click="addGender">添加性别</button>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3>性别:{{student.gender}}</h3>
</div>
<script type="text/javascript">
<!--生产环境提示-->
Vue.config.productionTip = false;
const vm = new Vue({
el: '#app',//挂载点,指定当前vue示例为哪个容器服务,值通常为css选择器字符串
data: { //数据,给el 容器中的元素提供数据
persons: [
{id: '001', name: 'zqg', age: 18},
{id: '002', name: 'bc',age: 19},
{id: '003', name: 'ym',age: 20},
],
student:{
name:'lyf',
age:18
}
},
methods:{
updateYm(){
//this.persons[2].name = 'lyf' //成功,生效
this.persons[0]={id: '001', name: 'zzz', age: 10}//失效,因为vue对数组的变更进行了监听,如果直接修改数组中的某一个对象,vue是无法知道这个数组发生了变化,所以需要使用数组的方法进行操作,如:push、pop、shift、unshift、splice等
},
addGender(){
//Vue.set(this.student,'gender','男')
this.$set(this.student,'gender','女')
}
}
});
</script>
</body>
</html>
现象:直接修改person数组中某个对象的某个属性值室,Vue可以监测到并解析模板重新渲染,但是把某个对象改了(替换),代码是执行了,内存中的数据夜修改成功了,但是页面没有变化,说明Vue没有监测到。
Vue 监测数据变化的原理:
加工data中的数据(主要是添加响应式的getter和setter方法,用的是Object.defineProperty()方法)==>把加工后的对象传给_data,也就是vm._data = data ==> 属性值改变会调用set方法,在set方法中去解析模板,生成虚拟DOM,新旧DOM比较,更新页面。
简单模拟实现一下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模拟数据监测</title>
</head>
<body>
<script type="text/javascript" >
let data = {
name:'zqg',
address:'bj',
}
//创建一个监视的实例对象,用于监视data中属性的变化,(Vue实际做的更多,更完善)
const obs = new Observer(data)
console.log(obs)
//准备一个vm实例对象
let vm = {}
vm._data = data = obs
function Observer(obj){
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
//遍历
keys.forEach((k) => {
Object.defineProperty(this, k, {
get() {
return obj[k]
},
set(val) {
console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
obj[k] = val
}
})
})
}
</script>
</body>
</html>
Vue.set 的使用
Vue.set(target,propertyName/index,value) 或vm.$set(target,propertyName/index,value)
用法:
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 vm.myObject.newProperty = ‘xx’)

监测数组:
vue 没有为数组中的元素生成 getter 和 setter,所以监测不到数据的更改(根据index替换元素的值),也不会引起页面的更新。
想要Vue监测到数据变化,需要使用数组的方法进行操作,如:push、pop、shift、unshift、splice等。(这些方法已经被Vue包装了)

除了用数组方法,当然还可以用Vue.set()或者vm.$set()来修改数组元素。
总结:
Vue监视数据的原理:
vue会监视data中所有层次的数据。
如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
对象中后追加的属性,Vue默认不做响应式处理,如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或vm.$set(target,propertyName/index,value)
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
1、调用原生对应的方法对数组进行更新
2、重新解析模板,进而更新页面
在Vue修改数组中的某个元素一定要用如下方法:
1、使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2、Vue.set() 或 vm.$set()
注意:Vue.set() 或 vm.$set() 不能给vm对象和根数据对象_data,添加响应式的属性。
1.17 收集表单数据
若:<input type=”text”/>,则v-model收集的是value值,用户输入的就是value值。
若:<input type=”radio”/>,则v-model收集的是value值,且要给标签配置value值。
若:<input type=”checkbox”/>
没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
配置input的value属性:
v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
v-model的初始值是数组,那么收集的的就是value组成的数组。
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="app">
<form @submit="submitForm">
<label for = "userName">账号:</label>
<input type="text" id="userName" v-model.trim="userInfo.userName"><br>
<label for="password">密码:</label>
<input type="password" id="password" v-model="userInfo.password"></input><br>
<br>
性别:
男<input type="radio" name="sex" value="1" v-model="userInfo.sex">
女<input type="radio" name="sex" value="0" v-model="userInfo.sex"><br>
<br>
年龄: <input type="number" v-model.number="userInfo.age"><br><br>
爱好:
打游戏<input type="checkbox" name="hobby" value="game" v-model="userInfo.hobby">
看电影<input type="checkbox" name="hobby" value="movie" v-model="userInfo.hobby">
跑步<input type="checkbox" name="hobby" value="run" v-model="userInfo.hobby">
<br>
<br>
学历:
<select name="userInfo.education" >
<option value="大专">大专</option>
<option value="本科">本科</option>
<option value="硕士">硕士</option>
<option value="博士">博士</option>
</select>
<br>
<br>
<label for="birthday">出生日期:</label>
<input type="date" id="birthday" v-model="userInfo.birthday"><br></input>
<br>
其他信息:
<textarea name="otherInfo" cols="30" rows="10" v-model.lazy="userInfo.otherInfo"></textarea>
<br>
<br>
<input type="checkbox" name="agree" value="agree" v-model="userInfo.agree"></input>
阅读并接受<a href="https://zhangqingguo.github.io/">《用户协议》</a>
<br>
<br>
<button type="submit">提交</button>
</form>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: '#app',
data: {
userInfo: {
userName: '',
password: '',
sex: '',
age: null,
hobby: [],
education: '',
birthday: '',
otherInfo: '',
agree: false
}
},
methods: {
submitForm(e) {
e.preventDefault();
console.log(this.userInfo.userName);
console.log(JSON.stringify(this.userInfo))
}
}
})
</script>
</html>
1.18 过滤器使用
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
- 注册过滤器:Vue.filter(name,callback) ==》全局过滤器 或 new Vue{filters:{}} ==》局部过滤器
- 使用过滤器:{ { xxx | 过滤器名} } 或 v-bind:属性 = “xxx | 过滤器名”
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="./js/vue.js"></script>
<script type="text/javascript" src="./js/dayjs.min.js"></script>
</head>
<body>
<div id="app">
<h2>显示格式化时间</h2>
现在是:{{time}}<br>
<!--计算属性-->
现在是:{{formatTime}}<br>
<!-- 方法-->
现在是:{{getTime()}}<br>
<!--过滤器-->
现在是:{{time | timeFormater}}<br>
<!--过滤器传参-->
现在是:{{time | timeFormater('YYYY年MM月DD日 HH:mm:ss')}}<br>
<!--多个过滤器串联使用-->
现在是:{{time | timeFormater('YYYY年MM月DD日 HH:mm:ss') | strSplice(0,11)}}<br>
<!--v-bind使用-->
<h3 :x="mag | strSplice(0,5)">zzz</h3>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 全局 过滤器
Vue.filter('timeFormater', function (value, str = 'YYYY-MM-DD HH:mm:ss') {
return dayjs(value).format(str)
})
const vm = new Vue({
el: '#app',
data: {
time: Date.now(),
mag: 'hello world'
},
// 计算属性
computed: {
formatTime() {
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
}
},
// 方法
methods: {
getTime() {
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss')
}
},
// 局部的 过滤器
filters: {
timeFormater(value, str = 'YYYY-MM-DD HH:mm:ss') {
console.log('value=', value)
return dayjs(value).format(str)
},
strSplice(value, start, end) {
return value.substring(start, end)
}
}
})
</script>
</html>
1.19 内置指令
已经学过的指令:
v-bind:属性绑定,单向数据绑定,简写为 :xxx
v-model:双向数据绑定
v-on:事件绑定,监听,简写为 @
v-if:条件渲染,动态控制节点是否存在
v-else:条件渲染,动态控制节点是否存在
v-show:条件渲染,动态控制节点是否显示
v-for:循环渲染,遍历数组/对象/字符串
v-text指令:
1.作用:向其所在的节点中渲染文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
v-html指令:
1.作用:向指定节点中渲染包含html结构的内容。
2.与插值语法的区别:
v-html会替换掉节点中所有的内容,则不会。
v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!
在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
v-cloak指令:
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
- 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题(未解析的模板,直接展示影响用户体验)。
v-once指令:
- v-once所在节点在初次动态渲染后,就视为静态内容了。
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre指令:
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./js/vue.js"></script>
<style>
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<!--
v-bind:属性绑定,单向数据绑定,简写为 :xxx
v-model:双向数据绑定
v-on:事件绑定,监听,简写为 @
v-if:条件渲染,动态控制节点是否存在
v-else:条件渲染,动态控制节点是否存在
v-show:条件渲染,动态控制节点是否显示
v-for:循环渲染,遍历数组/对象/字符串
v-text:文本绑定,
v-html:html绑定,
1.作用:向指定节点中渲染包含html结构的内容。
2.与插值语法的区别:
v-html会替换掉节点中所有的内容,{{xx}}则不会。
v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!
在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
-->
<div id="app">
<div>hello, {{name}}</div>
<div v-text="name"></div>
<div v-html="msg"></div>
<h2 v-cloak>{{name}}</h2>
<hr>
<h2 v-once>初始化数值num:{{num}}</h2>
<h2>当前数值num:{{num}}</h2>
<button @click="num++">点我num++</button>
<hr>
<h2 v-pre> 这是一个普通标签</h2>
</div>
</body>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '<h3>hello vue</h3>',
name: 'zhangsan',
num: 100
}
})
</script>
</html>
1.20 自定义指令
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
语法:
局部指令:
new Vue({
directives:{指名:配置对象}
})
或者
new Vue({
directives:{指令名:回调函数}
})
全局指令:Vue.directive(指令名,配置对象) 或者 Vue.directive(指令名,回调函数)
配置对象中常用的3个回调:
- bind:指令与元素成功绑定时调用。
- inserted:指令所在元素被插入页面时调用。
- update:指令所在模板结构被重新解析时调用。
备注:
指令定义时不加v-,但使用时要加v-。
指令名如果是多个单词,要使用kebab-case命名方式,不要使用驼峰命名。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
<!--
需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
-->
<div id="app">
<h3>当前值:<span v-text="num"></span></h3>
<h3 >放大10倍:<span v-big="num"></span></h3>
<h3 >放大15倍:<span v-big-number="num"></span></h3>
<button @click="num++">点我num++</button>
<br>
<input type="text" v-fbind:value="num">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 全局自定义指令
Vue.directive('big', {
bind(el, binding){
el.innerText = binding.value * 10
}
})
Vue.directive('fbind', {
// 指令与元素成功绑定时(一上来)
bind(el, binding){
el.value = binding.value
},
// 指令所在元素被插入页面时
inserted(el){
el.focus()
},
// 指令所在的模板被重新解析时
update(el, binding){
el.value = binding.value
}
})
new Vue({
el: '#app',
data: {
num: 1
},
// 局部自定义指令
directives: {
//big函数什么时候调用:1.指令与元素成功绑定时(一上来)2.指令所在的模板被重新解析时
big(el, binding){
//this 是window
console.log('big',this);
// el是绑定指令的元素,真实的dom
console.log(el)
console.log(el instanceof HTMLElement)
el.innerText = binding.value * 10
},
'big-number'(el, binding){
el.innerText = binding.value * 15
},
fbind:{
bind(el, binding){
el.value = binding.value
},
// 指令所在元素被插入页面时
inserted(el){
el.focus()
},
update(el, binding){
el.value = binding.value
}
}
}
})
</script>
</html>
1.21 生命周期
先上图

beforeCreate(创建前):数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
created(创建后):实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。
beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容。
mounted(挂载后):在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等
beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。
updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。(这个时候修改数据不会触发更新)
destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器(Vue自定义的,dom原生的监听事件还在)会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
created之后:
先判断有没有 el 这个配置项,没有就调用 vm.$mount(el),如果两个都没有就一直卡着,显示的界面就是最原始的容器的界面。有 el 这个配置项,就进行判断有没有 template 这个配置项,没有 template 就将 el 绑定的容器编译为 vue 模板。
第一种情况,有 template:
如果 el 绑定的容器没有任何内容,就一个空壳子,但在 Vue 实例中写了 template,就会编译解析这个 template 里的内容,生成虚拟 DOM,最后将 虚拟 DOM 转为 真实 DOM 插入页面(其实就可以理解为 template 替代了 el 绑定的容器的内容)。

第二种情况,没有 template:
没有 template,就编译解析 el 绑定的容器,生成虚拟 DOM,后面就顺着生命周期执行下去。
总结:
常用生命周期钩子:
1、mounted:发送ajax请求,启动定时器,绑定自定义事件,订阅消息【初始化操作】
2、beforeDestroy:清除定时器,解绑自定义事件,取消订阅消息等【收尾工作】
关于销毁Vue实例
1、销毁后借助Vue开发工具看不到任何信息。
2、销毁后自定义事件会失效,但是原生DOM事件依然有效。
3、一般不会在beforeDestroy操作数据,因为即使操作数据,也不会触发更新流程。
1.21 非单文件组件
组件的定义:实现应用中局部功能代码和资源的集合。
作用:复用编码,简化项目编码,提高运行效率。
非单文件组件:一个文件包含多个组件。
单文件组件:一个文件只包含1个组件。
基本使用
Vue中使用组件的三大步骤:
定义组件(创建组件)
注册组件
使用组件(写组件标签)
定义组件
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
区别如下:
el不要写,为什么? ———> 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
data必须写成函数(普通函数),为什么? ————> 避免组件被复用时,数据存在引用关系。
举例说明 data为什么要写成函数?
这是因为js底层设计的原因:
对象形式:
let data = {
a: 99,
b: 100
}
let x = data;
let y = data;
// x 和 y 引用的都是同一个对象,修改 x 的值, y 的值也会改变
x.a = 66;
console.log(x); // a:66 b:100
console.log(y); // a:66 b:100
函数形式:
function data() {
return {
a: 99,
b: 100
}
}
let x = data();
let y = data();
console.log(x === y); // false,调用函数,每次返回新的对象
备注:使用template可以配置组件结构。
注册组件
- 局部注册:靠new Vue的时候传入components选项
- 全局注册:靠Vue.component(‘组件名’,组件)
几个注意点:
1、关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。
2、关于组件标签
第一种写法:<school></school>
第二种写法:<school/>
备注:不使用脚手架时,<school/>会导致后续组件不能渲染。
3、一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<h2>{{msg}}</h2>
<hr>
<school></school>
<hr>
<person></person>
<hr>
<student></student>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 简写 组件
const student = {
template: `
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="showInfo">点我提示学生姓名</button>
</div>
`,
data() {
return {
name: '李四',
age: 19,
sex: '女'
}
},
methods: {
showInfo(){
alert(this.name)
}
}
}
const school = Vue.extend({
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showSchool">点我提示学校名称</button>
</div>
`,
data() {
return {
name: '圣芙蕾雅',
address: '极东'
}
},
methods: {
showSchool() {
alert(this.name)
}
}
})
const person = Vue.extend({
template: `
<div>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>性别:{{sex}}</h2>
<button @click="showInfo">点我提示姓名</button>
</div>
`,
data() {
return {
name: '张三',
age: 18,
sex: '男'
}
},
methods: {
showInfo(){
alert(this.name)
}
}
})
// 全局注册组件
Vue.component('student', student)
Vue.component('school', school)
Vue.component('person', person)
new Vue({
el: '#app',
data: {
msg: '你好啊'
},
// 局部 注册组件
// components: {
// school: school,
// person: person
// // ES6简写形式
// school,
// student
// }
})
</script>
</html>
组件嵌套:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="./js/vue.js"></script>
</head>
<body>
<div id="app">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 简写 组件
const student = {
template: `
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="showInfo">点我提示学生姓名</button>
</div>
`,
data() {
return {
name: '李四',
age: 19,
sex: '女'
}
},
methods: {
showInfo(){
alert(this.name)
}
}
}
const school = Vue.extend({
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
<button @click="showSchool">点我提示学校名称</button>
</div>
`,
data() {
return {
name: '圣芙蕾雅',
address: '极东'
}
},
components:{
student
},
methods: {
showSchool() {
alert(this.name)
}
}
})
const myAddress = Vue.extend({
template: `
<div>
<h2>地址:{{address}}</h2>
</div>
`,
data() {
return {
address: '神州'
}
}
})
const person = Vue.extend({
template: `
<div>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>性别:{{sex}}</h2>
<my-address></my-address>
<button @click="showInfo">点我提示姓名</button>
</div>
`,
data() {
return {
name: '张三',
age: 18,
sex: '男'
}
},
components: {
'my-address': myAddress
},
methods: {
showInfo(){
alert(this.name)
}
}
})
const app = {
template: `
<div>
<h2>{{msg}}</h2>
<hr>
<school></school>
<hr>
<person>
</person>
<hr>
</div>
`,
data() {
return {
msg: '你好啊'
}
},
components: {
school,
person
},
}
const vm = new Vue({
el: '#app',
template: '<app></app>',
components: {
app
}
})
</script>
</html>
VueComponent
1、school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2、我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
3、特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!(这个VueComponent可不是实例对象)
4、关于this指向:
组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
5、VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。
一个重要的内置关系
- 一个重要的内置关系:VueComponent.prototype._proto_=== Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
1.22 单文件组件
单文件组件就是将一个组件的代码写在 .vue 这种格式的文件中,webpack 会将 .vue 文件解析成 html,css,js这些形式。
2、vue脚手架,自定义事件,插槽等复杂内容
2.1 脚手架
使用前置:
第一步(没有安装过的执行):全局安装 @vue/cli
npm install -g @vue/cli
第二步:切换到要创建项目的目录,然后使用命令创建项目
vue create xxxxx
第三步:启动项目
npm run serve
脚手架文件结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
import Vue,不需要加文件路径,脚手架已经处理好了, package.json文件中的module指定了具体引入的文件。

main.js入口文件:
/**
* 整个项目的入口文件
*/
//引入Vue(vue.runtime.esm.js,精简版,不包含模板解析器)
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
/**
* vue.js与vue.runtime.xxx.js的区别:
* vue.js是完整版的Vue,包含:核心功能+模板解析器。
* vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
* 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
*/
//创建Vue实例对象
new Vue({
//将app组件,渲染到#app容器中
//render: h => h(App),
//完整写法
render(crateElement){
console.log(typeof crateElement)
return crateElement(App)
}
}).$mount('#app')
render函数
为什么要使用render函数?

这样template写法会报错:

所以,要么使用完整版本的Vue,要么使用运行版本Vue提供的render函数,否则无法解析编译模板。
//完整的render函数写法
render(crateElement){
return crateElement(App)
}
因为 render 函数内并没有用到 this,所以可以简写成箭头函数。只有一个参数就可以把圆括号去了,函数体内部只有一个 return 就可以把大括号去掉,return去掉,最后简写成:render: h => h(App)
new Vue({
render: h => h(App),
}).$mount('#app')
不同版本 vue 的区别
vue.js与vue.runtime.xxx.js的区别:
vue.js是完整版的Vue,包含:核心功能+模板解析器。
vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
修改脚手架的默认配置
- 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
- 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
脚手架中的index页面
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的兼容性 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!--配置页签图标,BASE_URL值是public目录-->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!--配置网页标题-->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持js,标签内容会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<!-- 挂载点,容器 -->
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
2.2 vue的其他知识
ref属性
1、被用来给元素或子组件注册引用信息(id的替代者)
2、应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3、使用方式:
打标识:<h1 ref=”xxx”>…..</h1>或 <School ref=”xxx”></School>
获取:this.$refs.xxx
props配置项
功能:让组件接收外部传过来的数据
传递数据:
<Demo name="xxx"/>接收数据:
第一种方式(只接收):
props:['name']第二种方式(限制类型):
props:{name:String}第三种方式(限制类型、限制必要性、指定默认值)
props:{ name:{ type:String, //类型 required:true, //必要性 default:'老王' //默认值 } }
备注:
1.props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
2.属性名不要使用已有的关键名字,如key,ref等
mixin(混入)
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
定义混入
export const mixin = {
methods: {
showName() {
alert(this.name)
}
},
mounted() {
console.log('mixin mounted')
}
}
export const mixin2 = {
data() {
return {
x: 100,
y: 200
}
}
}
使用混入
1、局部混入:
<script>
import {mixin,mixin2} from '../mixin'
export default {
name: 'SchoolInfo',
data() {
return {
name: '折纸大学',
address: '匹诺康尼'
}
},
mixins: [mixin, mixin2]
}
</script>
2、全局混入:不建议使用,会导致整个Vue,组件都引入了混入的配置。
在main.js中引入使用。
//全局混入
import {mixin,mixin2} from "@/mixin";
Vue.mixin(mixin)
Vue.mixin(mixin2)
选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
1、数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
2、同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
3、值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
插件
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制。
通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成。
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
定义插件:
export default {
install(Vue,y){
// y是自定义参数
console.log('插件被调用了', Vue,y)
Vue.filter('filterName', function(value) {
return value.slice(0,2)
})
Vue.mixin({
data() {
return {
x: 100
}
}
})
Vue.directive('fbind', {
bind(el, binding) {
el.value = binding.value
},
inserted(el) {
el.focus()
},
update(el,binding) {
el.value = binding.value
}
})
//给Vue原型上添加一个方法,vm和vc都可以使用
Vue.prototype.hello = function() {
alert('你好啊')
}
}
}
引入使用插件:
在main.js中:
import MyPlugins from './plugins.js'
//调用MyPlugins.install方法,可以带自定义参数
Vue.use(MyPlugins,111)
这样就可以在组件里使用插件里面的功能了。
scoped样式
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
style里面的lang指定语言,css,less等
总结TodoList案例
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
2.3 浏览器本地存储
Cookie
Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用 于控制 Cookie 有效期、安全性、使用范围的可选属性组成。不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。
Cookie的特性:
- Cookie一旦创建成功,名称就无法修改
- Cookie是无法跨域名的,也就是说a域名和b域名下的cookie是无法共享的,这也是由Cookie的隐私安全性决定的,这样就能够阻止非法获取其他网站的Cookie
- 每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb
- 有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,即使加密也于事无补,无需知道cookie的意义,只要转发cookie就能达到目的
- Cookie在请求一个新的页面的时候都会被发送过去
Cookie 在身份认证中的作用
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动 将 Cookie 保存在浏览器中。
随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给 服务器,服务器即可验明客户端的身份。
Cookie 不具有安全性
由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。
注意:千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。
Session
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了session是一种特殊的cookie。cookie是保存在客户端的,而session是保存在服务端。
为什么要用session 由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了
session原理 当客户端第一次请求服务器的时候,服务器生成一份session保存在服务端,将该数据(session)的id以cookie的形式传递给客户端;以后的每次请求,浏览器都会自动的携带cookie来访问服务器(session数据id)。
LocalStorage
LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。
LocalStorage的优点:
- 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息
- LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在
- 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带
LocalStorage的缺点:
- 存在浏览器兼容问题,IE8以下版本的浏览器不支持
- 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
- LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问
LocalStorage的使用场景:
- 有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可
- 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中
SessionStorage
SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。
SessionStorage与LocalStorage对比:
- SessionStorage和LocalStorage都在本地进行数据存储;
- SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享;
- LocalStorage和SessionStorage都不能被爬虫爬取;
总结:
WebStorage
1、存储大小一般支持5MB(不同浏览器可能不一样)
2、浏览器通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制。
3、相关API:
localStorage:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>localStorage</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="setLocalStorage()">设置localStorage</button>
<button onclick="getLocalStorage()">获取localStorage</button>
<button onclick="removeLocalStorage()">删除localStorage</button>
<button onclick="clearLocalStorage()">清空localStorage</button>
<script type="text/javascript">
function setLocalStorage() {
//setItem的第二个参数是string,会调用toSting方法
let p = {name: '张三', age: 18, sex: '男'};
localStorage.setItem('msg', 'hhhhhh2');
localStorage.setItem('n', 18);
localStorage.setItem('person', JSON.stringify(p));
localStorage.setItem('person2', p);
}
function getLocalStorage() {
console.log(localStorage.getItem('msg'));
console.log(localStorage.getItem('n'));
console.log(localStorage.getItem('person'));
console.log(JSON.parse(localStorage.getItem('person')));
}
function removeLocalStorage() {
localStorage.removeItem('msg');
localStorage.removeItem('n');
localStorage.removeItem('person');
localStorage.removeItem('person2');
}
function clearLocalStorage() {
localStorage.clear();
}
</script>
</body>
</html>
sessionStorage:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sessionStorage</title>
</head>
<body>
<h2>sessionStorage</h2>
<button onclick="setSessionStorage()">设置sessionStorage</button>
<button onclick="getSessionStorage()">获取sessionStorage</button>
<button onclick="removeSessionStorage()">删除sessionStorage</button>
<button onclick="clearSessionStorage()">清空sessionStorage</button>
<script type="text/javascript">
function setSessionStorage() {
//setItem的第二个参数是string,会调用toSting方法
let p = {name: '张三', age: 18, sex: '男'};
sessionStorage.setItem('msg', 'hhhhhh2');
sessionStorage.setItem('n', 18);
sessionStorage.setItem('person', JSON.stringify(p));
sessionStorage.setItem('person2', p);
}
function getSessionStorage() {
console.log(sessionStorage.getItem('msg'));
console.log(sessionStorage.getItem('n'));
console.log(sessionStorage.getItem('person'));
console.log(JSON.parse(sessionStorage.getItem('person')));
}
function removeSessionStorage() {
sessionStorage.removeItem('msg');
sessionStorage.removeItem('n');
sessionStorage.removeItem('person');
sessionStorage.removeItem('person2');
}
function clearSessionStorage() {
sessionStorage.clear();
}
</script>
</body>
</html>
4、备注:
- sessionStorage存储的内容会随着浏览器窗口关闭而消失。
- localStorage存储的内容需要手动清除才会消失。
- getItem(key),获取不到key对应的value值,返回null。
- JSON.parse(null)结果是null。
2.4 组件自定义事件
组件自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件
使用场景
A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中),B触发自定义事件。
给谁绑定事件,就找谁触发事件。
绑定自定义事件:
第一种方式,在父组件中:<StudentInfo v-on:zqg="getStudentName"/>或 <StudentInfo @zqg="getStudentName"/>
第二种方式,在父组件中:
使用 this.$refs.xxx.$on() 这样写起来更灵活,比如可以加定时器。this.$refs.xxx.$once(),只调用一次。
App.vue ,给student组件绑定自定义事件:zqg
<StudentInfo ref="student" @click.native="show"></StudentInfo>
...
methods: {
//可以声明接收多个参数,也可以指定一个参数,其他参数全部存到params数组中
getStudentName(name, ...params) {
console.log('app getStudentName receive:', name, params)
}
}
mounted() {
this.$refs.student.$on('zqg', this.getStudentName)
}
Student.vue 触发zqg事件
<button @click="sendStudentName">点我发送学生姓名</button>
...
methods: {
sendStudentName() {
// 触发组件实例的zqg事件
this.$emit('zqg',this.name)
}
}
解绑自定义事件:this.$off('zqg')==》解绑一个, this.$off('xx','yy')==》解绑多个,this.$off()==》解绑所有
组件上也可以绑定原生DOM事件,需要使用native修饰符。
<StudentInfo ref="student" @click.native="show"></StudentInfo>
注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!(使用普通函数,this是触发事件的组件的实例对象)
举例:下面写法不行,无法给app的studentName赋值,因为this指向不对
//回调是普通函数,this指向student实例对象, this.studentName = name 会出问题,这种写法不行
this.$refs.student.$on('zqg', function (name){
console.log('app mounted $refs receive:', name)
this.studentName = name
console.log(this)//this指向student实例对象
})
可以的写法:
//回调是箭头函数,没有this,往外找,找到app实例,this.studentName = name 成功
this.$refs.student.$on('zqg', (name) => {
console.log('app mounted $refs receive:', name)
this.studentName = name
console.log(this)//this指向app实例对象
})
或者:
methods: {
getSchoolName(name) {
console.log('app getSchoolName receive:', name)
},
//可以声明接收多个参数,也可以指定一个参数,其他参数全部存到params数组中
getStudentName(name, ...params) {
console.log('app getStudentName receive:', name, params)
}
}
mounted() {
// this.getStudentName,传入app实例对象的method的函数,会以app的函数为准,不会出现问题
this.$refs.student.$on('zqg', this.getStudentName)
}
2.5 全局事件总线(GlobalEventBus)
1、一种组件间通信的方式,适用于任意组件间通信。
2、安装全局事件总线:
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
3、使用事件总线:
1、接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
2、提供数据:this.$bus.$emit('xxxx',数据)
4、最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
2.6 消息订阅与发布
一种组件间通信的方式,适用于任意组件间通信。
使用步骤:
安装pubsub:
npm i pubsub-js引入:
import pubsub from 'pubsub-js'接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods:{ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 }提供数据:
pubsub.publish('xxx',数据)最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)去取消订阅。
2.7 nextTick
- 语法:
this.$nextTick(回调函数) - 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
// 在dom节点更新完后,执行设置焦点 this.$nextTick(() => { this.$refs.inputText.focus(); });
2.8 Vue封装的过度与动画

作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
写法:
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- 元素进入的样式:
使用
<transition>包裹要过渡的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>name 的作用可以让让不同的元素有不同的动画效果,用name区分开不同元素的样式。指定了name后,样式名称要改成 xxx-enter, 比如hello-enter-active
备注:若有多个元素需要过度,则需要使用:
<transition-group>,且每个元素都要指定key值。appear属性,作用是一开始进入就展示动画效果。
具体案例
单个元素动画
<script> export default { name: 'MyTest', data() { return { isShow: true } }, } </script> <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <!-- <h2 v-show="isShow" class="go">bcym</h2>--> <transition name="hello" appear> <h2 v-show="isShow">bcym</h2> </transition> </div> </template> <style scoped> h2 { background: orange; } .hello-enter-active{ animation: mymove 1s linear; } .hello-leave-active { animation: mymove 1s reverse linear; } .come{ animation: mymove 1s; } .go{ animation: mymove 1s reverse; } @keyframes mymove { from { margin-left: 0px; transform: translateX(-100%); } to { margin-left: 200px; transform: translateX(0px); } } </style>已经有了动画,想要借助Vue实现,那么用
<transition>标签包裹,同时在v-enter-active,v-leave-active配置好已有的动画。单个元素过度:
<script> export default { name: 'MyTest', data() { return { isShow: true } }, } </script> <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition name="hello" appear> <h2 v-show="isShow">bcym</h2> </transition> </div> </template> <style scoped> h2 { background: orange; } /** 进入的起点,离开的终点 */ .hello-enter,.hello-leave-to { transform: translateX(-100%); } .hello-enter-active,.hello-leave-active { transition: 0.5s linear; } /** 进入的终点,离开的起点 */ .hello-enter-to,.hello-leave { transform: translateX(0); } </style>没有写好的动画,想要靠过度实现动画效果,进入的起点,终点,离开的起点、终点,还有过程中,都要配置好。
多个元素过度:
<script> export default { name: 'MyTest', data() { return { isShow: true } }, } </script> <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group name="hello" appear> <h2 v-show="!isShow" key="1">zqg</h2> <h2 v-show="isShow" key="2">bcym</h2> </transition-group> </div> </template> <style scoped> h2 { background: orange; } /** 进入的起点,离开的终点 */ .hello-enter,.hello-leave-to { transform: translateX(-100%); } .hello-enter-active,.hello-leave-active { transition: 0.5s linear; } /** 进入的终点,离开的起点 */ .hello-enter-to,.hello-leave { transform: translateX(0); } </style>
导入第三方库:Animate.css | A cross-browser library of CSS animations.。需要安装npm install animate.css、导入import 'animate.css',然后使用。指定name,配置进入,离开等的clas。
<script>
import 'animate.css'
export default {
name: 'MyTest',
data() {
return {
isShow: true
}
},
}
</script>
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
appear>
<h2 v-show="!isShow" key="1">zqg</h2>
<h2 v-show="isShow" key="2">bcym</h2>
</transition-group>
</div>
</template>
<style scoped>
h2 {
background: orange;
}
</style>
2.9 vue脚手架配置代理
跨域问题:
1、服务器返回响应时添加cors的特殊响应头
2、jsonp,利用script 的src,只能解决get请求,不常用。
3、使用代理服务器,(让代理服务器和浏览器同源,不会有跨域问题,然后中转一下真实服务器返回的数据。常见代理有:nginx,vue-cli、vite等

注意:代理服务器和前端开发服务器确实需要监听同一个端口(如8080),但不会冲突,因为它们实际上是同一个服务器提供的两种功能。关键点:代理服务器是前端服务器的一部分。
❌ 误区:”配置了proxy就是启动了一个独立代理服务器”
✅ 事实:proxy只是开发服务器的一个功能,没有额外端口占用
❌ 误区:”proxy配置的目标服务器就是代理服务器”
✅ 事实:目标服务器是你的后端API服务,不是代理服务器
使用脚手架配置代理服务器:
方法一
在vue.config.js中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}//代理服务器将请求地址转给真实服务器时会将 /api1 去掉
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true, //控制请求头中host的值
pathRewrite: {'^/api2': ''},
ws: true// 支持代理websocket
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
2.10 插槽
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
分类:默认插槽、具名插槽named slot、作用域插槽scoped slot
使用方式:
默认插槽:
父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot>插槽默认内容...</slot> </div> </template>具名插槽:
父组件中: <Category> <template slot="center"> <div>html结构1</div> </template> <template v-slot:footer> <div>html结构2</div> </template> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot name="center">插槽默认内容...</slot> <slot name="footer">插槽默认内容...</slot> </div> </template>作用域插槽:
理解:数据在组件的自身(子组件),但根据数据生成的结构需要组件的使用者(父组件)来决定。(games数据在Category(子)组件中,但使用数据所遍历出来的结构由App(父)组件决定)。
具体编码:
父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> <Category> <template slot-scope="{games}"> <!-- 生成的是ul列表 --> <ol> <li v-for="g in games" :key="g">{{g}}</li> </ol> </template> </Category> <Category> <template v-slot="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category> 子组件中: <template> <div> <!-- 通过数据绑定就可以把子组件的数据传到父组件 --> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], //数据在子组件自身 data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
注意: scope 属性在 Vue 2.x 中已经被废弃,推荐使用 slot-scope。使用 v-slot(Vue 2.6.0+ 推荐,包括Vue 3.x)。在 Vue 3 中,v-slot 是唯一支持的语法。作用域插槽也可以指定名字。
v-slot="{ games }"(解构赋值)
✅ 作用:直接从插槽的 props 对象中解构出 games 属性,并直接在模板中使用 games。
✅ 适用场景:当只需要 games 数据,而不关心插槽提供的其他属性时。
v-slot="scopeData"(访问对象属性)
✅ 作用:接收整个插槽的 props 对象(假设命名为 scopeData),然后通过 scopeData.games 访问具体数据。
✅ 适用场景:当需要访问插槽提供的多个属性时。
3.Vuex

3.1 概念
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
3.2 何时使用?
多个组件需要共享数据时
3.3 搭建vuex环境
先安装 npm i vuex@3, 注意,Vue2.x使用的Vuex是3.x版本,Vue3使用Vuex4版本。
创建文件:
src/store/index.js//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件,必须在创建store实例之前使用 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })在
main.js中创建vm时传入store配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
3.4 基本使用
store.js
import Vue from 'vue'
import Vuex from 'vuex'
// 使用vuex,必须在创建store实例之前使用
Vue.use(Vuex)
// 定义actions,mutations,state
const actions = {
// add(context, value) {
// context.commit('ADD', value)
// },
// minus(context, value){
// context.commit('MINUS', value)
// },
addOdd(context, value){
if(context.state.count % 2){
context.commit('ADD', value)
}
},
addWait(context, value){
setTimeout(() => {
context.commit('ADD', value)
}, 1000)
}
}
const mutations = {
ADD(state, value) {
state.count += value
},
MINUS(state, value) {
state.count -= value
},
}
const state = {
count: 0,
}
// 创建,并导出 store 实例
export default new Vuex.Store({
actions,
mutations,
state
})
- 组件中读取vuex中的数据:
$store.state.count - 组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)或$store.commit('mutations中的方法名',数据) - 备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch,直接编写commit也是可以的。
Count.vue
<script>
export default {
name: "MyCount",
data() {
return {
num: 1
}
},
methods: {
increment() {
// this.$store.dispatch('add', this.num)
//没有业务逻辑,直接提交
this.$store.commit('ADD', this.num)
},
decrement() {
//this.$store.dispatch('minus', this.num)
this.$store.commit('MINUS', this.num)
},
incrementOdd() {
//业务逻辑,写在store中
this.$store.dispatch('addOdd', this.num)
// if (this.$store.state.count % 2) {
// this.$store.dispatch('add', this.num)
// }
},
incrementWait() {
this.$store.dispatch('addWait', this.num)
// setTimeout(() => {
// this.$store.dispatch('add', this.num)
// }, 1000)
}
}
}
</script>
<template>
<div>
<h1>当前和为:{{ $store.state.count }}</h1>
<select v-model.number="num">
<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>
<style scoped>
button {
margin: 0 10px;
}
</style>
3.5 getters的使用
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
在
store.js中追加getters配置...... const getters = { bigSum(state){ return state.sum * 10 } } //创建并暴露store export default new Vuex.Store({ ...... getters })组件中读取数据:
$store.getters.bigSum
3.6 四个map方法的使用
导入
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
mapState方法:用于帮助我们映射
state中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), },mapGetters方法:用于帮助我们映射
getters中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) },mapActions方法:用于帮助我们生成与
actions对话的方法,即:包含$store.dispatch(xxx)的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) }mapMutations方法:用于帮助我们生成与
mutations对话的方法,即:包含$store.commit(xxx)的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则传的参数是事件对象(event)。
Count.vue
<script>
import { mapState,mapGetters,mapMutations,mapActions } from 'vuex'
export default {
name: "MyCount",
data() {
return {
num: 1
}
},
computed: {
//手动写计算属性,
// count() {
// return this.$store.state.count
// },
// evenOrOdd() {
// return this.$store.getters.evenOrOdd
// },
// race() {
// return this.$store.state.race
// },
// score() {
// return this.$store.state.score
// },
//使用mapState生成计算属性,从state中获取
//...(展开运算符):因为 mapState 返回的是一个对象,使用 ... 可以将其展开并合并到组件的 computed 属性中。
/* ...mapState({
count: 'count', // 将 Vuex 的 `state.count` 映射到组件的 `count` 计算属性
race: 'race', // 将 Vuex 的 `state.race` 映射到组件的 `race` 计算属性
score: 'score' // 将 Vuex 的 `state.score` 映射到组件的 `score` 计算属性
}),
*/
//可以简写,因为key和value相同.数组写法
...mapState(['count', 'race', 'score']),
// bigCount() {
// return this.$store.getters.bigCount
// },
...mapGetters(['bigCount', 'evenOrOdd'])
},
methods: {
// increment() {
// //没有业务逻辑,直接提交
// this.$store.commit('ADD', this.num)
// },
// decrement() {
// //this.$store.dispatch('minus', this.num)
// this.$store.commit('MINUS', this.num)
// },
// 使用mapMutations生成方法, mapMutations会返回一个对象,对象中的方法会映射成组件的methods.注意这个时候需要传参,应为生成的函数是带参数的。
...mapMutations({increment: 'ADD', decrement: 'MINUS'}),
//数组写法,注意,生成的函数名字是ADD,MINUS,组件的点击事件函数名要修改,如@click="ADD(num)"
// ...mapMutations(['ADD', 'MINUS']),
// incrementOdd() {
// //业务逻辑,写在store中
// this.$store.dispatch('addOdd', this.num)
// },
// incrementWait() {
// this.$store.dispatch('addWait', this.num)
// }
...mapActions({incrementOdd: 'addOdd', incrementWait: 'addWait'}),
//数组写法,对应点击函数也要改
// ...mapActions(['addOdd', 'addWait'])
},
mounted() {
console.log(mapState({ss: 'count'}))
}
}
</script>
<template>
<div>
<h1>我在{{ race }}赢得了{{ score }}分好成绩!你也来试试吧!</h1>
<h1>当前和为:{{ count }}</h1>
<h1>当前和是:{{ evenOrOdd }}</h1>
<h1>当前和扩大10倍是:{{ bigCount }}</h1>
<select v-model.number="num">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment(num)">+</button>
<button @click="decrement(num)">-</button>
<button @click="incrementOdd(num)">当前和为奇数,则执行加法</button>
<button @click="incrementWait(num)">等一等再加</button>
</div>
</template>
<style scoped>
button {
margin: 0 10px;
}
</style>
3.7 模块化+命名空间
目的:让代码更好维护,让多种数据分类更加明确。
修改
store.jsconst 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 中的state 必须加上 'countAbout' ...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'}),
模块拆分后,可以单独写count.js,person.js,然后export,在index.js中import。
count.js
export default {
namespaced: true,//命名空间,默认为false,为true,mapState,mapMutations,mapGetters,mapActions会自动加上该模块的namespace
actions: {
addOdd(context, value) {
if (context.state.count % 2) {
context.commit('ADD', value)
}
},
addWait(context, value) {
setTimeout(() => {
context.commit('ADD', value)
}, 1000)
}
},
mutations: {
ADD(state, value) {
state.count += value
},
MINUS(state, value) {
state.count -= value
}
},
state: {
count: 0,
race: "bcym",
score: 100
},
getters: {
evenOrOdd(state) {
return state.count % 2 ? '奇数' : '偶数'
},
bigCount(state) {
return state.count * 10
}
}
}
person.js
import axios from 'axios'
import { nanoid } from 'nanoid'
export default {
namespaced: true,
actions: {
addPersonBc(context, value) {
//只允许添加bc开头的
if(value.name.indexOf('bc')===0){
context.commit('ADD_PERSON', value)
}else {
alert('bc开头的才被允许添加')
}
},
addPersonServer(context) {
axios.get('https://www.klapi.cn/api/yiyan.php?type=txt').then(
res => {
context.commit('ADD_PERSON', {id: nanoid(), name: res.data})
},
err => {
alert('请求失败:', err.message)
}
)
}
},
mutations: {
ADD_PERSON(state, value) {
state.personList.unshift(value)
}
},
state: {
personList: [
{id: '001', name: '张三'},
{id: '002', name: '李四'},
{id: '003', name: '王五'},
{id: '004', name: 'bcym'}
]
},
getters: {
firstPersonName(state) {
return state.personList[0].name
}
}
}
4. 路由
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件。
4.1 基本使用
安装vue-router,命令:
npm i vue-router应用插件:
Vue.use(VueRouter)编写router配置项:
//引入VueRouter import VueRouter from 'vue-router' //引入路由 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] })实现切换(active-class可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>指定展示位置
<router-view></router-view>
4.2 几个注意点
- 路由组件通常存放在
pages文件夹,一般组件通常存放在components文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router属性获取到。
4.3 多级路由(嵌套路由)
配置路由规则,使用children配置项:
routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]跳转(要写完整路径):
<router-link to="/home/news">News</router-link>指定展示位置
<router-view></router-view>
4.4 路由的query参数
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>接收参数:
$route.query.id $route.query.title
4.5 命名路由
作用:可以简化路由的跳转。
如何使用
给路由命名:
{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }简化跳转:
<!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>
4.6 路由的params参数
配置路由,声明接收params参数
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数:
$route.params.id $route.params.title
4.7 路由的props配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route) {
return {
id: $route.query.id,
title:$route.query.title,
a: 1,
b: 'hello'
}
}
}
方便在要跳转去的组件里更简便的写法
跳转去组件的具体代码
<template>
<ul>
<h1>Detail</h1>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}}</li>
<li>a:{{a}}</li>
<li>b:{{b}}</li>
</ul>
</template>
<script>
export default {
name: 'Detail',
props: ['id', 'title', 'a', 'b'],
mounted () {
console.log(this.$route);
}
}
</script>
<style>
</style>
4.8 <router-link>的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push(栈结构)和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push - 如何开启
replace模式:<router-link replace .......>News</router-link>
4.8 <router-link>的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push - 如何开启
replace模式:<router-link replace .......>News</router-link>
4.9 编程式路由导航
作用:不借助
<router-link>实现路由跳转,让路由跳转更加灵活具体编码:
//$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
4.10 缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁。
具体编码:
这个 include 指的是组件名
<keep-alive include="News"> <router-view></router-view> </keep-alive>
4.11 两个新的生命周期钩子
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。 具体名字:
activated路由组件被激活时触发。deactivated路由组件失活时触发。
这两个生命周期钩子需要配合前面的缓存路由组件使用(没有缓存路由组件不起效果)
activated() {
console.log('news组件被激活了');
this.timer = setInterval(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) {
this.opacity = 1;
}
}, 18);
},
deactivated() {
console.log('news组件失活了');
clearInterval(this.timer);
}
4.12 路由守卫
作用:对路由进行权限控制
分类:全局守卫、独享守卫、组件内守卫
全局守卫:
//全局前置路由守卫————初始化时、每次路由切换之前调用 router.beforeEach((to,from,next)=>{ console.log(to,from) // if(to.name === 'message' || to.name === 'news'){}。用name判断也行 // 如果to.path等于/home/message或者/home/news,则跳转到/home,否则放行 //to.path === '/home/message' || to.path === '/home/news' if(to.meta.isAuth){ if(localStorage.getItem('user')==='zqg'){ //放行 next() }else { alert('user不是zqg,无权限。') } }else{ //放行 next() } }) //全局后置路由守卫————初始化时、每次路由切换之后调用 router.afterEach((to,from)=>{ console.log(to,from) document.title = to.meta.title || 'Vue_Router' })完整代码
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由 组件
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
name:'about',
path:'/about',
component:About,
meta:{title:'关于'}
},
{
name:'home',
path:'/home',
component:Home,
meta:{title:'主页'},
children:[
{
path:'news', //注意这里的path不要写/
component:()=>import('../pages/News.vue'),
meta:{isAuth:true,title:'新闻'}
},
{
path:'message',
component:()=>import('../pages/Message.vue'),
meta:{isAuth:true,title:'消息'},
children:[
{
name:'detail',
path:'detail/:id/:title',
component:()=>import('../pages/Detail.vue'),
// props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件
/* props:{
a:1,
b:'hello'
}*/
// props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件
//props:true,
// props的第三种写法,值为函数,params和query都适用。该对象中的所有key-value都会以props的形式传给Detail组件
props($route){
return {
id:$route.params.id,
title:$route.params.title
}
}
}
]
}
]
}
]
})
//全局前置路由守卫————初始化时、每次路由切换之前调用
router.beforeEach((to,from,next)=>{
console.log(to,from)
// if(to.name === 'message' || to.name === 'news'){}。用name判断也行
// 如果to.path等于/home/message或者/home/news,则跳转到/home,否则放行
//to.path === '/home/message' || to.path === '/home/news'
if(to.meta.isAuth){
if(localStorage.getItem('user')==='zqg'){
//放行
next()
}else {
alert('user不是zqg,无权限。')
}
}else{
//放行
next()
}
})
//全局后置路由守卫————初始化时、每次路由切换之后调用
router.afterEach((to,from)=>{
console.log(to,from)
document.title = to.meta.title || 'Vue_Router'
})
export default router
4.独享守卫:
就是在 routes 子路由内写守卫,只对单独的路由起作用
//独享路由守卫
beforeEnter:(to,from,next)=>{
if(to.meta.isAuth){
if(localStorage.getItem('user')==='zqg'){
//放行
next()
}else {
alert('user不是zqg,无权限。')
}
}else{
//放行
next()
}
}
5.组件内守卫:
在具体组件内写守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
4.13 路由器的两种工作模式
- 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
- hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
- hash模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
- history模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
5.Vue UI组件库
5.1 移动端常用UI组件库
Mint UI http://mint-ui.github.io
…
5.2 PC端常用UI组件库
- Element UI https://element.eleme.cn
- IView UI https://www.iviewui.com
- …
Vue3快速上手
1.Vue3简介
- 2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
- 耗时2年多、2600+次提交、30+个RFC、600+次PR、99位贡献者
- github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.0
2.Vue3带来了什么
1.性能的提升
打包大小减少41%
初次渲染快55%, 更新渲染快133%
内存减少54%
……
2.源码的升级
使用Proxy代替defineProperty实现响应式
重写虚拟DOM的实现和Tree-Shaking
……
3.拥抱TypeScript
- Vue3可以更好的支持TypeScript
4.新的特性
Composition API(组合API)
- setup配置
- ref与reactive
- watch与watchEffect
- provide与inject
- ……
新的内置组件
- Fragment
- Teleport
- Suspense
其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除keyCode支持作为 v-on 的修饰符
- ……
一、创建Vue3.0工程
1.使用 vue-cli 创建
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
2.使用 vite 创建
官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
vite官网:https://vitejs.cn
- 什么是vite?—— 新一代前端构建工具。
- 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
- 传统构建 与 vite构建对比图

## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
3.分析文件目录
main.js
Vue2项目的main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
我们再来看看Vue3项目中的main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
分析一下
// 引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from 'vue'
import App from './App.vue'
// 创建应用实例对象——app(类似于之前Vue2中的vm,但app比vm更“轻”)
const app = createApp(App)
console.log(app)
// 挂载
app.mount('#app')
App.vue
我们再来看看组件
在template标签里可以没有根标签了
<template>
<!-- Vue3组件中的模板结构可以没有根标签 -->
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
二、常用 Composition API
官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html
1.拉开序幕的setup
- 理解:Vue3.0中一个新的配置项,值为一个函数。
- setup是所有Composition API(组合API)“ 表演的舞台 ”。
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
- 但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
- 如果有重名, setup优先。
- setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
- 尽量不要与Vue2.x配置混用
2.ref函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value - 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()的get与set完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive函数。
3.reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref函数) - 语法:
const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
4.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'
5.reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()的get与set来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value,读取数据时模板中直接读取不需要.value。 - reactive定义的数据:操作数据与读取数据:均不需要
.value。
- ref定义的数据:操作数据需要
6.setup的两个注意点
setup执行的时机
- 在beforeCreate之前执行一次,this是undefined。
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
this.$attrs。 - slots: 收到的插槽内容, 相当于
this.$slots。 - emit: 分发自定义事件的函数, 相当于
this.$emit。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于
7.计算属性与监视
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配置的回调执行了') })
8.生命周期


- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy改名为beforeUnmountdestroyed改名为unmounted
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate===>setup()created=======>setup()beforeMount===>onBeforeMountmounted=======>onMountedbeforeUpdate===>onBeforeUpdateupdated=======>onUpdatedbeforeUnmount==>onBeforeUnmountunmounted=====>onUnmounted
9.自定义hook函数
什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。
类似于vue2.x中的mixin。
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
10.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” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
……
