Vue是一套用于构建用户界面的渐进式JavaScript框架,包括Vue核心和模板解析器两部分。
特点:
采用组件化模式,提高代码复用率,让代码更好维护;
声明式编码,让编码人员无需直接操作DOM,提高开发效率;
使用虚拟DOM和优秀的Diff算法,尽量复用DOM节点。
安装:尚硅谷B站Vue教程
1. 初识
- 引入Vue:
<script type="text/javascript" src="../vue/vue.js"></script>
- 使用步骤:
准备一个容器 → \rightarrow → 创建一个Vue实例对象,传入一个配置对象
容器里的代码依然符合 htnl 规范,只不过混入了一些特殊的 Vue 语法(比如插值语法);容器里的代码被称为Vue模板。 - 容器和实例是一一对应的
- 真实开发中只有一个Vue实例,并且会配合组件一起使用;
{{xxx}}
中的xxx
要写js表达式,且xxx
可以自动读取到data
中的所有属性;- 一旦
data
中的数据发生改变,那么页面中用到该数据的地方也会自动更新。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 令视口为完美视口 -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue2</title>
<link rel="shortcut icon" href="../imgs/kirlant.ico">
<!-- 引入Vue(开发版) -->
<script type="text/javascript" src="../vue/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>hello, {{name}}</h1>
</div>
<script>
Vue.config.productionTip = false; // 阻止 vue 在启动时生成生产提示
// 创建 Vue 实例, 传入的参数只有一个,是配置对象
new Vue({
el:'#root', // el 用于指定当前 Vue 为哪个容器服务,其值通常为CSS选择器字符串
data:{ // data 存储数据, 供 el 指定的容器使用
name:"lcy"
}
});
</script>
</body>
</html>
- 注意区分js表达式和js代码
js表达式 一个表达式会产生一个值,可以放在任何一个需要值的地方,如a === b ? true : false
;
js代码(语句) 控制代码的走向,如for(){}
。 el
与data
的另一种写法
el: 把Vue实例赋值给一个常量对象v
,然后用v.$mount()
绑定容器;
data: 函数式写法,需要返回一个对象;这个函数要给Vue实例对象调用,所有不能写成箭头函数(会变成Window调用);学了组件之后必须用函数式嗷。
<body>
<!-- 准备好一个容器 -->
<div id="root">
el 和 data 的写法二{{name}}
</div>
<script>
Vue.config.productionTip = false; // 阻止 vue 在启动时生成生产提示
// 创建 Vue 实例, 传入的参数只有一个,是配置对象
const v = new Vue({
// el:'#root',
data(){
return {
name:"嗷"
}
}
});
v.$mount('#root');
</script>
</body>
- MVVM模型
M(Model )模型: data中的数据;
V(View) 视图: 模板代码;
VM(ViewModel)视图模型: Vue实例。
data中所有的属性,最后都能出现在vm上;
vm上所有的属性及Vue原型上的所有属性,在Vue模板中都可以直接使用。
2. 模板语法
2.1. 插值语法
功能 解析标签体的内容;
语法 {{xxx}}
,xxx
是js表达式,可以直接读取到data
中的所有属性。
2.2. 指令语法
功能 解析标签,包括标签属性、标签体内容、绑定事件等;
举例 v-bind:href="xxx"
(可简写为:href="xxx"
),xxx
是js表达式;
备注 Vue中有很多指令,形式都是v-???:
;只有v-bind:
可以简写成:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue2</title>
<link rel="shortcut icon" href="../imgs/kirlant.ico">
<link rel="stylesheet" href="./learn.css">
<script type="text/javascript" src="../vue/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>插值语法</h1>
<h3>hello, {{name}}</h3>
<hr/>
<h1>指令语法</h1>
<!-- v-bind 绑定 -->
<a v-bind:href="url_a1">{{school.name}} 指令语法</a><br>
<a :href="url_a2">bilibili !</a><br>
<hr/>
</div>
<script>
Vue.config.productionTip = false; // 阻止 vue 在启动时生成生产提示
// 创建 Vue 实例, 传入的参数只有一个,是配置对象
new Vue({
el:'#root', // el 用于指定当前 Vue 为哪个容器服务,其值通常为CSS选择器字符串
data:{ // data 存储数据, 供 el 指定的容器使用
name:"lcy",
school:{
name:"尚硅谷",
url_a1:"https://www.bilibili.com/video/BV1Zy4y1K7SH?p=7&spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=2ba3a6b645cd54d53aa937c0d558fa0a",
url_a2:"https://www.bilibili.com/"
}
}
});
</script>
<!-- <script type="text/javascript" src="./learn.js"></script> -->
</body>
</html>
3. 数据绑定
3.1. 单向数据绑定
v-bind:
数据只能从data流向页面;
3.2. 双向数据绑定
v-model:
数据不仅能从data流向页面,也能从页面流向data;
只能应用在表单类元素之中(如 input、select),也就是有输入功能的标签中,这些标签都有value
属性;
v-model:value
可以简写为v-model
,因为v-model
默认收集value
值;
<body>
<!-- 准备好一个容器 -->
<div id="root">
单向数据绑定:<input type="text" title="single" v-bind:value="name_single"><br>
双向数据绑定:<input type="text" title="duplexing" v-model:value="name_duplexing"><br>
</div>
<script>
Vue.config.productionTip = false; // 阻止 vue 在启动时生成生产提示
// 创建 Vue 实例, 传入的参数只有一个,是配置对象
new Vue({
el:'#root',
data:{
name_single:"single bind",
name_duplexing:"duplexing bind"
}
});
</script>
</body>
4. 数据代理
4.1. Object.defineProperty()方法
配置:
enumerable
属性是否可枚举,默认false
writable
:属性是否可修改,默认false
configurable
:属性是否可删除,默认false
… …
get()
:当有人读取设置的属性时,get()函数就会被调用,返回值就是属性的值;
set()
:当有人修改设置的属性时,set()函数就会被调用,返回值是修改后的值;
<script>
let number = 16;
let person = {
name:"yyt",
sex:"female"
};
Object.defineProperty(person,'age',{
get(){
return number;
},
set(value){
number = value;
console.log("被改了!变成了",value,"呜呜呜");
}
});
console.log(person);
</script>
<!-- Object.defineProperty() 把 number 和 person.age联系起来了。 -->
4.2. Vue中的数据代理
通过vm对象代理data对象中属性的操作(读 / 写),更加方便地操作data中的数据。
基本原理:
通过Object.defineProperty()
把data
对象中的所有属性添加到vm
上
→
\rightarrow
→ 分别为每一个添加到vm上的属性指定一个getter
/setter
→
\rightarrow
→ 在getter
/setter
内部操作(读 / 写)data
中对应的属性。
5. 事件处理
5.1. 事件的基本使用
- 使用
v-on:xxx
或@xxx
绑定事件,其中xxx
是事件名; - 事件的回调函数需要配置在
methods
对象中,最终会出现在vm上; - method中的函数不能用箭头函数,否则其
this
将不再是vm
或组件实例对象,而是window
; - method中配置的函数都是被Vue管理的函数;
@click="demo"
和@click=demo($event)
效果一致,后者可以在自身基础上传参;
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>welcome to wazard world</h2>
<button @click="show_tips">tips</button>
<button @click="show_myNum($event, '0007')">show my number</button>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
},
methods:{
show_tips(event){
alert("APPRENTICE, WELCOME TO SAVAGE CAVE!");
},
show_myNum(event, number){
console.log(number);
}
}
});
</script>
</body>
5.2. 事件修饰符
- prevent 阻止默认事件(常用)
- stop 阻止事件冒泡(常用)
- once 事件只触发一次(常用)
- capture 使用事件的捕获模式
- self 只有event.target是当前操作的元素时才能触发事件
- passive 事件的默认行为立即执行,无需等待事件回调执行完毕 累了不想抄了,看视频吧12:00
修饰符可以连续写,比如@click.prevent.stop
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>welcome to {{name}}</h2>
<!-- .prevent 让页面不在自动跳转 -->
<a href="https://www.bilibili.com" @click.prevent="show_tips">tips</a>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
name:"bilibili"
},
methods:{
show_tips(event){
alert("welcome to bilibili!");
}
}
});
</script>
</body>
5.3. 键盘事件
- Vue中常用的按键别名
回车 → \rightarrow → enter
删除 → \rightarrow → delete(捕获 delete 和 backspace 两个按键)
退出 → \rightarrow → esc
空格 → \rightarrow → space
换行 → \rightarrow → tab 把焦点从当前元素上切走,所以不要用keyup
,要用keydown
嗷
上 → \rightarrow → up
下 → \rightarrow → down
左 → \rightarrow → left
右 → \rightarrow → right - Vue未提供别名的按键,可以使用按键的原始key值进行绑定,但要注意转为kebab-case(短横线命名);
- 系统修饰键:
ctrl、alt、shift、meta(home键)
配合keyup
使用:按下修饰键的同时再按下其他键,随后释放其他键,事件才被触发;
配合keydown
使用:正常触发事件。 - 可以使用keyCode指定具体按键(不推荐)
- 定制按键别名:
Vue.config.keyCodes.自定义键名 = 键码
<body>
<!-- 准备好一个容器 -->
<div id="root">
<div id="title"><h2>welcome to {{name}}</h2></div>
<input type="text" placeholder="按下Enter提示输入" @keyup.enter="pressEnter">
<input type="text" placeholder="按下CapsLk提示输入" @keyup.caps-lock="pressCL">
<input type="text" placeholder="按下Tab提示输入" @keydown.tab="pressTab">
<input type="text" placeholder="按下ctrl提示输入" @keyup.ctrl="pressCtrl">
<input type="text" placeholder="按下y提示输入" @keyup.yyt="pressY">
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
Vue.config.keyCodes.yyt = 89;
const vm = new Vue({
el:"#root",
data:{
name:"bilibili"
},
methods:{
pressEnter(event){
alert(event.target.value);
},
pressCL(event){
alert(event.target.value + ', 成功了!');
},
pressTab(event){
alert(event.target.value + ', 好奇怪哦。');
},
pressCtrl(event){
alert(event.target.value + ', ctrl!');
},
pressY(event){
alert(event.target.value + ', 我的y嗷');
}
}
});
</script>
</body>
6. 计算属性与监视
6.1. 计算属性 computed
用已经存在的属性计算出一个全新的属性。computed 的优势在于它拥有缓存机制 ( 复用 ),效率更高,调试方便;而 mathods 每用到一次方法就会进行一次调用。
- 要用的属性不存在,需要通过已有的属性 (由vm管理的属性) 计算得到;
- 在
computed
对象 中定义计算属性; - 在页面中使用
{{方法名}}
来显示计算结果。 - 原理 底层借助了
Object.defineproperty
方法提供的getter
和setter
get()
函数在初次读取计算属性和计算时所依赖的属性发生改变时被调用;
set()
函数在计算属性被修改且会引起依赖的属性发生改变时被调用。
插值语法举例
<body>
<div id="root">
<div id="title"><h2>welcome to bilibili</h2></div>
<div class="name">姓:<input type="text" title="firstName" v-model="firstName"></div>
<div class="name">名:<input type="text" title="lastName" v-model="lastName"></div>
<div class="name">全名:{{firstName}} · {{lastName}}</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
firstName:"Saunders",
lastName:"Igulo"
}
});
</script>
</body>
methods举例:只要data中的数据发生改变,Vue就会重新解析模板。
<body>
<div id="root">
<div id="title"><h2>welcome to yyt's test</h2></div>
<div class="name">姓:<input type="text" title="firstName" v-model="firstName"></div>
<div class="name">名:<input type="text" title="lastName" v-model="lastName"></div>
<div class="name">全名:{{fullName()}}</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
firstName:"Saunders",
lastName:"Igulo"
},
methods:{
fullName(){
return this.firstName+' · '+this.lastName;
}
}
});
</script>
</body>
计算属性举例
<body>
<div id="root">
<div id="title"><h2>welcome to yyt's test</h2></div>
<div class="name">姓:<input type="text" title="firstName" v-model="firstName"></div>
<div class="name">名:<input type="text" title="lastName" v-model="lastName"></div>
<div class="name">全名:{{fullName}}</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
firstName:"Saunders",
lastName:"Igulo"
},
computed:{
fullName:{
// 当有人读取 fullName 时,get() 就会被调用,且返回值为 fullName 的值
get(){
return this.firstName+' · '+this.lastName;
},
// 当 fullName 被修改时,会调用set()
set(value){
splitName = value.split(' ');
this.firstName = splitName[0];
this.lastName = splitName[2];
}
}
}
});
</script>
</body>
计算属性简写 确定该属性只读不改时使用,以属性名为函数名,代替get()函数。
computed:{
fullName(){
return this.firstName+' · '+this.lastName;
}
}
#root{
width: 400px;
text-align: center;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-items: center;
#title{
width: 100%;
}
.name{
width: 240px;
margin-top: 10px;
text-align: left;
input{
width: 200px;
}
}
}
练习:天气切换
<body>
<div id="root">
<div id="title"><h2>welcome to yyt's test</h2></div>
<div class="content">
<div class="weather">今日天气:{{weather}}</div>
<div class="weather"><button @click="changeWeather">切换天气</button></div>
</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
isHot:true
},
methods: {
changeWeather(){
this.isHot = !this.isHot;
}
},
computed:{
weather(){
return this.isHot ? '热呜呜呜':'凉爽嗷';
}
}
});
</script>
</body>
#root{
width: 400px;
text-align: center;
#title{
width: 100%;
border: 1px solid #aaa;
}
.content{
width: 100%;
height: 200px;
border: 1px solid #aaa;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-items: center;
.weather{
width: 300px;
margin-top: 15px;
text-align: left;
}
}
}
6.2. 监视属性 watch
- 当监视的属性发生变化时,回调函数自动被调用,进行相关操作;
- 属性必须存在才能被监视;
- 计算属性也可以被监视;
- 两种添加监视的写法:new Vue()时传入watch配置;vm实例化后通过$watch()监视。
以上面的切换天气为例,在vm的配置对象中添加watch
属性,监视isHot
的变化:
watch:{
isHot:{
// immediate:true 初始化时调用一次 handler, 默认值是 false
handler(newValue, oldValue){
console.log('isHot 被改了嘤嘤嘤', oldValue+' --> '+newValue);
}
}
}
方法二:在vm实例对象初始化完成后,使用$绑定监视
const vm = new Vue({......});
vm.$watch('isHot', {
handler(newValue, oldValue){
console.log('isHot被改了嘤嘤嘤', oldValue+' --> '+newValue);
}
});
简写
watch:{
isHot(newValue, oldValue){
console.log('isHot 被改了嘤嘤嘤', oldValue+' --> '+newValue);
},
}
vm.$watch('isHot', function(newValue, oldValue){
console.log('isHot 被改了嘤嘤嘤', oldValue+' --> '+newValue);
})
- 深度监视
Vue中的watch默认不监视对象内部值的改变(只能监视一层),配置了deep:true
之后则可以监视对象内部值的改变(监视好多层了嗷);
Vue自身可以监测对象内部值的改变,只是它的watch默认不可以嗷;
使用watch时要根据数据的具体结构确定是否要采用深度监视。
<body>
<div id="root">
<div id="title"><h2>welcome to yyt's test</h2></div>
<div class="content">
<div>
<button @click="numbers.a++;">a++</button>
<button @click="numbers.a--;">a--</button>
a的值是 {{numbers.a}}
</div>
<div>
<button @click="numbers.b++;">b++</button>
<button @click="numbers.b--;">b--</button>
b的值是 {{numbers.b}}
</div>
</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
numbers:{
a:1,
b:1
}
},
watch:{
// 监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(newValue, oldValue){
console.log('numbers被改了嘤嘤嘤');
}
}
}
});
</script>
</body>
6.3. watch 和 conputed 对比
- computed能完成的功能,watch都能完成;
- watch能完成的功能,computed不一定能完成,比如watch可以实现异步计算,而computed不可以;
用watch写6.1节的全名计算:
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:"#root",
data:{
firstName:"Saunders",
lastName:"Igulo",
fullName:""
},
watch:{
firstName:{
immediate: true,
handler(newValue){
// 箭头函数的this指向当前作用域,所以此时定时器里的箭头函数的this指向的是vm
setTimeout(()=>{this.fullName = newValue+' · '+this.lastName;}, 1000);
}
},
lastName:{
immediate: true,
handler(newValue){
this.fullName = this.firstName+' · '+newValue;
}
}
}
});
</script>
被Vue管理的函数要写成普通函数,这样this才能指向vm或者组件实例对象;
不被Vue管理的函数(比如定时器、Ajax、promise的回调函数等)最好写成箭头函数,这样才能让this指向vm或者组件实例对象。
#root{
width: 400px;
border: 1px solid #aaa;
padding-bottom: 10px;
text-align: center;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-items: center;
#title{
width: 100%;
border-bottom: 1px solid #aaa;
}
.name{
width: 240px;
margin-top: 10px;
text-align: left;
input{
width: 200px;
}
}
}
6.4. class 与 style 绑定
- 绑定class样式
:class="vm.data中对应于样式的属性名"
写法 | 适用场景 |
---|---|
字符串 | 样式的类名不确定,需要动态指定 |
数组 | 要绑定的样式个数不确定,名字也不确定 |
对象 | 要绑定的样式个数确定,名字也确定,但不确定用不用 |
<body>
<div id="root">
<!-- 字符串写法 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
<!-- 数组写法 -->
<div class="basic" :class="classArr">{{name}}</div>
<!-- 对象写法 -->
<div class="basic" :class="classObj">{{name}}</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
name:'yyt',
mood:'normal',
classArr:['yyt1', 'yyt2', 'yyt3'],
classObj:{
yyt1:false,
yyt2:true,
yyt3:true
}
},
methods: {
changeMood(){
// 随机改变心情
let allMoods = ['happy','sad','normal'];
this.mood = allMoods[Math.floor(Math.random()*3)];
}
},
});
</script>
</body>
.basic{
width: 200px;
height: 50px;
border: 1px solid #aaa;
padding: 20px;
}
.happy{
background-color: rgb(206, 172, 232);
}
.sad{
background-color: rgb(191, 255, 178);
}
.normal{
background-color: antiquewhite;
}
.yyt1{
border-radius: 10px;
}
.yyt2{
font-size: 35px;
text-align: center;
}
.yyt3{
background-color: rgb(197, 201, 204);
}
- 绑定style样式
真会玩啊 08:20
<div class="basic" :style="styleObj">{{name}}</div>
... ...
data:{
name:'yyt',
styleObj:{
fontSize: '20px'
}
}
7. 渲染
7.1. 条件渲染
v-if
、v-else-if
与v-else
可以用templete
标签包裹使用同一组v-if
的元素,不会破坏结构;
适用于切换频率较低的场景;
不展示的DOM元素直接被移除;
v-if
、v-else-if
与v-else
一起使用时,结构不能被打断。v-show
适用于切换频率较高的场景;
不展示的DOM元素不被移除,只是用样式隐藏掉。
使用v-if
时元素可能无法获取到,而使用v-show
一定可以获取到。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue2</title>
<link rel="shortcut icon" href="../imgs/kirlant.ico">
<link rel="stylesheet" href="./learn.css">
<script type="text/javascript" src="../vue/vue.js"></script>
<style>
#root{
width: 300px;
border: 1px solid #aaa;
line-height: 50px;
text-align: center;
}
</style>
</head>
<body>
<div id="root" @click="change">
<div v-show="ifShow">welcome to {{name}}'s 1st test</div>
<div v-if="showIdx == 0">welcome to {{name}}'s 2nd test</div>
<div v-else-if="ifShow == 1">welcome to {{name}}'s 3rd test</div>
<div v-else>welcome to {{name}}'s 4th test</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
name:'yyt',
ifShow:true,
showIdx:2
},
methods: {
change(){
this.ifShow = !this.ifShow;
this.showIdx = (this.showIdx+1)%3;
}
}
});
</script>
</body>
</html>
7.2. 列表渲染 v-for
7.2.1 基本用法
用于展示列表数据;
语法:v-for="(item, index) in xxx" :key="index"
;
可以遍历数组、对象、字符串、指定次数(后两者用得少)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue2</title>
<link rel="shortcut icon" href="../imgs/kirlant.ico">
<link rel="stylesheet" href="./learn.css">
<script type="text/javascript" src="../vue/vue.js"></script>
<style>
#root{
width: 300px;
border: 1px solid #aaa;
padding: 10px 0;
text-align: center;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-content: center;
}
ul{
width: 80%;
text-align: left;
border-top: 1px solid #aaa;
border-bottom: 1px solid #aaa;
}
</style>
</head>
<body>
<div id="root">
<div>Welcome to yyt's list</div>
<!-- 遍历数组 -->
<ul><li v-for="p in persons" :key="p.id">{{p.name}} - {{p.age}}</li></ul>
<!-- <li v-for="(p,idx) in persons" :key="idx">{{p.name}} - {{p.age}}</li> -->
<!-- 遍历对象 -->
<ul><li v-for="(val,k) of wizards" :key="k">{{k}} : {{val}}</li></ul>
<!-- 遍历字符串 -->
<ul><li v-for="(val,k) of yyt" :key="k">[{{k}}] {{val}}</li></ul>
<!-- 遍历指定次数-->
<ul><li v-for="(val,k) of 5" :key="k">idx:{{k}} - val:{{val}}</li></ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'yyt',age:16},
{id:'002',name:'lyl',age:18},
{id:'003',name:'lcy',age:20}
],
wizards:{
name:'santos',
age:207,
orgnization:'Savage cave'
},
yyt:"yyt"
}
});
</script>
</body>
</html>
7.2.2. key的作用与原理
- 虚拟DOM中key的作用
key 是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较。 - 对比规则
旧虚拟DOM中找到了与新虚拟DOM相同的key:
\qquad 虚拟DOM中内容没变 → \rightarrow → 直接使用之前的真实DOM;
\qquad 虚拟DOM中内容改变 → \rightarrow → 生成新的真实DOM,然后替换之前的真实DOM;
旧虚拟DOM中未找到与新虚拟DOM相同的key:
\qquad 创建新的真实DOM,然后渲染到页面。 - 用index作为key可能会引发的问题
若对数据进行逆序添加、逆序删除等破坏顺序的操作:
\qquad 会产生没有必要的真实DOM更新,界面效果没问题,但效率低;
如果结构中包含输入类的DOM:
\qquad 会产生错误DOM更新,界面有问题。 - 开发中如何选择key
最好用每条数据的唯一标识作为key,如id、手机号、身份证号、学号等唯一值;
如果不存在破坏顺序的操作,仅用于渲染列表用于展示,那么用index也没什么问题。
v-for遍历时,默认会把遍历时的index作为key的值。10:30
<div id="root">
<div>Welcome to yyt's list</div>
<ul>
<li v-for="p in persons" :key="p.id">
<!--
<li v-for="(p, idx) in persons" :key="idx">
<li v-for="p in persons">
这两种情况中,后面的 input 框都会出现错位
-->
{{p.name}} - {{p.age}}
<input type="text" title="info"></li>
</ul>
<button type="button" @click.once="add">add a person</button>
</div>
7.3. 列表过滤 列表排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue2</title>
<link rel="shortcut icon" href="../imgs/kirlant.ico">
<script type="text/javascript" src="../vue/vue.js"></script>
<style>
#root{
width: 500px;
border: 1px solid #aaa;
padding: 10px 0;
text-align: center;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-content: center;
}
ul{
width: 80%;
text-align: center;
list-style: none;
border-top: 1px solid #aaa;
border-bottom: 1px solid #aaa;
margin: 0;
}
.gap{
margin: 5px 0;
}
</style>
</head>
<body>
<div id="root">
<div class="gap">Welcome to yyt's list</div>
<div class="gap">
<input type="text" title="serch" placeholder="请输入姓名" v-model="keyWord">
<button @click="sortStyle = 1;">年龄升序</button>
<button @click="sortStyle = 2;">年龄降序</button>
<button @click="sortStyle = 0;">原顺序</button>
</div>
<ul>
<li v-for="p in filPersons" :key="p.id">{{p.name}} - {{p.age}} - {{p.sex}}
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
keyWord:"",
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:21,sex:'女'},
{id:'003',name:'周杰伦',age:20,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
sortStyle:0 // 0 原顺序; 1 升序; 2 降序
},
computed:{
filPersons(){
// 过滤后的数组
const arr = this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1;
});
// 是否需要排序
if(this.sortStyle){
arr.sort((a,b)=>{
return this.sortStyle == 1 ? (a.age-b.age):(b.age-a.age);
});
};
return arr;
},
}
});
</script>
</body>
</html>
7.4. Vue监测数据的原理
Vue会监视data中所有层次的数据。
- 对象
通过setter实现监视,且要在 new Vue 时就传入要监视的数据。
对象中后追加的属性,Vue默认不做响应式处理;
如需给后添加的属性做响应式,需要使用Vue.set(target, propertyName/index, value)
或vm.$set(target, propertyName/index, value)
。 - 数组
通过包裹数组更新元素的方法实现,本质上做了两件事:调用原生对应的方法对数组进行更新 → \rightarrow → 重新解析模板,更新页面。
在Vue中修改数组的某个元素一定要用以下方法:push(), pop(), shift(), unshift(), splice(), sort(), reverse()
;Vue.set() 或 vm.$set()
。
Vue.set() 和 vm.$set() 不能给 vm 或 vm的根数据对象 添加属性。
8. 收集表单数据
v-model
配合不同的输入框有不同的使用技巧:
<input type="text">
v-model
收集的是value
值,用户输入的就是value
值;<input type="radio">
v-model
收集的是value
值,需要给标签配置value
值;<input type="checkbox">
未配置value
属性时,v-model
收集的是checked
值(勾选true,未勾选false);
配置value
属性时,收集的是value
组成的数组;
v-model
的三个修饰符:
- lazy 失去焦点时收集数据;
- number 输入字符串转为有效数字;
- trim 对输入字符串的首尾进行空格过滤。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue2</title>
<link rel="shortcut icon" href="../imgs/kirlant.ico">
<script type="text/javascript" src="../vue/vue.js"></script>
<style>
#root{
width: 500px;
border: 1px solid #aaa;
padding: 10px 0;
text-align: left;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-content: center;
}
.gap{
margin: 5px 0;
}
</style>
</head>
<body>
<div id="root">
<form @submit.prevent="submitAllInfo">
<!-- for 和 id 将两个标签联系起来,单击‘账号’的时候也能让 input 框获取焦点 -->
<label for="num">账号:</label><input type="text" id="num" v-model.trim="account"><br/>
<br/>
<label for="pwd">密码:</label><input type="password" id="pwd" v-model="password"><br/>
<br/>
年龄:
<input type="number" v-model.number="age"><br/>
<br/>
性别:
男<input type="radio" value="male" v-model="sex">
女<input type="radio" value="female" v-model="sex"><br/>
<br/>
爱好:
唱歌<input type="checkbox" value="sing" v-model="hobby">
游泳<input type="checkbox" value="swimming" v-model="hobby">
打麻将<input type="checkbox" value="mahjong" v-model="hobby"><br/>
<br/>
本命:
<select name="animation" v-model="animation" title="lovest animation character">
<option value="bf">白凤</option>
<option value="ssm">少司命</option>
<option value="ks">时崎狂三</option>
<option value="hy">四宫辉夜</option>
<option value="aer">阿尔托莉雅</option>
<option value="sd">塞巴斯蒂安</option>
</select><br/>
<br/>
其他信息:<br/><br/>
<textarea cols="30" rows="10" v-model.lazy="other_info"></textarea><br/>
<br/>
<input type="checkbox" v-model="access">
阅读并接受 <a href="https://www.bilibili.com">《用户协议》</a><br/>
<br/>
<button>提交</button>
</form>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
account:'',
password:'',
age:'16',
sex:'female',
hobby:[],
animation:'ssm',
other_info:'',
access:false
},
methods: {
submitAllInfo(){
alert(JSON.stringify(this._data));
}
},
});
</script>
</body>
</html>
9. 过滤器
- 定义
对要显示的数据进行特定格式化后再显示,适用于一些简单逻辑的处理。 - 语法
注册过滤器:Vue.filter(name, callback)
或new Vue({ filters:{ name(){} } })
使用过滤器:{{ xxx | 过滤器名 }}
或v-bind:属性 = " xxx | 过滤器名 "
- 备注
过滤器可以接收参数;
多个过滤器可以串联;
过滤器没有改变原本的数据,而是产生了新的数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue2</title>
<link rel="shortcut icon" href="../imgs/kirlant.ico">
<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.3/dayjs.min.js"></script>
<script type="text/javascript" src="../vue/vue.js"></script>
<style>
#root{
width: 500px;
border: 1px solid #aaa;
padding: 10px 0;
text-align: left;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-content: center;
}
.gap{
margin: 5px 0;
}
</style>
</head>
<body>
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 计算属性实现 -->
<h3>现在是 {{fmtTime}}</h3>
<!-- methods实现 -->
<h3>现在是 {{getFmtTime()}}</h3>
<!-- 过滤器实现:把 time 传给 timeFmt, 用 timeFmt 的返回值替换整个'time | timeFmt' -->
<h3>现在是 {{time | timeFmt}}</h3>
<!-- 过滤器实现:传参 -->
<h3>现在是 {{time | timeFmt('YYYY-MM-DD') | mySlice}}</h3>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
// 注册全局过滤器,必须写在 new Vue() 之前
Vue.filter('mySlice', function(value){
return value.slice(0, 4);
});
const vm = new Vue({
el:'#root',
data:{
time:1621561377603
},
computed: {
fmtTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
}
},
methods: {
getFmtTime(){
return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss');
}
},
// 局部过滤器
filters:{
timeFmt(value, str='YYYY-MM-DD HH:mm:ss'){
return dayjs(value).format(str);
}
}
});
</script>
</body>
</html>
10. 指令
10.1. 内置指令
v-bind
单向绑定解析表达式v-model
双向数据绑定v-for
遍历数组、对象、字符串v-on
绑定事件监听,可简写为@v-if
条件渲染(动态控制节点是否存在)v-else
条件渲染(动态控制节点是否存在)v-show
条件渲染(动态控制节点是否展示)v-text
向所在节点中渲染文本内容,会替换节点中的内容;解析文本,不解析标签嗷。v-html
向指定节点中渲染包含html结构的内容
与插值语法相比,v-html会替换掉节点中全部的内容,插值语法不会;且v-html可以识别html结构。
v-html有安全性问题:在网站上动态渲染任意html是非常危险的,容易导致XSS攻击(虽然得不到用户名和密码,但是可以偷走用户的cookie,冒充用户去找服务器);一定要在可以信任的网站上使用v-html,永远不要对用户提交的内容使用。v-clock
没有值,本质是一个特殊的属性,Vue实例创建完毕并接管容器后,会删掉该属性。配合CSS可以解决网速慢时页面展示出{{xxx}}的问题。[v-clock]{ display: none;} // 属性选择器,标签中有v-clock的时候不展示该标签的内容,当Vue接管后删掉v-clock,就可以展示出渲染完毕的内容。
v-once
所在的节点在初次动态渲染后,就视为静态内容了,之后数据的改变不会引起v-once所在结构的更新,可用于优化性能。v-pre
让Vue跳过所在节点的编译过程,可以利用它跳过没有使用指令语法、插值语法的节点,加快编译。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue2</title>
<link rel="shortcut icon" href="../imgs/kirlant.ico">
<script type="text/javascript" src="../vue/vue.js"></script>
<style>
#root{
width: 500px;
border: 2px solid #aaa;
padding: 10px 0;
text-align: left;
display: flex;
flex-wrap: wrap;
flex-direction: column;
align-content: center;
}
.title{
width: 80%;
font-weight: bold;
text-align: center;
border-bottom: solid 1.5px #aaa;
padding-bottom: 5px;
}
.gap{
padding: 5px 20px;
border-top: solid 1px #aaa;
}
</style>
</head>
<body>
<div id="root">
<div class="title">Welcome to {{name}}'s Test</div>
<div class="gap" v-text="str_text"></div>
<div class="gap" v-html="str_html"></div>
<div class="gap">
<p v-once>初始化n值为:{{n}}</p>
<p>当前n值为:{{n}}</p>
<button @click="++n;">++n</button>
</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
name:'yyt',
str_text:'<h4> v-text不支持结构解析 <h4/>',
str_html:'<h4> v-html支持结构解析 <h4/>',
n:7
}
});
</script>
</body>
</html>
10.2 自定义指令
所有指令的this
都是window
;
- 定义语法
局部指令:new Vue({ directives:{ 指令名:配置对象 } })
或new Vue({ directives:{ 指令名(){} } })
全局指令:Vue.directive( 指令名, 配置对象 )
或Vue.directive( 指令名, 回调函数 )
- 配置对象中常用的三个回调
bind
指令与元素成功绑定时调用;
inserted
指令所在的元素被成功插入页面时调用;
update
指令所在的模板结构被重新解析时调用。 - 备注
指令定义时不加v-
,使用时才加v-
;
指令名如果是多个单词,要使用kebab-case
命名方式,不能用camelCase命名。
<div class="gap">
当前的n值是:<span v-text="n"></span><br>
放大的n值是:<span v-big="n"></span><br>
<button @click="++n;">++n</button>
</div>
<div class="gap">
v-fbind: <input type="text" v-fbind:value="n">
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
n:0
},
directives:{
// 自定义的指令在指令和元素成功绑定时会被调用,指令所在的模板被重新解析时也会被调用。
big(element, binding){
element.innerText = binding.value * 10;
},
fbind:{
// 元素和指令绑定时调用
bind(element, binding){
element.value = binding.value;
},
inserted(element, binding){
element.focus();
},
update(element, binding){
element.value = binding.value;
element.focus();
}
}
}
});
</script>
11. Vue实例生命周期
生命周期 = 生命周期回调函数 = 生命周期函数 = 生命周期钩子
生命周期函数就是Vue在关键时刻帮我们调用的一些特殊名称的函数,它们的名字不可更改,但内容是根据需求编写的。
生命周期函数的this
指向的是vm
或 组件实例对象
。
vm的一生
将要创建
⟶
\longrightarrow
⟶ 调用beforeCreate()
创建完毕
⟶
\longrightarrow
⟶ 调用created()
将要挂载
⟶
\longrightarrow
⟶ 调用beforeMount()
挂载完毕
⟶
\longrightarrow
⟶ 调用mounted()
将要更新
⟶
\longrightarrow
⟶ 调用beforeupdate()
更新完毕
⟶
\longrightarrow
⟶ 调用updated()
将要销毁
⟶
\longrightarrow
⟶ 调用beforeDestroy()
销毁完毕
⟶
\longrightarrow
⟶ 调用destroyed()
注:
1). 在 进入 beforeUnmount() 后,也就是开始执行卸载流程后,能够访问数据,也能够调用方法,但是对数据进行的所有操作都不会进行更新了。
2). 销毁后借助Vue开发工具看不到任何信息,自定义事件失效,但原生DOM事件依然有效。