Vue核心
搭建Vue环境
先在此处附上Vue的安装教程地址:https://v2.cn.vuejs.org/v2/guide/installation.html
这里有两个版本,一个开发版本,一个生产版本
开发版本是在开发时使用的,当出现问题的时候会在控制台报警告
生产版本是在项目上线时使用的,不会有警告,而且体积更小
在这里,我使用的是开发版本,学习一般使用开发版
将开发版本下载后保存到本地,使用方法与jquery是一致的,使用来script引入
直接用script引入
创建一个文件夹,在里面创建一个html文件
然后直接引入vue文件即可
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="../vue.js"></script>
</head>
<body>
</body>
</html>
运行文件
消除开发环境提示
在console里查看
第一个是说下载vue的开发者工具来达到一个更好的开发者体验
第二个是说你正在运行开发环境,请你确信在生产环境不要这样做
两个小提示,但是不影响,后面再解决
如果成功引入了Vue,在console上写入Vue会输出其对应的函数
解决第一个提示的方法是下载一个vue的开发者工具即可
vue开发者工具的github链接如下:https://github.com/vuejs/devtools#vue-devtools
找到下面这个地方,点击进入谷歌商店下载
第二个提示需要在代码中对其进行配置
vue
<script>
Vue.config.productionTip = false // 阻止vue在启动时生成生产提示
</script>
模板语法
Vue模板语法有2大类:
1. 插值语法:
功能:用于解析标签体内容
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性
2. 指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件等)
举例:v-bind:hreft="xxx"或简写为:href="xxx",xxx同样要写js表达式。且可以直接读取到data中的所有属性
备注:Vue中有很多的指令,且形式都是:v-?????
插值语法
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="box">
{{test}}
</div>
<script src="../vue.js"></script>
<script>
Vue.config.productionTip = false;
var vm = new Vue({
el: '#box',
data: {
test: 'haha'
}
})
</script>
</body>
</html>
指令语法
v-bind
单向绑定数据
html
<body>
<div id="box">
<a v-bind:href="url">跳转到百度</a>
</div>
<script src="../vue.js"></script>
<script>
Vue.config.productionTip = false;
var vm = new Vue({
el: '#box',
data: {
url: 'http://www.baidu.com'
}
})
</script>
</body>
可以简写为`跳转到百度`
html
<body>
<div id="box">
<a :href="url">跳转到百度</a>
</div>
<script src="../vue.js"></script>
<script>
Vue.config.productionTip = false;
var vm = new Vue({
el: '#box',
data: {
url: 'http://www.baidu.com'
}
})
</script>
</body>
数据绑定
v-bind:单向数据绑定
v-model:双向数据绑定
Vue中有2种数据绑定的方式:
1. 单向绑定(v-bind):数据只能从data流向页面
2. 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
备注:
1. 双向绑定一般都应用在表单类元素上(如:input、select等)
2. v-model:value 可以简写为v-model,因为v-model默认收集的就是value值
1. new Vue时配置el属性
vue
new Vue({
// el: '#box', 第一种写法
})
2. 先创建Vue实例,随后再通过vm.$mount('#root')指定el的值
`vm.$mount('#box')`
1. 对象式
html
new Vue({
// el: '#box', 第一种写法
})
2. 函数式
html
// data的第二种写法:函数式
data() {
return {
name: 'zhangsan'
}
}
v-bind的简写方式::xxx=""
v-model的简写方式:v-model=""
html
单向数据绑定:<input type="text" :value="name"><br>
双向数据绑定:<input type="text" v-model="name">
单向数据绑定
下面这段代码是对其进行了一个单向数据绑定,可以在页面中进行测试
html
<body>
<div id="box">
单向数据绑定:<input type="text" :value="name">
</div>
<script src="../vue.js"></script>
<script>
Vue.config.productionTip = false;
var vm = new Vue({
el: '#box',
data: {
name: 'zhangsan'
}
})
</script>
</body>
之前在网上下载的Vue开发者工具此时就可以使用了
打开页面后单击F12,打开这个页面,在最后一栏找到Vue
在name:'zhangsan'的位置进行修改,单向数据绑定处的数据也会发生改变,而在输入框中修改,并不会改变name:'zhangsan'的内容
双向数据绑定
v-model:双向数据绑定
html
<body>
<div id="box">
单向数据绑定:<input type="text" :value="name"><br>
双向数据绑定:<input type="text" v-model:value="name">
</div>
<script src="../vue.js"></script>
<script>
Vue.config.productionTip = false;
var vm = new Vue({
el: '#box',
data: {
name: 'zhangsan'
}
})
</script>
</body>
被绑定了双向后,在测试工具中输入任意的数据,输入框中的内容都会发生改变,或者在输入框中输入内容,也会互相的改变
当输入框中的内容改变时,会带起一系列的连锁反应
注意:v-model元素只能应用在表单类元素上(输入类元素),在其他标签类元素上使用会报错
el与data的两种写法
对象式
html
<body>
<div id="box">
单向数据绑定:<input type="text" :value="name"><br>
双向数据绑定:<input type="text" v-model="name">
</div>
<script src="../vue.js"></script>
<script>
Vue.config.productionTip = false;
var vm = new Vue({
// el: '#box', 第一种写法
data: {
name: 'zhangsan'
}
})
vm.$mount('#box') // 第二种写法
</script>
</body>
相较之下,第二种更灵活,使用的时候两种都可以
html
<body>
<div id="box">
单向数据绑定:<input type="text" :value="name"><br>
双向数据绑定:<input type="text" v-model="name">
</div>
<script src="../vue.js"></script>
<script>
Vue.config.productionTip = false;
var vm = new Vue({
// el: '#box', 第一种写法
// data: {
// name: 'zhangsan'
// }
// data的第二种写法:函数式
data: function () {
return {
name: 'zhangsan'
}
}
})
vm.$mount('#box')
</script>
</body>
data可以简写为下面的方式
vue
// data的第二种写法:函数式
data() {
return {
name: 'zhangsan'
}
}
MVVM
M:模型(Model),对应data中的数据
V:视图(View), 模板
VM:视图模型(ViewModel),Vue实例对象
Data Bindings:数据绑定,将Model中的数据绑定到View中
DOM Listeners:页面模型监听器
总结:
数据代理
Object.defineProperty方法
`Object.defineProperty(添加属性的对象名,添加的属性名,{value:添加的值})`
html
<body>
<script src="../vue.js"></script>
<script>
let person = {
name: '张三',
sex: '男'
}
Object.defineProperty(person, 'age', {
value: 18
})
console.log(person)
</script>
</body>
为person对象添加了一个age的属性,值为18
这样添加与普通的添加方式有什么区别呢,这样添加的属性是不参与遍历的
正常情况下是参与遍历的
html
<body>
<script src="../vue.js"></script>
<script>
let person = {
name: '张三',
sex: '男',
age: 18
}
// Object.defineProperty(添加属性的对象名,添加的属性名,{value:添加的值})
// Object.defineProperty(person, 'age', {
// value: 18
// })
console.log(Object.keys(person))
</script>
</body>
但是使用该方法进行遍历就遍历不到了
html
<body>
<script src="../vue.js"></script>
<script>
let person = {
name: '张三',
sex: '男',
// age: 18
}
// Object.defineProperty(添加属性的对象名,添加的属性名,{value:添加的值})
Object.defineProperty(person, 'age', {
value: 18
})
console.log(Object.keys(person))
</script>
</body>
这种情况也叫不可枚举,如果需要遍历该怎么办呢
可以在代码中加入
`enumerable: true`:控制属性是否可以被枚举,默认是false
html
<body>
<script src="../vue.js"></script>
<script>
let person = {
name: '张三',
sex: '男',
}
// Object.defineProperty(添加属性的对象名,添加的属性名,{value:添加的值})
Object.defineProperty(person, 'age', {
value: 18,
enumerable: true
})
console.log(Object.keys(person))
</script>
</body>
再次运行,就可以遍历到了
如果你试图修改`Object.defineProperty`添加的方法,可以修改,但不会修改成功
如果需要被修改怎么办呢
在代码中加入`writable: true`,控制属性是否可以被修改,默认是false
html
<body>
<script src="../vue.js"></script>
<script>
let person = {
name: '张三',
sex: '男',
}
// Object.defineProperty(添加属性的对象名,添加的属性名,{value:添加的值})
Object.defineProperty(person, 'age', {
value: 18,
enumerable: true,
writable: true
})
console.log(Object.keys(person))
</script>
</body>
数据需要被删除,可以加入`configurable: true`,控制属性是否可以被删除,默认是false
getter和setter
下面的代码提供了`Object.defineProperty`的get和set示例
html
<body>
<script src="../vue.js"></script>
<script>
let person = {
name: '张三',
sex: '男',
}
let number = 19
// Object.defineProperty(添加属性的对象名,添加的属性名,{value:添加的值})
Object.defineProperty(person, 'age', {
// 当有人读取person的age属性时,get函数就会被调用,且返回number的值
get() {
return number
},
// 当有人修改person的age属性时,set函数就会被调用,且会收到修改的具体值value
set(value) {
number = value;
}
})
</script>
</body>
Vue中的数据代理
数据代理就是让_data中的数据在Vue身上也有一份,不管是getter还是setter都可以直接通过属性名来获取使用,而不是通过 _data.属性名来使用,这样会非常的冗余,在getter时,数据代理会getter到 _data的身上,而setter时,会setter到 _data的身上,从而达到一个连锁的效果,简化了开发
数据代理图示:
Vue先将data中的数据存放到_data中,然后再通过`Object.defineProperty`方法将数据放到vm身上,这样就起到一个数据代理的效果
html
<body>
<div id="box">
<h1>{{name}}</h1>
<h1>{{age}}</h1>
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#box',
data: {
name: 'zhangsan',
age: 66
}
})
</script>
</body>
以上面这段示例代码为例,Vue中的数据代理其实也是同理的
当有人访问页面中的name或age时,它会利用getter去访问data中的name或age
当有人修改页面中的name或age时,它会利用setter去修改data中的name或age
此时就不是直接访问,而是以一种代理的形式,通过访问Vue上的getter或setter方法来达到修改data上的数据
这里对数据代理的两种形式做一个验证
修改data中的属性后,查看其中vue中的data是否发生变化
发现此时这里的name确实是发生改变了,这说明数据改变后,依然是从data中来获取的
对setter进行一个验证
修改对应的属性,然后获取其data中的属性是否发生了改变
这里我们发现,修改了它的属性后,data中的属性也发生了改变,这说明setter方法是存在数据代理的,设置完的属性会放到data中
总结:
1. Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2. Vue中数据代理的好处:
更加方便的操作data中的数据
3. 基本原理
通过Object.defineProperty()把data对象中所有属性添加到vm上
通过每一个添加到vm上的属性,都指定一个getter/setter
在getter/setter内部去操作(读/写)data中对应的属性
事件处理
事件的基本使用
事件的基本使用:
1. 使用v-on:xxx 或 @xxx绑定事件,其中xxx是事件名
2. 事件的回调需要配置在methods对象中,最终会在vm上
3. methods配置的函数,不要用箭头函数,否则this就不是vm了
4. methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
5. @click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参
v-on:click
简写形式为:@click
当被点击时
html
<body>
<div id="box">
<button v-on:click="show">点我</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
show() {
alert("show")
}
},
})
</script>
</body>
如果需要接收参数,可以这样写
html
<body>
<div id="box">
<button v-on:click="show(66)">点我66</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
show(number) {
alert(number)
}
},
})
</script>
</body>
但是这样写event就无法使用了
可以通过在传参位置加入占位符`$event`的形式传递event参数
html
<body>
<div id="box">
<button v-on:click="show($event,66)">点我66</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
show(event, number) {
console.log(event)
alert(number)
}
},
})
</script>
</body>
事件修饰符
正常的运行下面这段代码,会在代码运行后依然将网页跳转到了百度
html
<body>
<div id="box">
<a href="http://www.baidu.com" @click="jump">点击跳走</a>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
jump() {
alert("跳走喽")
}
},
})
</script>
</body>
跳转是a标签的默认行为,我们有什么好的办法来阻止这个默认行为呢
可以通过`e.preventDefault`方法对这个默认行为进行阻止
html
methods: {
jump(e) {
e.preventDefault();
alert("跳走喽")
}
},
也可以通过Vue中的事件修饰符
1. prevent:阻止默认事件(常用)
2. stop:阻止事件冒泡(常用)
3. once:事件只触发一次(常用)
4. capture:使用事件的捕获模式
5. self:只有event.target是当前操作的元素时才触发事件
6. passive:事件的默认行为立即执行,无需等待事件回调执行完毕
演示一下常用的三种,其余不做演示
prevent:
效果和`e.preventDefault();`是一样的
html
<body>
<div id="box">
<a href="http://www.baidu.com" @click.prevent="jump">点击跳走</a>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
jump(e) {
// e.preventDefault();
alert("跳走喽")
}
},
})
</script>
</body>
stop:
冒泡是从当没有返回值true或false之类时,会从里向外(向它的父元素执行其父元素的方法直到结束)
冒泡情况演示
html
<body>
<div id="box">
<div @click="clickOnButton">
<button @click="clickOnButton">点我老铁666</button>
</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
clickOnButton() {
alert("冒泡效果")
}
},
})
</script>
</body>
冒泡解决方案:
html
<body>
<div id="box">
<div @click="clickOnButton">
<button @click="clickOnButton">点我老铁666</button>
</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
clickOnButton(e) {
e.stopPropagation();
alert("冒泡效果")
}
},
})
</script>
</body>
冒泡解决方案Vue版:
html
<body>
<div id="box">
<div @click="clickOnButton">
<button @click.stop="clickOnButton">点我老铁666</button>
</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
clickOnButton(e) {
// e.stopPropagation();
alert("冒泡效果")
}
},
})
</script>
</body>
once:
事件只触发一次,字面意思,挺好理解的
html
<body>
<div id="box">
<button @click.once="clickOnButton">点我老铁666</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
},
methods: {
clickOnButton(e) {
// e.stopPropagation();
alert("once")
}
},
})
</script>
</body>
键盘事件
@keyup:当键盘弹起时
@keydown:当键盘按下时
下面这段代码是当键盘弹起时,如果按下的是回车就打印内容在控制台上
html
<body>
<div id="box">
<input type="text" placeholder="按下回车提示输入" @keyup="show">
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#box',
data: {
},
methods: {
show(e) {
if (e.keyCode !== 13) return;
console.log(e.target.value)
}
},
})
</script>
</body>
这里的判断keyCode也可以置换为vue中的别名
html
<body>
<div id="box">
<input type="text" placeholder="按下回车提示输入" @keyup.enter="show">
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#box',
data: {
},
methods: {
show(e) {
// if (e.keyCode !== 13) return;
console.log(e.target.value)
}
},
})
</script>
</body>
vue常见按键别名
回车 => enter
删除 => delete(捕获"删除"和"退格"键)
退出 => esc
空格 => space
换行 => tab
上 => up
下 => down
左 => left
右 => right
Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
短横线命名是什么意思呢?
就是说,如果某个按键未提供别名,可以使用原始的key去绑定,比如CapsLock,如果想要使用的话,需要转为caps-lock,记得要在两个单词之间加入短横线-
示例代码
html
<body>
<div id="box">
<input type="text" placeholder="按下回车提示输入" @keyup.caps-lock="show">
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#box',
data: {
},
methods: {
show(e) {
// if (e.keyCode !== 13) return;
console.log(e.target.value)
}
},
})
</script>
</body>
特殊按键tab:按了之后会离开焦点,这种不适合在keyup上使用,因为在键盘弹起后触发时,焦点已经离开了,方法可能就无法触发了(必须配合keydown使用)
系统修饰键(用法特殊):ctrl、alt、shift、meta(win)
1. 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
2. 配合keydown使用:正常触发事件
也可以使用keyCode去指定具体的按键(不推荐)
``
Vue.config.keyCodes.自定义键名 = 键码,可以定制按键别名
`Vue.config.keyCodes.huiche = 13`
html
<body>
<div id="box">
<input type="text" placeholder="按下回车提示输入" @keyup.huiche="show">
</div>
<script src="../vue.js"></script>
<script>
Vue.config.keyCodes.huiche = 13
new Vue({
el: '#box',
data: {
},
methods: {
show(e) {
// if (e.keyCode !== 13) return;
console.log(e.target.value)
}
},
})
</script>
</body>
事件总结
计算属性
姓名案例
插值语法实现
html
<body>
<div id="box">
姓: <input type="text" v-model=firstName><br>
名: <input type="text" v-model=lastName><br>
姓名: <span>{{firstName.slice(0,3)}}-{{lastName}}</span>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
firstName: '张',
lastName: '三'
}
})
</script>
</body>
如果我们想为名称加一些新的效果,但是会有很多,这样就会很麻烦,如果我们把它整理成一个methods,看起来就不会特别的冗余了
html
<body>
<div id="box">
姓: <input type="text" v-model=firstName><br>
名: <input type="text" v-model=lastName><br>
姓名: <span>{{name()}}</span>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
firstName: '张',
lastName: '三'
},
methods: {
name() {
return this.firstName + "-" + this.lastName
}
},
})
</script>
</body>
计算属性
1. 定义:要用的属性不存在,要通过已有属性(vue中的才叫属性)计算得来
2. 原理:底层借助了Object.defineproperty方法提供的getter和setter
3. get函数什么时候执行?
4. 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
5. 备注:
1. 计算属性最终会出现在vm上,直接读取使用即可
2. 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变,否则无效
使用计算属性编写之前的姓名案例
这里的get和`Object.defineProperty`是一样的
当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
get什么时候调用
html
<body>
<div id="box">
姓: <input type="text" v-model=firstName><br>
名: <input type="text" v-model=lastName><br>
姓名: <span>{{fullName}}</span>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName: {
get() {
console.log(this)
return this.firstName + "-" + this.lastName
}
}
}
})
</script>
</body>
且get是有缓存的,而methods是没有缓存的,性能上computed更好一些
有get肯定就有set,和get的道理其实也是一样的
html
<body>
<div id="box">
姓: <input type="text" v-model=firstName><br>
名: <input type="text" v-model=lastName><br>
姓名: <span>{{fullName}}</span>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName: {
// set方法的调用时机是当fullName被改变时调用
// 而fullName是依靠firstName和lastName来改变的
// 也就是当输入框中的两个东西被改变后,get就调用了
get() {
console.log(this)
return this.firstName + "-" + this.lastName
},
set(value) {
// set会传递一个参数
// 这个参数就是值了
console.log(value)
// 得到了值,需要通过中间的-来进行获取姓和名
const arr = value.split('-');
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
})
</script>
</body>
计算属性简写
简写的形式只有在只读不改的时候才能使用
html
<body>
<div id="box">
姓: <input type="text" v-model=firstName><br>
名: <input type="text" v-model=lastName><br>
姓名: <span>{{fullName}}</span>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName() {
// 简写
return this.firstName + "-" + this.lastName;
}
}
})
</script>
</body>
监视属性
天气案例
先做一个天气的小案例,点击按钮后变换天气
这里使用了计算属性来进行操作
html
<body>
<div id="box">
<h1>今天天气很{{info}}</h1>
<button @click="change">改变天气</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? '炎热' : '寒冷'
}
}, methods: {
change() {
this.isHot = !this.isHot
}
},
})
</script>
</body>
但是在这里有一个小坑
如果我们将代码中的这一行改变了
改变为`
今天天气很一般
`此时页面上就不会用到isHot和info了,如果我们此时点击一下按钮,页面是肯定不会发生变化的,但是你打开开发者工具一看
开发者工具中却是true和炎热,我们去控制台上看一下
控制台上就发生了变化了
这是为什么呢,原因其实是vue的开发者工具认为你页面没有使用到该数据,就没有为你更新了,这个是官方的一个bug,不影响
这里写的代码其实很多,只是为了实现一个切换功能,那还有什么办法能更简单吗
html
<body>
<div id="box">
<h1>今天天气很{{info}}</h1>
<button @click="isHot = !isHot">改变天气</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? '炎热' : '寒冷'
}
}, methods: {
},
})
</script>
</body>
一些简单的代码其实可以直接使用@click来写
如果你不仅想做这个操作,还想做别的操作呢,有两种方式
1. 直接在methods中写
2. `@click="isHot = !isHot";你想做的操作`
比如:
html
<body>
<div id="box">
<h1>今天天气很{{info}}</h1>
<button @click="isHot = !isHot;x++">改变天气{{x}}</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
isHot: true,
x: 0
},
computed: {
info() {
return this.isHot ? '炎热' : '寒冷'
}
}, methods: {
},
})
</script>
</body>
监视属性watch
1. 当被监视的属性变化时,回调函数自动调用,进行相关操作
2. 监视的属性必须存在,才能进行监视
3. 监视的两种写法:
1. new Vue时传入watch配置
2. 通过vm.$watch监视
html
<body>
<div id="box">
<h1>今天天气很{{info}}</h1>
<button @click="isHot = !isHot">改变天气</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '寒冷'
}
}, watch: {
info: {
immediate: true, // 初始化时让handler调用一下
// handler什么时候调用,当isHot发生改变时
handler(newValue, oldValue) {
console.log("新的值为", newValue, "旧的值为", oldValue)
}
}
}
})
</script>
</body>
监视属性是watch,是一个对象的形式,里面可以放置多个对象,每个对象里面有配置属性和handler等
handler中有两个参数可以接收,第一个是newValue,第二个是oldValue
除了上面这种监视属性的写法,还有另一种监视属性的写法
html
<script>
const vm = new Vue({
el: '#box',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '寒冷'
}
},
// watch: {
// info: {
// immediate: true, // 初始化时让handler调用一下
// // handler什么时候调用,当isHot发生改变时
// handler(newValue, oldValue) {
// console.log("新的值为", newValue, "旧的值为", oldValue)
// }
// }
// }
})
vm.$watch('info',
{
immediate: true, // 初始化时让handler调用一下
// handler什么时候调用,当isHot发生改变时
handler(newValue, oldValue) {
console.log("新的值为", newValue, "旧的值为", oldValue)
}
}
)
</script>
深度监视
1. Vue中的watch默认不监测对象内部值的改变(一层)
2. 配置`deep:true`可以监测对象内部值的改变(多层)
备注:
监视多级结构中某个属性的变化
当多级结构中的a发生变化时,可以获取得到它
html
<body>
<div id="box">
<h1>a的值是:{{numbers.a}}</h1>
<button @click="numbers.a++">点我让a++</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
numbers: {
a: 1,
b: 2
}
},
watch: {
'numbers.a'() {
console.log(this.numbers.a)
}
}
})
</script>
</body>
检测整体结构变化
html
<body>
<div id="box">
<h1>a的值是:{{numbers.a}}</h1>
<button @click="numbers.a++">点我让a++</button>
<button @click="numbers.b++">点我让b++</button>
<!-- 当numbers整体发生改变时,才能检测到 -->
<button @click="numbers = {a:66,b:99}">点我让numbers改变</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
numbers: {
a: 1,
b: 2
}
},
watch: {
'numbers'() {
console.log(this.numbers)
}
}
})
</script>
</body>
检测整体结构的任意一个值的变化
为watch中的属性配置deep即可检测值的变化
html
<body>
<div id="box">
<h1>a的值是:{{numbers.a}}</h1>
<button @click="numbers.a++">点我让a++</button>
<button @click="numbers.b++">点我让b++</button>
<!-- 当numbers整体发生改变时,才能检测到 -->
<button @click="numbers = {a:66,b:99}">点我让numbers改变</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
numbers: {
a: 1,
b: 2
}
},
watch: {
numbers: {
deep: true,
handler() {
console.log(this.numbers)
}
}
}
})
</script>
</body>
监视的简写形式
监视的简写形式只有在仅handler的情况下才可以使用
html
<body>
<div id="box">
<h1>numbers的值是:{{numbers}}</h1>
<button @click="numbers++">点我让numbers++</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
numbers: 1
},
watch: {
// 正常写法
// numbers: {
// deep: true,
// handler() {
// console.log(this.numbers)
// }
// }
// 简写
numbers(newValue, oldValue) {
console.log(newValue, oldValue)
}
}
})
</script>
</body>
但是监视有两种写法,它还有一种vm.$watch的写法,也可以简写
html
<body>
<div id="box">
<h1>numbers的值是:{{numbers}}</h1>
<button @click="numbers++">点我让numbers++</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
numbers: 1
},
// watch: {
// // 正常写法
// // numbers: {
// // deep: true,
// // handler() {
// // console.log(this.numbers)
// // }
// // }
// // 简写
// numbers(newValue, oldValue) {
// console.log(newValue, oldValue)
// }
// }
})
vm.$watch('numbers', function (newValue, oldValue) {
console.log(newValue, oldValue)
})
</script>
</body>
watch对比computed
computed和watch之间的区别:
1. computed能完成的功能,watch都可以完成
2. watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作(定时器)
两个重要的小原则:
1. 被Vue所管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象
2. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等),最好写成箭头函数,因为在JavaScript中,箭头函数并不会创建自己的this上下文,而是继承其所在代码块的this。因此,在Vue组件的方法中,如果我们使用了箭头函数,那么this才会指向Vue实例。
watch写法的姓名案例
html
<body>
<div id="box">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
<br><span>姓名:{{fullName}}</span>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
firstName: '张',
lastName: '三',
fullName: '张三'
},
watch: {
firstName(newValue) {
this.fullName = newValue + this.lastName;
},
lastName(newValue) {
this.fullName = this.firstName + newValue;
}
}
})
</script>
</body>
computed写法的姓名案例
html
<body>
<div id="box">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
<br><span>姓名:{{fullName}}</span>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
firstName: '张',
lastName: '三',
},
computed: {
fullName() {
return this.firstName + this.lastName;
}
}
})
</script>
</body>
绑定样式
绑定class样式
字符串写法,适用于:样式的类名不确定,需要动态指定
可以将样式修改为指定的内容
html
<body>
<div id="box">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">test</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
mood: 'normal'
},
methods: {
changeMood() {
this.mood = 'happy'
}
},
})
</script>
</body>
也可以通过布尔值进行判断之类的情况
当isActive为true时,active才会作为类生效
html
<body>
<div id="box">
<div :class="{active:isActive}">test</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
isActive: false
}
})
</script>
</body>
绑定计算属性作为class的判断
html
<body>
<div id="box">
<div :class="classObject">test</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
isActive: true,
error: null
},
computed: {
classObject() {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
})
</script>
</body>
这段代码的释义是使用了一个计算属性
计算属性的含义是classObject,当满足isActive为true并且error不为空的条件时,返回active;当error存在并且error的类型为fatal时返回text-danger
数组语法
我们可以把一个数组传给v-bind:class,来应用一个class列表的情况
html
<body>
<div id="box">
<div :class="[active,active2]">test</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
active: 'activeClass',
active2: 'isYes'
},
})
</script>
</body>
最终它会渲染为`class='activeClass isYes'`
如果想根据条件来切换数组语法中的内容,也是可以的
html
<div :class="[isActive ? active : '',active2]">test</div>
可以通过三元表达式来对其进行修改
如果觉得三元表达式不够友好,可以采取对象语法的方式
html
<div :class="[{active:isActive},active2]">test</div>
绑定内联样式
对象语法
html
<body>
<div id="box">
<!-- 使用对象语法 -->
<div :style="{fontSize:size}">hahahha</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
size: '36px'
},
})
</script>
</body>
或者将样式绑定到一个对象中,直接使用对象
html
<div :style="styleData">hahahha</div>
js
data: {
styleData: {
backgroundColor: 'red',
fontSize: '30px'
}
}
数组语法
将多个样式对象绑定到一个数组上
html
<div :style="[styleData,a,b,c]">hahahha</div>
多重值
从vue2.3.0开始可以为style绑定中的property提供一个包含多个值的数组,常用于提供多个带前缀的值,例如:
html
<div :style="{display:['-webkit-box,'-ms-flexbox','flex']}"></div>
`'-webkit-box,'-ms-flexbox','flex'`是不同浏览器支持的类型前缀,这样写只会渲染数组中最后一个被浏览器支持的值,如果浏览器支持不带浏览器前缀的flexbox,那么就只会渲染`display:flex`
条件渲染
v-if、v-else
`v-if` 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true值的时候被渲染。
`v-else`的效果是当if不生效时,else生效
`v-else` 元素必须紧跟在带 `v-if` 或者 `v-else-if` 的元素的后面,否则它将不会被识别。
html
<body>
<div id="box">
<div v-if="flag">66</div>
<div v-else="flag">77</div>
<button @click="onClick">点击后改变</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
flag: false
},
methods: {
onClick() {
this.flag = !this.flag
}
},
})
</script>
</body>
在``元素上使用v-if渲染分组
因为`v-if`是一个指令,所以必须将它添加到一个元素上,但是如果想切换多个元素呢,此时可以把一个``元素当作不可见的包裹元素,并在上面使用`v-if`。最终的渲染结果不包含``元素
html
<body>
<div id="box">
<template v-if="flag">
<!-- v-if的效果可以让template下的所有内容消失 -->
<h1>title</h1>
<span>1</span>
<span>2</span>
</template>
<button @click="onClick">点击切换</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
flag: false
},
methods: {
onClick() {
this.flag = !this.flag
}
},
})
</script>
</body>
v-else-if
2.1.0 新增
类似于 `v-else`,`v-else-if` 也必须紧跟在带 `v-if` 或者 `v-else-if` 的元素之后。
html
<body>
<div id="box">
<div v-if="score >=90">
成绩在90分及以上
</div>
<div v-else-if="score >=70">
成绩在70分及以上
</div>
<div v-else-if="score >=50">
成绩在50分及以上
</div>
<div v-else-if="score >=0">
成绩在0分及以上
</div>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
score: 66
},
})
</script>
</body>
用key管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
html
<body>
<div id="box">
<template v-if="loginName === 'username'">
<label>username</label>
<input placeholder="Enter your username">
</template>
<template v-else="loginName === 'email'">
<label>email</label>
<input placeholder="Enter your email">
</template>
<button @click="toggle">Toggle login type</button>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
loginType: true
},
computed: {
loginName() {
return this.loginType === true ? 'username' : 'email'
}
}, methods: {
toggle() {
this.loginType = !this.loginType;
}
}
})
</script>
</body>
那么在上面的代码中切换 `loginType` 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,`` 不会被替换掉——仅仅是替换了它的 `placeholder`。
这样会起到复用input输入框的作用
如果我们想让这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 `key` attribute 即可:
html
<div id="box">
<template v-if="loginName === 'username'">
<label>username</label>
<input placeholder="Enter your username" key="username">
</template>
<template v-else="loginName === 'email'">
<label>email</label>
<input placeholder="Enter your email" key="email">
</template>
<button @click="toggle">Toggle login type</button>
</div>
`
v-show
根据条件展示元素的选项是 `v-show` 指令。
用法是类似的
html
<body>
<div id="box">
<div v-show="showNice">
展示
</div>
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#box',
data: {
showNice: true
}
})
</script>
</body>
不同的是带有 `v-show` 的元素始终会被渲染并保留在 DOM 中。`v-show` 只是简单地切换元素的 CSS property `display`,元素是存在页面上的,只是被display所隐藏了
注意,`v-show` 不支持 `` 元素,也不支持 `v-else`。
`v-if` vs `v-show`
`v-if` 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
`v-if` 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,`v-show` 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,`v-if` 有更高的切换开销,而 `v-show` 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 `v-show` 较好;如果在运行时条件很少改变,则使用 `v-if` 较好。
总结:
列表渲染
用`v-for`把一个数组对应为一组元素
我们可以用 `v-for` 指令基于一个数组来渲染一个列表。`v-for` 指令需要使用 `item in items` 形式的特殊语法,其中 `items` 是源数据数组,而 `item` 则是被迭代的数组元素的别名。
html
<body>
<div id="box">
<ul>
<li v-for="item in items">{{item.message}}</li>
</ul>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
</script>
</body>
在 `v-for` 块中,我们可以访问所有父作用域的 property。`v-for` 还支持一个可选的第二个参数,即当前项的索引。
html
<body>
<div id="box">
<ul>
<li v-for="(item,index) in items">索引{{index}}<br>{{parentMessage}}<br>{{item.message}}</li>
</ul>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
parentMessage: 'Parent',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
</script>
</body>
也可以用 `of` 替代 `in` 作为分隔符,因为它更接近 JavaScript 迭代器的语法:
html
<li v-for="(item,index) of items">索引{{index}}<br>{{parentMessage}}<br>
v-for使用对象
可以用 `v-for` 来遍历一个对象的 property。
html
<div id="box">
<ul>
<li v-for="val in obj">
{{val}}
</li>
</ul>
</div>
js
new Vue({
el: '#box',
data: {
obj: {
name: 'zhang',
age: 66
}
}
})
结果如下
可以提供第二个参数为property名称(也就是键名)
键名是对象里的名称,值是名称所对应的值
html
<div id="box">
<ul>
<li v-for="(val,name) in obj">
{{val}}:{{name}}
</li>
</ul>
</div>
结果如下
还可以用第三个参数作为索引
html
<div id="box">
<ul>
<li v-for="(val,name,index) in obj">
{{val}}:{{name}}:{{index}}
</li>
</ul>
</div>
数组更新检测
Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
你可以打开控制台,然后对数组尝试调用变更方法。
在 `v-for` 里使用范围值
`v-for` 也可以接受整数。在这种情况下,它会把模板重复对应次数。
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
生命周期
1. 又名:生命周期回调函数、生命周期函数、生命周期钩子
2. 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
3. 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
4. 生命周期函数中的this指向是vm或组件实例对象
通过外部定时器实现无限透明
html
<body>
<div id="box">
<h1 :style="{opacity}">Vue欢迎您</h1>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
opacity: 1
}
})
// 通过外部的定时器实现无限透明的效果
setInterval(() => {
if (vm.opacity <= 0) {
vm.opacity = 1
}
vm.opacity -= 0.01
}, 16);
</script>
</body>
Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
通过生命周期实现无限透明
html
<body>
<div id="box">
<h1 :style="{opacity}">Vue欢迎您</h1>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
data: {
opacity: 1
},
// 第一次将元素放入页面调用mounted叫做挂载
mounted() {
console.log("666")
setInterval(() => {
if (this.opacity <= 0) {
this.opacity = 1
}
this.opacity -= 0.01
}, 16);
},
})
</script>
</body>
挂载流程
生命周期流程图
刚开始创建Vue时,初始化生命周期、事件,数据代理未开始,说明_data还未创建,来到beforeCreate上,在beforeCreate上无法通过vue的对象来访问到data中的数据和methods中的方法,在代码中进行检验,看看是否无法访问
html
<body>
<div id="box">
<h2>n:{{n}}</h2>
<button @click="add">点我让N++</button>
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#box',
data: {
n: 0
},
methods: {
add() {
this.n++
}
},
beforeCreate() {
console.log('beforeCreate', this.n)
},
})
</script>
</body>
此时这里的n并没有出现,因为还是beforeCreate时
接着继续进行初始化,将数据监测、数据代理,也就是_data初始化了,来到created上,就可以通过Vue访问到数据和方法了,在代码中进行检验,看看效果
html
<body>
<div id="box">
<h2>n:{{n}}</h2>
<button @click="add">点我让N++</button>
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#box',
data: {
n: 0
},
methods: {
add() {
this.n++
}
},
created() {
console.log('created', this.n)
},
})
</script>
</body>
此时在created上就有n的值了
created结束后,接着就是判断是否存在el的值,如果存在el,就查看是否有template的选项,如果没有,就根据el所在的位置去解析Html的值,该阶段只是解析模板,但是并没有显示解析好的内容,只能显示初始的html页面,vue中的效果还没有渲染
在beforeMount上就会呈现未经Vue编译的DOM结构,这里测试一下,看看效果
html
<body>
<div id="box">
<h2>n:{{n}}</h2>
<button @click="add">点我让N++</button>
</div>
<script src="../vue.js"></script>
<script>
new Vue({
el: '#box',
data: {
n: 0
},
methods: {
add() {
this.n++
}
},
beforeMount() {
console.log('beforeMount')
debugger;
},
})
</script>
</body>
在这个阶段,如果你断点后,修改页面中的DOM属性,最终都不会有效,因为渲染解析后,会全部改为Vue解析后的内容
此时我们发现,页面已经解析完成了,但并没有把数据填充上去
接着将内存中的虚拟的DOM转为真实的,插入到页面中
此时,挂载完毕,进入mounted
此时:
页面中呈现的是经过Vue编译的DOM,此时对DOM的操作均有效
回到最开始的el判断,它还有另一条线,如果el不存在时,查找vm.$mount(el),这个相当于主动来指定vm的元素,指定为xxx作为el,与其是一个道理
关闭了el后,在命令行下输入该内容也是一个道理,也会主动的进行解析,如果两个都没满足,vue就不会进行工作了,也就是不会进行解析了,接着就是查看是否存在template这个属性了,如果存在,就进行解析
template中的内容需要被包裹,它需要一个根节点的支持
html
<body>
<div id="box">
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
template: '<div><h2>n:{{n}}</h2><button @click="add">点我让N++</button></div>',
data: {
n: 0
},
methods: {
add() {
this.n++
}
},
beforeMount() {
console.log('beforeMount')
},
})
</script>
</body>
更新流程
接着进入更新流程后,先判断数据是否改变了,数据更新前,也就是beforeUpdate时
此时:
数据是新的,页面是旧的,此时页面和数据尚未保持一致
html
<body>
<div id="box">
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#box',
template: '<div><h2>n:{{n}}</h2><button @click="add">点我让N++</button></div>',
data: {
n: 0
},
methods: {
add() {
this.n++
}
},
beforeUpdate() {
console.log(this.n)
debugger
},
})
</script>
</body>
接着根据新数据,生成新的虚拟DOM,随后与旧的虚拟DOM进行比较,最终完成页面更新,即:完成了Model到View的更新
此时:进入到了updated,也就是更新后,数据是新的,页面也是新的,此时的页面与数据是保持同步的
销毁流程
当vm.$destroy方法被调用时,就调用销毁流程的方法
先进入销毁前,也就是beforeDestory
此时:vm中所有的:data、methods、指令等等,都处于可用状态,马上要执行销毁过程,一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
最后destoyed,销毁
生命周期总结
常用的生命周期钩子:
1. mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
2. beforeDestory:清除定时器、解绑自定义事件、取消订阅消息等【收尾操作】
关于销毁Vue实例
1. 销毁后借助Vue开发者工具看不到任何信息
2. 销毁后自定义事件会失效,但原生DOM事件依然有效
3. 一般不会再beforeDestory操作数据,因为即便操作数据,也不会再触发更新流程了
Vue组件化编程
Vue是组件化的,这里给出一个Vue组件的示例:
js
Vue.component('box', {
data: function () {
return {
count: 0
}
},
template: `<button @click="count++">{{count}}++</button>`
})
这是一个Vue的组件,叫做box,参数是一个count,模板是template中的内容
html
<div id="box2">
<box></box>
</div>
js
new Vue({
el: '#box2'
})
在id为box2下的div调用这个组件并进行使用,使用方式就是一种标签的形式
组件的复用
你可以将组件进行任意次数的复用
html
<div id="box2">
<box></box>
<box></box>
<box></box>
<box></box>
</div>
每个组件都会各自独立维护它的 `count`。因为你每用一次组件,就会有一个它的新实例被创建。
data必须是一个函数
一般情况下,data都是以这种形式来展示的
js
data: {
count: 0
}
取而代之的是,一个组件的`data`选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
js
data:function(){
return{
count:0
}
}
如果 Vue 没有这条规则,点击一个按钮就可能会影响到其它所有实例,可能会发展成全局的一种值的方式,每个组件都不会是独立的形态
组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织:
这里的组件树,最顶层,是最大的一个壳,下面的三个,分别对应着上,下左,和下右三个盒子,下左的盒子内有两个盒子对应着组件树中的两个盒子,而下右也是同理
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 `Vue.component` 全局注册的:
js
Vue.component('my-component-name', {
// ... options ...
})
全局注册的组件可以用在其被注册之后的任何 (通过 `new Vue`) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
通过Prop向子组件传递数据
Prop是你可以在组件上注册的一些自定义attribute。当一个值传递给一个prop attribute的时候,或者说当一个值传递给在组件上注册的attribute时,它就变成了组件实例的一个property,为了传递一个标题,我们可以用一个props选项将其包含在该组件可接收的prop列表中
js
Vue.component('blog-title', {
props: ['title'],
template: `<h3>{{title}}</h3>`
})
这里写了一个叫做blog-title的组件,里面是一个模板语法,内置了props的title,当title存在时显示标签中的内容
非单文件组件
一个文件中包含有n个组件
类似于下图所示
组件学习
Vue中使用组件的三大步骤:
1. 定义组件(创建组件)
2. 注册组件
3. 使用组件(写组件标签)
如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别:
区别如下:
1. el不要写,为什么? 最终所有组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
2. data必须写成函数,为什么? 避免组件被复用时,数据存在引用关系
备注:在options中可以使用template配置组件结构
如何注册组件?
1. 局部注册:靠new Vue的时候传入components选项
2. 全局注册:靠Vue.component('组件名',组件)
编写组件标签:<组件名>组件名>
创建组件
通过Vue.extend来进行创建
这里的data要作为函数来进行编写,这样可以使得组件独立,函数每一次都会返回一个全新的内容
js
// 创建school组件
const school = Vue.extend({
// el: '#box', 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于谁
template: `
<div>
<h2>学校名称{{schoolName}}</h2>
<h2>学校地址{{address}}</h2>
</div>
`,
data() {
return {
schoolName: 'NB',
address: '北京'
}
}
})
// 创建student组件
const student = Vue.extend({
template: `
<div>
<h2>学生名称{{studentName}}</h2>
<h2>学生年龄{{age}}</h2></div>
`,
// el: '#box', 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于谁
data() {
return {
studentName: '张三',
age: 19
}
}
})
注册组件
js
// 注册组件
new Vue({
el: '#box',
// 局部注册
components: {
school,
student
}
})
使用组件
html
<!-- 使用组件标签 -->
<school></school>
<hr>
<!-- 使用组件标签 -->
<student></student>
局部注册和全局注册
js
// 注册组件
new Vue({
el: '#box',
// 局部注册
components: {
school,
student
}
})
这样的注册只能算局部注册
有时候在一个页面里面可以用到多个组件,一个组件中需要使用到另一个组件中定义的组件,此时就需要全局注册,如果不进行全局注册,只能将之前的局部注册在需要使用到该组件的大组件中再次注册(components)
js
Vue.component('组件名称', 组件变量名)
组件的几个注意点
1. 关于组件名:
1. 组件名尽可能回避HTML中已有的元素名称,例如:h1,H2都不行
2. 可以使用name配置项指定组件在开发者工具中呈现的名字
js
// 定义组件
const school = {
name: 'test',
template: `
<div>
<h2>{{name}}</h2>
</div>
`,
data() {
return {
name: '张三'
}
}
}
2. 关于组件标签
3. 一个简写的方式:
const school = Vue.extend(options) 可简写为:const school = options
js
// 定义组件
const school = {
template: `
<div>
<h2>{{name}}</h2>
</div>
`,
data() {
return {
name: '张三'
}
}
}
组件的嵌套
将一个组件嵌套到另一个组件中,就叫做组件的嵌套
下面是一个组件嵌套的示例,student是被嵌套的组件,school嵌套了student组件的内容,最后在#box的根元素处调用了school组件,又是一个嵌套
html
<body>
<div id="box">
<school></school>
</div>
<script src="../vue.js"></script>
<script>
const student = Vue.extend({
template: `
<div>
<h2>学生名称{{name}}</h2>
</div>
`,
data() {
return {
name: '张三'
}
}
})
const school = Vue.extend({
template: `
<div>
<h2>学校名称{{name}}</h2>
<student></student>
</div>
`,
data() {
return {
name: 'WD'
}
},
components: {
student
}
})
new Vue({
el: '#box',
components: {
school
}
})
</script>
</body>
VueComponent
1. school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
2. 我们只需要写
3. 特别注意:每次调用Vue.extend,返回的是一个全新的VueComponent!
4. 关于this指向:
1. 组件配置中:
2. new Vue(options)配置中:
5. VueComponent的实例对象,以后简称vc(也可以称之为:组件实例对象)
Vue的实例对象,以后简称vm
分析Vue和VueComponent的关系
1. VueComponent.prototype. ____proto____ === Vue.prototype
2. 为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法
在看内置关系之前,需要先知道原型对象的概念
下面的这段代码讲述了一件事情,就是关于隐式原型属性和显示原型属性是否一致,这俩是一致的,都归类在原型对象上
js
<body>
<script src="../vue.js"></script>
<script>
// 定义一个构造函数
function Demo() {
this.a = 1
this.b = 2
}
// 创建一个Demo的实例对象
const d = new Demo()
console.log(Demo.prototype) // 显示原型属性
console.log(d.__proto__)// 隐式原型属性
// 比较显示原型属性和隐式原型属性是否相同
console.log(Demo.prototype === d.__proto__)
// 通过显示原型属性操作原型对象,追加一个x属性,值为99
Demo.prototype.x = 99
// 通过隐式原型对象查看x
console.log(d.__proto__.x)
</script>
</body>
证明了原型对象内的东西是一致的
Vue对象是一个显示原型链,当Vue存在一个实例对象,实例对象会通过隐式原型链,链到Vue的原型对象上,而Vue的原型对象会通过隐式原型链,链到它的缔造者的原型对象身上,Vue的缔造者就是Object,所以Vue的原型对象链接到了Object的原型对象身上
而VueComponent在编写组件标签后,就会创建一个VueComponent的实例对象,暂且称为vc,它通过隐式原型链会链接到缔造者的身上,也就是VueComponent的原型对象身上,而VueComponent的原型对象也有一条隐式的原型链,通过这条原型链,它会链到Vue的原型对象身上,也就是说,VueComponent可以访问到Vue身上的属性和方法,因为VueComponent的原型对象关联了Vue的原型对象
单文件组件
一个文件只包含1个组件,这种单文件组件的形式更加常用
如何创建一个单文件组件,文件名.vue,就创建成功了
里面的代码结构如下:
vue
<template>
<!-- 组件的结构 -->
<div></div>
</template>
<script>
// 组件交互相关的代码(数据、方法等等)
export default {
name: "JsV1",
data() {
return {};
},
mounted() {},
methods: {},
};
</script>
<style lang="scss" scoped>
// 组件的样式
</style>
但是发现写了之后没有代码高亮提示,我们需要安装一个插件来添加vue文件的代码高亮提示
接着编写组件,组件的基本格式为:
vue
<template>
<!-- 组件的结构 -->
<div class="demo">
<h2>姓名{{ name }}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
// 组件交互相关的代码(数据、方法等等)
// 通常使用export default暴露,这样导入也很方便
export default {
// 这里的名称通常和文件名一致,要符合首字母
name: "V1",
data() {
return {
name: "张三",
};
},
mounted() {},
methods: {
showName() {
alert(this.name);
},
},
};
</script>
<style lang="scss" scoped>
// 组件的样式
.demo {
background: #ff6900;
}
</style>
如果想使用组件,可以创建一个App.vue来进行使用,这里的App可以换成任意的名字,是一个作为汇总的总组件
vue
<template>
<div>
<School></School>
</div>
</template>
<script>
import School from "./v1.vue";
export default {
name: "App",
components: {
School: School,
},
};
</script>
<style lang="scss" scoped>
</style>
一般情况下,都不会new Vue,因为.vue后缀的文件都是组件,组件都不会new Vue
所以我们需要创建一个js文件,在vue中一般叫做main.js
js
import App from './App.vue'
new Vue({
el:'#box',
components:{
App
}
})
将APP的组件注册后,我们并没有编写容器来容纳我们的程序在哪展示,所以需要创建一个index.html来展示
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="box">
<App></App>
</div>
<!-- 需要引入vue.js和已经导入App组件的main.js文件 -->
<script src="../vue.js"></script>
<script src="main.js"></script>
</body>
</html>
如果不想在index.html中进行App标签的编写,可以在main.js中编写对应template的代码
使用vue脚手架
Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
vue脚手架官方文档:https://cli.vuejs.org/zh/
以下命令都在命令行使用
1. 全局安装@vue/cli
`npm install -g @vue/cli`
2. 安装完成后,关闭命令行,再重新打开
`vue`
当出现vue相关的提示命令时,说明安装成功了
3. 切换到你要创建项目的目录,然后使用命令创建项目(创建的项目不能包含大写字母)
`vue create xxxx`
4. 启动项目
`npm run serve`
提示:
分析脚手架结构
assets文件夹一般用来存放静态资源
components一般存放组件
脚手架文件结构
main.js
js
/*
该文件是整个项目的入口文件
*/
// 引入vue框架
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭vue的生产提示
Vue.config.productionTip = false
// 创建vue的实例对象--->vm
new Vue({
// 下面这行代码后面解释,功能:完成了将App组件放入容器中
render: h => h(App),
}).$mount('#app')
index.html
html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签的图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 配置网页的标题:在package.json的第二行中配置的name -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 如果浏览器不支持js时,noscript中的元素就会被渲染 -->
<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>
运行测试一下看看效果,如果报错
`Component name “Student” should always be multi-word vue/multi-word-component-names`
原因是组件中的驼峰命名不规范,要么改成多个单词的驼峰命名,要么就在vue.config.js中修改内容为
js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false
})
render函数
关于不同版本的Vue:
1. vue.js与vue.runtime.xxx.js的区别:
1. vue.js是完整版的Vue,包含:核心功能+模板解释器
2. vue.runtime.xxx.js是运行版的Vue只包含:核心功能:没有模板解释器
2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
修改默认配置
shell
vue inspect > output.js
将vue脚手架的默认配置整理成一个.js文件
该指令需要在vue脚手架文件所在处运行
被红色框框选的文件夹或文件的名称不可以修改
如果需要修改默认文件,可以在vue.config.js下进行修改,这是一个可选的文件,也就是说没有也可以,如果没有的话,使用的就是vue默认的配置项,如果需要修改,可以在与packagejson同级的目录下进行添加
有些配置项是不可改的,如果需要修改对应的配置项,可以在vue官网进行查阅:https://cli.vuejs.org/zh/config/
现在来进行测试一下,看看修改了配置项以后能否生效
在vue.config.js中进行修改
js
module.exports = defineConfig({
pages:{
index:{
// 入口
entry:'src/peiqi.js'
}
}
})
然后修改一下main.js的文件名为peiqi.js
重新运行`npm run serve`
关闭语法检查
js
module.exports = defineConfig({
lintOnSave:false, // 关闭语法检查
})
ref属性
1. 被用来给元素或子组件注册引用信息(id替代者)
2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3. 使用方式
.....
或ref属性可以帮助我们获取对应的DOM元素
下面是一个利用ref属性获取对应DOM元素的方法
可以忽略对应的组件,里面其实就写了一个获取DOM元素的点击事件,因为methods方法对应的是vc,在vc中就存在着ref的属性,通过ref就可以获得对应的DOM元素了
为哪一个标签添加ref,到时候就可以通过vc来进行获取
vue
<template>
<div id="app">
<h1 ref="title">学习Vue</h1>
<button @click="showDOM">点我获取上方的DOM元素</button>
<Student></Student>
</div>
</template>
<script>
import Student from "./components/Student.vue";
export default {
name: "App",
components: {
Student: Student,
},
methods: {
showDOM() {
console.log(this.$refs.title);
},
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
如果我给一个组件设置了ref属性,那么我能拿到什么呢?
那么我们就可以获得对应的组件实例对象
vue
showDOM() {
console.log(this.$refs.title); // 真实DOM元素
console.log(this.$refs.school); // school组件的实例对象(vc)
console.log(document.querySelector("#school")); // school组件的真实DOM元素
},
props配置
功能:让组件接收到外部传过来的数据
1. 传递数据:`
2. 接收数据:
`props:['name']`
`props:{`
`name:String`
`}`
props:{
name:{
type:String, // 类型
required:true, //必要性
default:'老王' // 默认值
}
}
3.
txt
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
有些时候其他地方需要该组件,但是参数不一定与默认的参数一致,需要修改,那该怎么办呢,这时候就要用到props属性了,它可以在组件上添加属性
props配置有三种方式,例如我们有一个Student组件
vue
<template>
<div>
<div>学生名称:{{ studentName }}</div>
<div>学生年龄:{{ age }}</div>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
studentName: "张三",
age: 19,
};
},
mounted() {},
methods: {},
};
</script>
<style lang="scss" scoped>
</style>
这里面有一个studentName和一个age,我们可能需要为其的学生名称和年龄来做修改
在里面加入props,并删除data中的studentName和age属性
vue
<script>
export default {
name: "Student",
data() {
return {};
},
props: ["studentName", "age"],
mounted() {},
methods: {},
};
</script>
在调用该组件的位置赋值
vue
<template>
<div id="app">
<Student studentName="李四" age="20"></Student>
</div>
</template>
但是这里还有个坑
如果我想为这里李四的年龄+1,该如何操作呢,如果直接在学生的组件上对年龄加一,就会造成以下这个情况
`学生名称:李四`
`学生年龄:201`
原因是传递的这个参数是个字符串,字符串+1还是字符串1这样的一种情况
可以通过强制类型转化,将其转为数字类型
vue
<template>
<div>
<div>学生名称:{{ studentName }}</div>
<div>学生年龄:{{ age * 1 + 1 }}</div>
</div>
</template>
但这不是最好的解决方法
在age上面加入:,也就是v-model,单向绑定,这样会将:age后面的内容修改成一个表达式,js表达式就可以进行计算了,这样的话,20+1=21
vue
<template>
<div id="app">
<Student studentName="李四" :age="20"></Student>
</div>
</template>
有时候粗心大意容易写错代码,可以为props添加限制
vue
<script>
export default {
name: "Student",
data() {
return {};
},
props: {
studentName: String,
age: Number,
},
mounted() {},
methods: {},
};
</script>
添加了限制后,当类型不正确时,就会出现异常
props的高级写法
接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
vue
props: {
studentName: {
type: String, // studentName的类型是字符串
required: true, // studentName必须传递的一个参数
},
age: {
type: Number, // age的类型是数字类型
default: 99, // 可传可不传,不传就给个默认值
},
},
mixin混入
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步定义混合,例如:
{
data(){....},
methods:{.....}
.........
}
第二步使用混入,例如:
1. 全局混入:Vue.mixin(xxx)
2. 局部混入:mixins:['xxx']
先将代码修改为原样
School组件
vue
<template>
<div>
<h1 @click="showName">学校名称:{{ schoolName }}</h1>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
schoolName: "NB",
};
},
mounted() {},
methods: {
showSchool() {
alert(this.schoolName);
},
},
};
</script>
<style lang="scss" scoped>
</style>
Student组件
vue
<template>
<div>
<h1 @click="showName">学生名称:{{ studentName }}</h1>
<h1>学生年龄:{{ age }}</h1>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
studentName: "张三",
age: 19,
};
},
mounted() {},
methods: {
showName() {
alert(this.studentName);
},
},
};
</script>
<style lang="scss" scoped>
</style>
App组件
vue
<template>
<div id="app">
<School />
<hr />
<Student />
</div>
</template>
<script>
import School from "./components/School.vue";
import Student from "./components/Student.vue";
export default {
name: "App",
components: {
School,
Student,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
这里的Student和School组件都有一个类似的方法,把这俩东西整合成一个东西,然后在俩组件中引用一下,就是混入了
在src目录下新建一个mixin.js,文件名不固定
将Student和School组件中的methods删除,并在mixin文件中添加如下内容
js
export const mixin = {
methods: {
showName() {
alert(this.name);
},
}
}
应用mixin对应的混入内容
School
vue
<script>
import { mixin } from "../mixin";
export default {
name: "Student",
data() {
return {
name: "NB",
};
},
mixins: [mixin],
mounted() {},
};
</script>
Student
vue
<script>
import { mixin } from "../mixin";
export default {
name: "Student",
data() {
return {
name: "张三",
age: 19,
};
},
mixins: [mixin],
mounted() {},
};
</script>
如果想要全局混入,可以在main.js中进行配置
先导入对应的mixin,然后在Vue中使用对应的mixin
js
import {mixin} from './mixin'
Vue.mixin(mixin)
插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据,也就是说,在main.js中里引入插件后,可以为它添加参数,例如:Vue.use(plugins,xxx,xxx,xxx)
定义插件:
js
export default {
install(Vue,xxx,xxx){
console.log("@@install",Vue)
}
}
使用插件:Vue.use(xxx)
先修改之前的内容为
App
vue
<template>
<div id="app">
<School />
<hr />
<Student />
</div>
</template>
<script>
import School from "./components/School.vue";
import Student from "./components/Student.vue";
export default {
name: "App",
components: {
School,
Student,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
main.js
js
/*
该文件是整个项目的入口文件
*/
// 引入vue框架
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭vue的生产提示
Vue.config.productionTip = false
// 创建vue的实例对象--->vm
new Vue({
// 下面这行代码后面解释,完成了将App组件放入容器中
render: h => h(App),
}).$mount('#app')
School
vue
<template>
<div>
<h1>学校名称:{{ name }}</h1>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
name: "NB",
};
},
mounted() {},
};
</script>
<style lang="scss" scoped>
</style>
Student
vue
<template>
<div>
<h1>学生名称:{{ name }}</h1>
<h1>学生年龄:{{ age }}</h1>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
name: "张三",
age: 19,
};
},
mounted() {},
};
</script>
<style lang="scss" scoped>
</style>
在src下新建一个文件,作为自己定义的插件
plugins
js
export default {
install(){
console.log("@@install")
}
}
接着在main.js中引入对应的插件
js
// 引入plugins插件
import plugins from './plugins'
// 在Vue中使用对应的插件
Vue.use(plugins)
在install方法中可以添加一个形参,这个形参是一个Vue的构造器
js
export default {
install(Vue){
console.log("@@install",Vue)
}
}
scoped样式
作用:让样式在局部生效,防止冲突
写法:`