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 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
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.21 非单文件组件
基本使用
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(‘组件名’,组件)
几个注意点:
关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。
一个简写方式:
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
school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!(这个VueComponent可不是实例对象)
关于this指向:
组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。
一个重要的内置关系
- 一个重要的内置关系:VueComponent.prototype._proto_=== Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。
1.22 单文件组件
单文件组件就是将一个组件的代码写在 .vue 这种格式的文件中,webpack 会将 .vue 文件解析成 html,css,js这些形式。