文章目录
- 1. Vue简介
- 2. Vue官网使用指南
- 3. 初识Vue
- 3.1 搭建Vue开发环境
- 3.2 HelloWorld案例
- 3.3 el与data的两种写法
- 3.4 MVVM模型
- 3.5 模板语法
- 4. 数据绑定
- 4.1 v-bind单向数据绑定
- 4.2 v-model双向数据绑定
- 5. 事件处理
- 5.1 v-on绑定事件
- 5.2 事件修饰符
- 5.3 键盘事件
- 6. 计算属性
- 6.1 姓名案例
- 6.2 computed计算属性
- 7. 监视属性
- 7.1 天气案例
- 7.2 watch监视属性
- 8. 绑定样式
- 8.1 绑定class样式
- 8.2 绑定style样式
- 9. 条件渲染
- 9.1 v-show
- 9.2 v-if、v-else与v-else-if
- 10. 列表渲染
- 10.1 v-for渲染列表数据
- 10.2 key的作用与原理
- 10.3 列表过滤
- 10.4 列表排序
- 11. 收集表单数据
- 12. 内置指令
- 12.1 v-text 解析普通文本
- 12.2 v-html 解析html标签
- 12.3 v-cloak 隐藏模板
- 12.4 v-once 仅渲染一次
- 12.5 v-pre 跳过元素编译
- 13. 自定义指令
- 13.1 函数式和对象式
- 13.2 局部指令和全局指令
- 14. Vue的生命周期
- 14.1 生命周期概念
- 14.2 生命周期演示
- 15. Vue的组件化编程
- 15.1 模块与组件
- 15.2 非单文件组件
- 15.2.1 组件的基本使用
- 15.2.2 组件的注意事项
- 15.2.3 组件的嵌套
- 15.2.4 VueComponent
- 15.2.5 一个重要的内置关系
- 15.3 单文件组件
- 15.3.1 单文件组件的组成
- 15.3.2 单文件组件的示例
1. Vue简介
Vue的概念:Vue是一套用于构建用户界面的渐进式JavaScript框架。
渐进式:表示Vue可以自底向上逐层的应用,从简单应用逐渐递进到复杂应用。
Vue的特点:
- 采用组件化模式,提高代码复用率,且让代码更好维护。
- 声明式编码,让编码人员无需直接操作DOM,提高开发效率。
- 使用虚拟DOM和Diff算法,尽量复用DOM节点。
学习Vue之前要掌握的js基础知识:
- ES6语法规范
- ES6模块化
- 包管理器
- 原型和原型链
- 数组常用方法
- axios
- promise
2. Vue官网使用指南
Vue官网:
- Vue3文档:https://cn.vuejs.org/
- Vue2文档:https://v2.cn.vuejs.org/
以Vue2文档为例,对该文档顶部的导航栏做详细的介绍。
1.学习:其中最重要的是教程和API,贯穿vue学习的始终。
- 教程:入门vue的教程
- API:vue的属性、方法和指令等
- 风格指南:官方推荐的vue代码风格指南
- 示例:官方展示的一些vue案例
- Cookbook:展示一些vue案例,教你如何优化vue代码
2.生态系统:其中工具和插件很重要,用于搭建Vue工程。
3.资源列表:其中Awesome Vue、浏览和Vue相关的包,这两个包含了官方整理的一些优秀的vue周边库。
3. 初识Vue
3.1 搭建Vue开发环境
1.首先需要下载Vue
进入Vue官网的导航列表:学习 -> 教程 -> 安装,也可点击如下地址直接跳转:https://v2.cn.vuejs.org/v2/guide/installation.html
有开发和生产两种版本,这里我们使用开发版本,其中包含了完整的警告和调试模式,适用于学习阶段。
- 开发环境版本:安装包名称为
vue.js
,包含了有帮助的命令行警告,体积较大。 - 生产环境版本:安装包名称为
vue.min.js
,优化了尺寸和速度,体积较小。
2.新建一个js文件夹,将下载好的vue.js
放入js文件夹中,然后使用 <script>
标签引入 vue.js
,就可以使用Vue了
目录结构如下:
index.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>
<!-- 引入Vue -->
<script src="../js/vue.js"></script>
</head>
<body></body>
</html>
打开页面后,再打开控制台,发现有两个提示:
3.消除开发环境的提示
添加vue开发者工具,消除第一个提示,步骤如下:打开Chrome扩展程序 -> 打开开发者模式 ->拖入vue_dev_tools.crx
文件到浏览器并松开,点击添加程序即可
script
标签中写入以下vue配置,消除第二个提示:
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
3.2 HelloWorld案例
<!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>
<!-- 引入Vue -->
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="app">
<h1>{{message}}</h1>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
// 创建Vue实例
new Vue({
// 配置
el: "#app", // el用于指定当前Vue实例为哪个容器服务
data: { // data中用于存储数据,数据供el所指定的容器去使用
message: "Hello World!"
}
});
</script>
</body>
</html>
代码解释:
1.el
表示元素(element),值通常为css选择器,此处#app
为id选择器,对应<div id="app">
,此时当前vue实例挂载到了div
容器上,为该容器服务。
2.data
表示数据,数据供挂载的容器去使用,而容器中使用了模板语法{{message}}
,引用了vue实例中的data数据。
3.vue实例中的message
属性值Hello World!
替换了{{message}}
,因此<h1>{{message}}</h1>
最终解析成了<h1>Hello World!</h1>
运行结果如下:
注:
1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
2.容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
3.容器里的代码被称为Vue模板
4.Vue实例和容器是一一对应的
5.真实开发中只有一个Vue实例,并且会配合着组件一起使用
6.{{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新
3.3 el与data的两种写法
1.el
与data
的第一种写法是对象式写法,就是上述HelloWorld案例中的写法,即通过配置对象属性和属性值。
// 创建Vue实例
new Vue({
// 配置
el: "#app", // 将Vue实例挂载到 id=app的容器上
data: { // data中用于存储数据,数据供挂载的容器去使用
message: "Hello World!"
}
});
2.el
与data
的第二种写法是函数式写法
// 创建Vue实例
const vm = new Vue({
// data的函数式写法,组件中必须使用该写法
data() {
return {
message: "Hello World!"
};
}
});
vm.$mount("#app"); // 等价于 el: "#app"
注意:vue实例中的函数不能使用箭头函数的写法,原本函数中的
this
指向的是vue实例对象,使用箭头函数后,函数中的this
指向的就是window
对象。
3.4 MVVM模型
Vue采用了MVVM架构模型,其对应关系如下:
M
:模型(Model) ,对应 data 中的数据V
:视图(View) ,对应模板VM
:视图模型(ViewModel) , 对应 Vue 实例对象
3.5 模板语法
模板语法:Vue实例挂载的容器里中的代码被称为模板,模板语法也就是这个容器中代码的语法规范,模板语法分为两大类(插值语法和指令语法)。
插值语法:
- 功能:用于解析标签体内容
- 写法:
{{xxx}}
,xxx是js表达式,且可以直接读取到data中的所有属性
指令语法:
- 功能:用于解析标签(包括标签属性、标签体内容、绑定事件等)
- 写法:
v-xxx
,如v-bind
、v-model
、v-on
等
<!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>
<!-- 引入Vue -->
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 插值语法,{{js表达式}} -->
<h3>{{message.toUpperCase()}}</h3>
<!-- 指令语法,v-xxx -->
<a v-bind:href="url">百度链接</a>
</div>
<script>
new Vue({
el: "#app",
data: {
message: "hello world",
url: "https://www.baidu.com/"
}
});
</script>
</body>
</html>
运行结果:
4. 数据绑定
Vue中有两种数据绑定的方式:
- 单向绑定(v-bind):数据只能从data流向页面
- 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
4.1 v-bind单向数据绑定
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>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 普通写法 -->
单向数据绑定:<input type="text" v-bind:value="name" /> <br />
<!-- 简写 -->
单向数据绑定:<input type="text" :value="name" />
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
name: "hello"
}
});
</script>
</body>
</html>
验证数据能从data流向页面:打开控制台,输入 vm.name="hello123"
并回车,可以发现页面中的数据也显示成了hello123
验证页面的数据无法流向data:在文本框中输入hello1234
,随后在控制台输入vm.name
,得到的结果依然是hello123
,说明vue实例中的data数据未发生改变
4.2 v-model双向数据绑定
v-model
:用于双向数据绑定,双向绑定只能应用在表单类元素(输入类元素)上,如:input、select等
注:
v-model:value
可以简写为v-model
,因为v-model默认收集的就是value值。
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 普通写法 -->
双向数据绑定:<input type="text" v-model:value="name" /> <br />
<!-- 简写 -->
双向数据绑定:<input type="text" v-model="name" />
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
name: "hello"
}
});
</script>
</body>
</html>
验证数据能从data流向页面:打开控制台,输入 vm.name="hello123"
并回车,可以发现页面中的数据也显示成了hello123
验证页面的数据也能流向data:在文本框中输入hello1234
,随后在控制台输入vm.name
,得到的结果是hello1234
,说明vue实例中的data数据随页面数据的改变而改变
5. 事件处理
5.1 v-on绑定事件
事件的基本使用:
- 使用
v-on:xxx="yyy"
指令用于绑定事件,其中v-on:
可以简写为@
,xxx
是事件名,yyy
是回调函数名(也可以是简单的语句,如isShow=!isShow
) methods
对象用于配置vm
的方法,事件的回调函数需要配置在methods
对象中methods
中配置的函数,this
指向的是vm
或组件实例对象,且配置的函数不能使用箭头函数,否则this
指向的是window
对象@click="demo"
和@click="demo($event)"
效果一致,但后者可以传参
1.绑定事件,且回调函数不传参
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 普通写法 -->
<button v-on:click="showInfo">按钮1</button>
<!-- 简写 -->
<button @click="showInfo">按钮2</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
// 配置方法
methods: {
// 事件回调函数
showInfo() {
alert("Hello World!");
}
}
});
</script>
</body>
</html>
2.绑定事件,且给回调函数传参
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<button @click="showInfo">按钮1</button>
<!-- $event为占位符,表示此时第一个参数为事件对象 -->
<button @click="showInfo1($event,'Jack')">按钮2</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
// 配置方法
methods: {
// 不传参时,函数的第一个参数代表事件对象
showInfo(e) {
console.log(e.target); // 触发事件的对象
console.log(e.type); // 事件的类型
console.log(this); // this指向vm
},
// 传参时,根据$event占位符的位置确定哪个参数代表事件对象
showInfo1(e, name) {
console.log(e.target);
console.log(e.type);
console.log(`Hello ${name}`);
}
}
});
</script>
</body>
</html>
运行结果:
5.2 事件修饰符
Vue中的事件修饰符:
prevent
:阻止默认事件(常用)stop
:阻止事件冒泡(常用)once
:事件只触发一次(常用)capture
:使用事件的捕获模式self
:只有event.target是当前操作的元素时才触发事件passive
:事件的默认行为立即执行,无需等待事件回调执行完毕
注:修饰符可以连续写,如
@click.prevent.stop="showInfo"
1.prevent
:阻止事件的默认行为(常用)
同js事件对象中的
e.preventDefault()
方法,用于阻止事件的默认行为
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 阻止链接跳转 -->
<a href="https://www.baidu.com/" @click.prevent="showInfo">百度链接</a>
<!-- 阻止表单提交 -->
<form action="https://www.baidu.com/">
<input type="submit" value="提交" @click.prevent="showInfo" />
</form>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
methods: {
showInfo() {
console.log("已阻止事件的默认行为");
}
}
});
</script>
</body>
</html>
运行结果:点击百度链接和表单提交按钮,都不发生页面跳转,因为跳转的默认行为被阻止了
2.stop
:阻止事件冒泡(常用)
同js事件对象中的
e.stopPropagation()
方法,用于阻止事件冒泡
<!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="../js/vue.js"></script>
<style>
.father {
position: relative;
width: 200px;
height: 200px;
background-color: #666;
margin: 30px;
}
.son {
position: absolute;
width: 100px;
height: 100px;
background-color: orange;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<div id="app" class="father" @click="showInfo1">
<!-- 阻止事件冒泡 -->
<div class="son" @click.stop="showInfo2"></div>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
methods: {
showInfo1() {
alert("father");
},
showInfo2() {
alert("son");
}
}
});
</script>
</body>
</html>
运行结果:点击子元素 ,只弹出警示框“son”,没有后续弹出“father”
(释义:点击子元素son以后,阻止冒泡,因此没有冒泡到父元素,父元素的事件行为不执行)
3.once
:事件只触发一次(常用)
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 此按钮只在第一次点击时,触发事件 -->
<button @click.once="showInfo">点击按钮</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
methods: {
showInfo() {
alert("Hello World!");
}
}
});
</script>
</body>
</html>
运行结果:第一次点击按钮时,弹出警示框,后续再点击无法弹出
4.capture
:使用事件的捕获模式
注:js中
addEventListener(type, listener, useCapture)
:useCapture
为false
或者忽略时,事件处于冒泡阶段;为true
时,事件处于捕获阶段
<!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="../js/vue.js"></script>
<style>
.father {
position: relative;
width: 200px;
height: 200px;
background-color: #666;
margin: 30px;
}
.son {
position: absolute;
width: 100px;
height: 100px;
background-color: orange;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<!-- 绑定事件到父元素father,为捕获阶段 -->
<div id="app" class="father" @click.capture="showInfo1">
<div class="son" @click="showInfo2"></div>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
methods: {
showInfo1() {
alert("father");
},
showInfo2() {
alert("son");
}
}
});
</script>
</body>
</html>
运行结果:点击橘色的子元素div,先弹出警示框 “father”,后弹出警示框 “son”
(释义:捕获从上往下传递,点击子元素son以后,会先找到父元素并执行它的事件,再往下找到子元素后,执行子元素的事件)
5.self
:只有event.target
是当前操作的元素时才触发事件
注:通过这种方式,也能阻止事件冒泡
<!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="../js/vue.js"></script>
<style>
.father {
position: relative;
width: 200px;
height: 200px;
background-color: #666;
margin: 30px;
}
.son {
position: absolute;
width: 100px;
height: 100px;
background-color: orange;
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<!-- 只有当e.target是当前操作的元素时才触发事件,即只有点击该父元素才触发父元素的事件 -->
<div id="app" class="father" @click.self="showInfo1">
<!-- <div id="app" class="father" @click="showInfo1"> -->
<div class="son" @click="showInfo2"></div>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
methods: {
showInfo1() {
alert("father");
},
showInfo2() {
alert("son");
}
}
});
</script>
</body>
</html>
运行结果:只有点击灰色的父元素div时,才弹出警示框 “father”
5.3 键盘事件
Vue中常用的按键别名:
- 回车 => enter
- 删除 => delete (捕获“删除”和“退格”键)
- 退出 => esc
- 空格 => space
- 换行 => tab (特殊,必须配合keydown去使用)
- 上 => up
- 下 => down
- 左 => left
- 右 => right
注:
1.Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名,如caps-lock)
2.可以使用keyCode去指定具体的按键(不推荐),如e.keyCode===13
代表回车键
3.Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名,如Vue.config.keyCodes.huiche=13
,此时别名huiche
代表回车键
4.系统修饰键(用法特殊):ctrl、alt、shift、meta,
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- 配合keydown使用:正常触发事件。
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 按下回车键松开时触发事件 -->
<input type="text" placeholder="输入完成后请按回车" @keyup.enter="showInfo" />
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
methods: {
showInfo(e) {
alert(e.target.value);
}
}
});
</script>
</body>
</html>
运行结果:输入“123456”,再按下回车后松开
6. 计算属性
6.1 姓名案例
要求:一个input框输入“姓”,另一个input框输入“名”,生成“姓-名”
解题方法有三种:
- 使用插值语法
{{}}
实现 - 使用
methods
方法实现 - 使用
computed
计算属性实现(推荐)
1.使用插值语法{{}}
实现姓名案例,通过直接在模板里进行字符串拼接
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
姓:<input type="text" v-model="firstName" /> <br />
名:<input type="text" v-model="lastName" /> <br />
全名:<span>{{firstName}}-{{lastName}}</span>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
firstName: "张",
lastName: "三"
}
});
</script>
</body>
</html>
运行结果如下:在两个输入框中分别输入“姓”和“名”,得到全名为“姓-名”
2.使用methods
方法实现姓名案例,通过调用方法,将方法的返回值渲染到模板中
<!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="../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>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
firstName: "张",
lastName: "三"
},
methods: {
fullName() {
return this.firstName + "-" + this.lastName;
}
}
});
</script>
</body>
</html>
3.使用computed
计算属性实现,通过计算原有的属性,得到新的属性,并将其渲染到模板中
<!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="../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>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
firstName: "张",
lastName: "三"
},
computed: {
fullName: {
get() {
return this.firstName + "-" + this.lastName;
}
}
}
});
</script>
</body>
</html>
6.2 computed计算属性
计算属性:
- 定义:要用的属性不存在,要通过已有属性计算得来,计算属性最终会出现在
vm
上,直接读取使用即可 - 原理:底层借助了
Objcet.defineproperty
方法提供的getter
和setter
- 优势:与
methods
实现相比,内部有缓存机制(复用),效率更高,调试方便 - 计算属性中的
get
和set
函数:- 使用计算属性时,初次读取会执行一次
get
函数,当依赖的数据发生改变时也会再次调用get
函数 - 修改计算属性时,会调用
set
函数
- 使用计算属性时,初次读取会执行一次
- 当计算属性中,只有
get
函数时,可以简写计算属性
1.计算属性中的get
和set
函数的使用
<!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="../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>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
firstName: "张",
lastName: "三"
},
computed: {
fullName: {
// 使用fullName时,返回全名
get() {
console.log("get被调用了");
return this.firstName + "-" + this.lastName;
},
// 修改fullName时,修改firstName和lastName属性
set(value) {
console.log("set被调用了");
arr = value.split("-");
this.firstName = arr[0];
this.lastName = arr[1];
}
}
}
});
</script>
</body>
</html>
运行结果:
由于模板中使用了fullName属性,先调用了一次get,然后在输入修改名后,fullName依赖的lastName属性发生了改变,再次调用了get;
控制台中输入vm.fullName='张-三丰'
,fullName属性被修改,调用了set,又因为set函数修改了fullName所依赖的firstName 和lastName,所以再次触发调用了get
2.简写计算属性
当计算属性中,只有get
函数时,代码可以进行如下简写:
// 普通写法
computed: {
fullName: {
get() {
return this.firstName + "-" + this.lastName;
}
}
}
// 简写
computed: {
fullName() {
return this.firstName + "-" + this.lastName;
}
}
7. 监视属性
7.1 天气案例
要求:点击按钮时,天气在炎热和凉爽之间切换,初始值默认为炎热。
实现如下:
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>今天天气很{{info}}</h1>
<button @click="changeWeather">点击切换天气</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
isHot: true // 默认天气为炎热
},
computed: {
info() {
return this.isHot ? "炎热" : "凉爽";
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot; // 每次调用方法时,isHot取反
}
}
});
</script>
</body>
</html>
注:由于methods中的
changeWeather
方法逻辑简单,因此也可以将methods去掉,并把@click="changeWeather"
改为@click="isHot = !isHot"
,这样就简化了代码。
运行结果:点击按钮,天气在炎热和凉爽之间切换
7.2 watch监视属性
监视属性watch
:
- 监视的属性必须存在,才能进行监视
- 当被监视的属性变化时,
handler
回调函数自动调用 - 监视属性有两种写法,一是通过
watch
属性配置,二是通过vm.$watch()
方法 - 深度监视属性:
- 当被监视的属性有多层结构时,默认不监测对象内部值的改变(一层)
- 通过配置
deep:true
可以监测对象内部值改变(多层)
- 当监视属性中,只有
handler
函数时,可以监视计算属性
1.监视属性的基本用法(通过watch
属性配置)
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h1>今天天气很{{info}}</h1>
<button @click="isHot = !isHot">点击切换天气</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
isHot: true // 默认天气为炎热
},
computed: {
info() {
return this.isHot ? "炎热" : "凉爽";
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot; // 每次调用方法时,isHot取反
}
},
watch: {
// 当isHot属性发生变化时,会调用handler函数
isHot: {
immediate: true, //初始化时会调用一下handler函数
handler(newValue, oldValue) {
console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
}
}
}
});
</script>
</body>
</html>
点击按钮,运行结果如下:
2.可以通过vm.$watch()
方法,进行属性监视
// watch属性配置的写法
watch: {
isHot: {
immediate: true,
handler(newValue, oldValue) {
console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
}
}
}
// vm.$watch()的写法
vm.$watch("isHot", {
immediate: true,
handler(newValue, oldValue) {
console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
}
});
3.deep:true
用于深度监视属性
- 深度监视单个属性:监视的属性写为
"numbers.a"
,表示监视numbers
属性中的a
属性 - 深度监视所有属性: 监视的属性写为
numbers
,再配置deep:true
,表示监视numbers
属性中的所有属性
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h3>a的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让a+1</button>
<h3>b的值是:{{numbers.b}}</h3>
<button @click="numbers.b++">点我让b+1</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
// numbers属性有多层结构
numbers: {
a: 1,
b: 1,
c: {
d: {
e: 100
}
}
}
},
watch: {
//监视多级结构中某个属性的变化
"numbers.a": {
handler() {
console.log("a发生了改变");
}
},
//监视多级结构中所有属性的变化
numbers: {
deep: true,
handler() {
console.log("numbers发生了改变");
}
}
}
});
</script>
</body>
</html>
分别点击上下两个按钮,使a、b值增加,运行结果如下:
4.简写监视属性
当监视属性中,只有handler
函数时,代码可以进行如下简写:
// 普通写法
watch: {
isHot: {
handler(newValue, oldValue) {
console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
}
}
}
// watch属性配置简写
watch: {
isHot(newValue, oldValue) {
console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
}
}
// vm.$watch()方法简写
vm.$watch("isHot", function (newValue, oldValue) {
console.log(`isHot被修改,原来的值是${oldValue},现在是${newValue}`);
});
5.计算属性与监视属性的区别
computed
和watch
之间的区别:
computed
能完成的功能,watch
都可以完成watch
能完成的功能,computed
不一定能完成,例如:watch
可以进行异步操作computed
的写法更加的简洁,因此能用computed
完成的功能,不要用watch
完成
注:Vue中有关函数的两个重要的小原则:
1.所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
8. 绑定样式
8.1 绑定class样式
绑定class
样式有三种方式,分别适用于不同的场景:写法为:class="xxx"
- 字符串写法:适用于样式的类名不确定,需要动态指定
- 对象写法:适用于要绑定的样式个数确定、名字也确定,但要动态决定用不用
- 数组写法:适用于要绑定的样式个数不确定、名字也不确定
1.绑定class
样式,字符串写法:适用于样式的类名不确定,需要动态指定
<!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="../js/vue.js"></script>
<style>
/* basic为固定样式,happy和sad为需要动态绑定的样式 */
.basic {
width: 200px;
height: 100px;
border: 1px solid black;
}
/* 快乐情绪 */
.happy {
border: 4px solid red;
background-color: orangered;
background: linear-gradient(30deg, yellow, pink, orange, yellow);
}
/* 悲伤情绪 */
.sad {
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
</style>
</head>
<body>
<div id="app">
<!-- 字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood"></div>
<br />
<button @click="changeMood">点击切换情绪</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
mood: "happy" // 属性值为字符串
},
methods: {
// 改变情绪
changeMood() {
if (this.mood == "happy") {
this.mood = "sad";
} else {
this.mood = "happy";
}
}
}
});
</script>
</body>
</html>
运行结果:点击按钮,盒子样式发生改变(盒子的样式是动态的)
2.绑定class
样式,对象写法:适用于要绑定的样式个数确定、名字也确定,但要动态决定用不用
动态切换单个 class
样式:
<!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="../js/vue.js"></script>
<style>
.basic {
width: 200px;
height: 100px;
border: 1px solid black;
}
.a1 {
background-color: yellowgreen;
}
</style>
</head>
<body>
<div id="app">
<!-- 对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="{a1:isActive}">归海一刀</div>
<button @click="isActive=!isActive">添加/删除样式</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
isActive: true
}
});
</script>
</body>
</html>
运行结果:点击按钮,选择添加或者删除样式.
动态切换多个 class
样式:
<!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="../js/vue.js"></script>
<style>
.basic {
width: 200px;
height: 100px;
border: 1px solid black;
}
.a1 {
background-color: yellowgreen;
}
.a2 {
font-size: 30px;
text-shadow: 2px 2px 10px red;
}
.a3 {
border-radius: 20px;
}
</style>
</head>
<body>
<div id="app">
<!-- 对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">归海一刀</div>
<button @click="classObj.a1=!classObj.a1">添加/删除样式a1</button>
<button @click="classObj.a2=!classObj.a2">添加/删除样式a2</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
classObj: {
a1: false,
a2: false
}
}
});
</script>
</body>
</html>
运行结果:点击按钮,选择添加或者删除样式
3.绑定class
样式,数组写法:适用于要绑定的样式个数不确定、名字也不确定
<!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="../js/vue.js"></script>
<style>
.basic {
width: 200px;
height: 100px;
border: 1px solid black;
}
.a1 {
background-color: yellowgreen;
}
.a2 {
font-size: 30px;
text-shadow: 2px 2px 10px red;
}
.a3 {
border-radius: 20px;
}
</style>
</head>
<body>
<div id="app">
<!-- 数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">归海一刀</div>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
classArr: ["a1", "a2"] // 属性值为数组
}
});
</script>
</body>
</html>
运行结果:可以通过对数组进行操作,从而对样式进行添加和删除
8.2 绑定style样式
绑定style
样式有两种方式:
- 对象写法:
:style="{fontSize: xxx}"
其中xxx是动态值 - 数组写法:
:style="[a,b]"
其中a、b是样式对象
1.绑定style
样式,对象写法
注:在
style
样式绑定中,CSS中的样式名要改为小驼峰写法,如:font-size
要改为fontSize
<!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="../js/vue.js"></script>
<style>
.basic {
width: 200px;
height: 100px;
border: 1px solid black;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div id="app">
<!-- 绑定style样式,对象写法 -->
<div class="basic" :style="{fontSize: `${fsize}px`}">归海一刀</div>
<div class="basic" :style="styleObj">归海一刀</div>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
fsize: 30,
styleObj: {
// 小驼峰写法
fontSize: "30px",
color: "red",
backgroundColor: "orange"
}
}
});
</script>
</body>
</html>
运行结果:
2.绑定style
样式,数组写法
<!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="../js/vue.js"></script>
<style>
.basic {
width: 200px;
height: 100px;
border: 1px solid black;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div id="app">
<!-- 绑定style样式,数组写法 -->
<div class="basic" :style="styleArr">归海一刀</div>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
styleArr: [
{
fontSize: "30px",
color: "red"
},
{
backgroundColor: "orange"
}
]
}
});
</script>
</body>
</html>
运行结果:
9. 条件渲染
9.1 v-show
v-show
:
- 写法:
v-show="表达式"
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉(设置为
display:none
)
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h1 v-show="isActive">Hello World</h1>
<button @click="isActive=!isActive">显示/隐藏元素</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
isActive: true
}
});
</script>
</body>
</html>
运行结果:点击按钮之后,元素被隐藏,再查看DOM结构,发现元素样式上多了个style="display: none;"
9.2 v-if、v-else与v-else-if
v-if
:
- 写法:
v-if="表达式"
、v-else="表达式"
、v-else-if="表达式"
- 适用于:切换频率较低的场景
- 特点:不展示的DOM元素直接被移除
1.使用v-if
会把DOM元素直接被移除
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h1 v-if="isActive">Hello World</h1>
<button @click="isActive=!isActive">显示/隐藏元素</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
isActive: true
}
});
</script>
</body>
</html>
运行结果:点击按钮之后,元素被隐藏,再查看DOM结构,发现元素直接被移除了
2.v-if
、v-else
与v-else-if
配合使用
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h2>当前n的值是{{n}}</h2>
<button @click="n++">点击n+1</button>
<h3 v-if="n===1">Angular</h3>
<h3 v-else-if="n===2">React</h3>
<h3 v-else-if="n===3">Vue</h3>
<h3 v-else>不是前端三大框架</h3>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
n: 0
}
});
</script>
</body>
</html>
运行结果:点击按钮增加n的值,当n达到对应值时,分别展示对应的元素
注:
v-if
可以和v-else-if
、v-else
一起使用,但要求结构不能被“打断”
打断的结构示例如下:
<h3 v-if="n===1">Angular</h3>
<h3 v-else-if="n===2">React</h3>
<h3>打断结构</h3>
<h3 v-else-if="n===3">Vue</h3>
<h3 v-else>不是前端三大框架</h3>
3.v-if
与template
的配合使用:当几个v-if
的条件一样时,可以配合template
,将多个条件简写为一个
注:使用template标签,不会影响结构,而且只能和v-if配合使用,不能和v-show配合
<!-- 简写前 -->
<h3 v-if="n===1">Angular</h3>
<h3 v-if="n===1">React</h3>
<h3 v-if="n===1">Vue</h3>
<!-- 简写后 -->
<template v-if="n===1">
<h3>Angular</h3>
<h3>React</h3>
<h3>Vue</h3>
</template>
10. 列表渲染
10.1 v-for渲染列表数据
v-for
指令:用于展示列表数据
语法:v-for="(item, index) in xxx" :key="yyy"
xxx
为可遍历对象,包括数组、对象、字符串等item
为可遍历对象的每一项数据,index
为每一项数据的索引值key
属性为每项数据的唯一标识
1.使用v-for
遍历数组(常用)
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 遍历数组 -->
<ul>
<!-- p为数组成员,index为数组下标 -->
<li v-for="(p,index) in persons" :key="index">{{index}}:{{p.name}}-{{p.age}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
// id为每项数据的唯一标识
persons: [
{ id: "001", name: "张三", age: 18 },
{ id: "002", name: "李四", age: 19 },
{ id: "003", name: "王五", age: 20 }
]
}
});
</script>
</body>
</html>
注:由于数组下标
index
和id
都是唯一标识,而key
只要作为唯一标识就可以,因此以上代码中的:key="index"
也可以改为:key="p.id"
,
运行结果:
2.使用v-for
遍历对象(常用)
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 遍历对象 -->
<ul>
<!-- v为对象的属性值(value),k为对象的属性名(key) -->
<li v-for="(v,k) in car" :key="k">{{k}}-{{v}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
// 对象是一个键值对,属性名就是键(key),可以作为唯一标识
car: {
name: "奥迪A6L",
price: "70万",
color: "黑色"
}
}
});
</script>
</body>
</html>
运行结果:
3.使用v-for
遍历字符串(用的少)
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 遍历字符串 -->
<ul>
<!-- item为每个字符,index为字符串下标 -->
<li v-for="(item,index) in str" :key="index">{{index}}-{{item}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
str: "Hello"
}
});
</script>
</body>
</html>
运行结果:
4.使用v-for
遍历指定的次数(用的少)
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 遍历指定的次数 -->
<ul>
<!-- 遍历5次 -->
<li v-for="(number,index) in 5" :key="index">{{number}}-{{str}}</li>
</ul>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
str: "Hello World"
}
});
</script>
</body>
</html>
运行结果:
10.2 key的作用与原理
1.key
作为数据的唯一标志,选择原则遵循如下:
- 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
2.用index
作为key
可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 遍历数组 -->
<button @click.once="add">添加一个老刘</button>
<ul>
<!-- p为数组成员,index为数组下标 -->
<li v-for="(p,index) in persons" :key="index">
{{p.name}}-{{p.age}} <input type="text" />
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
// id为每项数据的唯一标识
persons: [
{ id: "001", name: "张三", age: 18 },
{ id: "002", name: "李四", age: 19 },
{ id: "003", name: "王五", age: 20 }
]
},
methods: {
// 对数据进行逆序添加,破坏原有顺序
add() {
const p = { id: "004", name: "老刘", age: 40 };
this.persons.unshift(p);
}
}
});
</script>
</body>
</html>
运行结果如下:在输入框中输入内容,然后再点击按钮,发现输入框中的数据显示有问题
当把以上代码的:key="index"
改为:key="p.id"
,数据则显示正常了
原理如下:
- 虚拟DOM中key的作用:key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】
- 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:若虚拟DOM中内容没变, 直接使用之前的真实DOM;若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
- 旧虚拟DOM中未找到与新虚拟DOM相同的key:创建新的真实DOM,随后渲染到到页面
10.3 列表过滤
列表过滤:对列表的数据进行筛选,筛选后的数据再渲染到页面中。
1.使用监视属性,实现列表过滤
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
人员列表:<input type="text" placeholder="请输入名字" v-model="keyWord" />
<ul>
<!-- 展示过滤后的数组 -->
<li v-for="(p,index) of filPerons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
keyWord: "", // 关键词,用于模糊搜索
persons: [
{ id: "001", name: "马冬梅", age: 19, sex: "女" },
{ id: "002", name: "周冬雨", age: 20, sex: "女" },
{ id: "003", name: "周杰伦", age: 21, sex: "男" },
{ id: "004", name: "温兆伦", age: 22, sex: "男" }
],
filPerons: [] // 用于存放过滤后的数组,避免改动原数组
},
watch: {
// 监视keyWord属性,当keyWord属性值发生变化时,调用handler函数进行模糊搜索
keyWord: {
immediate: true, // 初始化时调用handler,空字符串会匹配所有数据,首次展示为完整数据
handler(val) {
this.filPerons = this.persons.filter(p => {
return p.name.includes(val); // 判断输入框中的值是否出现在姓名中
});
}
}
}
});
</script>
</body>
</html>
运行结果:
2.使用计算属性,实现列表过滤
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
人员列表:<input type="text" placeholder="请输入名字" v-model="keyWord" />
<ul>
<!-- 展示过滤后的数组 -->
<li v-for="(p,index) of filPerons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
keyWord: "", // 关键词,用于模糊搜索
persons: [
{ id: "001", name: "马冬梅", age: 19, sex: "女" },
{ id: "002", name: "周冬雨", age: 20, sex: "女" },
{ id: "003", name: "周杰伦", age: 21, sex: "男" },
{ id: "004", name: "温兆伦", age: 22, sex: "男" }
]
},
computed: {
// 计算属性filPerons依赖于keyWord属性,当keyWord发生改变时,会重新计算属性
filPerons() {
return this.persons.filter(p => {
return p.name.includes(this.keyWord);
});
}
}
});
</script>
</body>
</html>
注:使用
computed
还是watch
的原则
1.computed 的写法更加的简洁(推荐使用),能用 computed 完成的功能,不要用 watch 完成
2.computed 无法完成的功能才用 watch 实现,例如:watch 可以进行异步操作
10.4 列表排序
列表排序:对列表数据进行排序,排序后的数据再渲染到页面中
- 可以对完整的列表数据进行排序
- 也可以配合列表过滤,对筛选后的列表数据进行排序。
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
人员列表:<input type="text" placeholder="请输入名字" v-model="keyWord" />
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<!-- 展示过滤后的数组 -->
<li v-for="(p,index) of filPerons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
keyWord: "",
sortType: 0, // 0原顺序、1降序、2升序
persons: [
{ id: "001", name: "马冬梅", age: 30, sex: "女" },
{ id: "002", name: "周冬雨", age: 31, sex: "女" },
{ id: "003", name: "周杰伦", age: 18, sex: "男" },
{ id: "004", name: "温兆伦", age: 19, sex: "男" }
]
},
computed: {
// 计算属性filPerons依赖于keyWord和sortType属性,依赖的这两个属性任何一个发生变化,都会引起重新计算属性
filPerons() {
// 对列表数据进行筛选
const arr = this.persons.filter(p => {
return p.name.includes(this.keyWord);
});
// 判断一下是否需要排序
if (this.sortType) {
arr.sort((p1, p2) => {
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age;
});
}
return arr;
}
}
});
</script>
</body>
</html>
运行结果:在输入框中输入关键词,可以对数据进行筛选,再点击按钮可以对筛选后的数据再进行排序
11. 收集表单数据
v-model
用于收集表单数据,且在收集表单数据时,根据表单控件类型的不同,默认收集的值也不同。
v-model
需要注意的点如下:
- 若表单控件为文本框或密码框,即
<input type="text"/>
或<input type="password"/>
,则v-model收集的是value值,用户输入的就是value值。 - 若表单控件为单选框,即
<input type="radio"/>
,则v-model收集的是value值,需要给标签配置value值。 - 若表单控件为复选框,即
<input type="checkbox"/>
,则有以下两种情况:- 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
- 配置了input的value属性,当v-model的初始值是非数组时,那么收集的就是checked;是数组时,那么收集的的就是value组成的数组
注:v-model的三个修饰符
1.lazy
:失去焦点时,再收集数据
2.number
:输入字符串转为有效的数字
3.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>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 阻止表单默认跳转行为 -->
<form @submit.prevent="demo">
账号:<input type="text" v-model.trim="userInfo.account" /> <br /><br />
密码:<input type="password" v-model="userInfo.password" /> <br /><br />
年龄:<input type="number" v-model.number="userInfo.age" /> <br /><br />
性别: 男<input type="radio" name="sex" v-model="userInfo.sex" value="male" /> 女<input type="radio" name="sex" v-model="userInfo.sex" value="female" /> <br /><br />
爱好: 学习<input type="checkbox" v-model="userInfo.hobby" value="study" /> 打游戏<input type="checkbox" v-model="userInfo.hobby" value="game" /> 吃饭<input
type="checkbox"
v-model="userInfo.hobby"
value="eat"
/>
<br /><br />
所属校区
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
<option value="wuhan">武汉</option>
</select>
<br /><br />
其他信息:
<textarea v-model.lazy="userInfo.other"></textarea> <br /><br />
<input type="checkbox" v-model="userInfo.agree" />阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
<button>提交</button>
</form>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
userInfo: {
account: "",
password: "",
age: 18,
sex: "female",
hobby: [],
city: "beijing",
other: "",
agree: ""
}
},
methods: {
demo() {
console.log(JSON.stringify(this.userInfo));
}
}
});
</script>
</body>
</html>
运行结果如下:输入内容,勾选单选和复选框等控件,再点击提交按钮,可以看到控制台打印了表单提交的信息。
12. 内置指令
目前已经学过的内置指令有:v-bind
、v-model
、v-on
、v-show
、v-if
、v-else
、v-for
。
12.1 v-text 解析普通文本
v-text
指令:用于解析普通文本,向其所在的节点中渲染文本内容。
v-text
与插值语法的区别:v-text
会替换掉节点中的内容,而{{xxx}}
则不会,因此{{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>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 插值语法相对v-text更加灵活 -->
<div>Hello, {{name}}</div>
<!-- v-text会替换掉节点中的内容 -->
<div v-text="name">Hello</div>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
name: "Jack"
}
});
</script>
</body>
</html>
运行结果:
12.2 v-html 解析html标签
v-html
指令:用于解析html标签,向其所在的节点中渲染包含html结构的内容。
v-html
与插值语法的区别:v-html
会替换掉节点中的内容,而{{xxx}}
则不会。
注:
v-html
有安全性问题
1.在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击
2.一定要在可信的内容上使用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>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- v-text只能解析普通文本,无法解析html标签 -->
<div v-text="str"></div>
<!-- v-html可以解析html标签 -->
<div v-html="str"></div>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
str: `<h3>Hello World</h3>`
}
});
</script>
</body>
</html>
运行结果:
12.3 v-cloak 隐藏模板
v-cloak
指令:
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
- 该指令可以配合css属性选择器,解决网速慢时页面展示出{{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>Document</title>
<script src="../js/vue.js"></script>
<style>
/* 使用该属性的元素被隐藏 */
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<!--
初始时,v-cloak属性存在,但由于属性选择器的作用,会将h2标签隐藏;
等到vue接管容器后,会移除掉h2上的v-cloak属性,此时会显示h2标签
-->
<h2 v-cloak>{{name}}</h2>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
name: "Jack"
}
});
</script>
</body>
</html>
12.4 v-once 仅渲染一次
v-once
指令:
- v-once所在节点在初次动态渲染后,就视为静态内容
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
n: 1
}
});
</script>
</body>
</html>
运行结果:点击按钮,只有下面的n值发生变化,而上面的n值不变化
12.5 v-pre 跳过元素编译
v-pre
指令:
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<!-- 该标签未用到指令语法和插值语法,可以使用v-pre加快编译 -->
<h2 v-pre>Vue其实很简单</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
const vm = new Vue({
el: "#app",
data: {
n: 1
}
});
</script>
</body>
</html>
13. 自定义指令
13.1 函数式和对象式
自定义指令有两种定义方式:
- 函数式定义
- 对象式定义
1.函数式定义
例:定义一个 v-big
指令,和 v-text
功能类似,但会把绑定的数值放大10倍
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big="n"></span></h2>
<button @click="n++">点我n+1</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
new Vue({
el: "#app",
data: {
n: 1,
},
// 自定义指令
directives: {
// element表示真实的dom元素, binding表示绑定的对象
big(element, binding) {
element.innerText = binding.value * 10;
},
},
});
</script>
</body>
</html>
big函数被调用的条件:
1.指令与元素成功绑定时(一上来)
2.指令所在的模板被重新解析时
运行结果:点击按钮,上面的n值+1,下面的n值+1后乘以10倍
2.对象式定义
配置对象中常用的三个回调:
bind
:指令与元素成功绑定时调用inserted
:指令所在元素被插入页面时调用update
:指令所在模板结构被重新解析时调用
对象式定义与函数式定义的区别:函数式定义写法等于对象式定义中的
bind
+update
回调函数,而对象式定义还有inserted
回调函数,可以实现函数式定义无法实现的细节操控。
例:定义一个 v-fbind
指令,和 v-bind
功能类似,但可以让其所绑定的input元素默认获取焦点
<!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="../js/vue.js"></script>
</head>
<body>
<div id="app">
<h2>当前的n值是:<span v-text="n"></span></h2>
<button @click="n++">点我n+1</button>
<br />
<input type="text" v-fbind:value="n" />
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
new Vue({
el: "#app",
data: {
n: 1,
},
// 自定义指令
directives: {
fbind: {
//指令与元素成功绑定时(一上来)
bind(element, binding) {
element.value = binding.value;
},
//指令所在元素被插入页面时
inserted(element, binding) {
element.focus();
},
//指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value;
},
},
},
});
</script>
</body>
</html>
运行结果:刷新页面后,文本框默认获取焦点,点击按钮后失去焦点
13.2 局部指令和全局指令
自定义指令分为两类:
- 局部指令
- 全局指令
1.局部指令写法
// 函数式定义
new Vue({
directives:{指令名:回调函数}
})
// 对象式定义
new Vue({
directives:{指令名:配置对象}
})
new Vue({
// 局部自定义指令
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;
},
},
},
});
2.全局指令写法
// 函数式定义
Vue.directive(指令名, 回调函数)
// 对象式定义
Vue.directive(指令名, 配置对象)
// 函数式定义
Vue.directive("big", function (element, binding) {
element.value = binding.value;
});
// 对象式定义
Vue.directive("fbind", {
bind(element, binding) {
element.value = binding.value;
},
inserted(element, binding) {
element.focus();
},
update(element, binding) {
element.value = binding.value;
},
});
自定义指令注意事项:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
new Vue({
directives: {
"big-number"(element, binding) {
element.innerText = binding.value * 10;
},
},
});
14. Vue的生命周期
14.1 生命周期概念
Vue的生命周期:又称生命周期回调函数、生命周期函数、生命周期钩子,是Vue在关键时刻帮我们调用的一些特殊名称的函数。
生命周期函数中的this指向是vm 或 组件实例对象
vue的生命周期分为以下四个流程:每个流程对应两个函数,其中最重要的是mounted
和 beforeDestroy
。
- 创建流程:
beforeCreate
:此时无法通过vm访问到data中的数据和methods中的方法。created
:此时可以通过vm访问到data中的数据和methods中配置的方法。
- 挂载流程:
beforeMount
:此时页面呈现的是未经Vue编译的DOM结构,所有对DOM的操作,最终都不奏效。mounted
:此时页面呈现的是经过Vue编译的DOM,对DOM的操作均有效(尽可能避免)。至此初始化过程结束,一般在此阶段:开启定时器、发送网络请求、订阅消息以及绑定自定义事件等初始化操作。
- 更新流程:
beforeUpdate
:此时数据是新的,但页面是旧的,即页面尚未和数据保持同步。updated
:此时数据是新的,页面也是新的,即页面尚未和数据保持同步。
- 销毁流程
beforeDestroy
:此时vm中所有的data、methods和指令等等,都处于可用状态,马上要执行销毁过程。一般在此阶段:关闭定时器、取消订阅消息以及解绑自定义事件等收尾操作。destroyed
:此时vm已完成销毁。
Vue的生命周期流程图:
14.2 生命周期演示
1.生命周期钩子函数的调用顺序
<!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="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="app">
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
<script>
Vue.config.productionTip = false; // 阻止vue在启动时生成生产提示
new Vue({
el: "#app",
data: {
n: 1,
},
methods: {
add() {
this.n++;
},
bye() {
this.$destroy();
},
},
beforeCreate() {
console.log("beforeCreate");
},
created() {
console.log("created");
},
beforeMount() {
console.log("beforeMount");
},
mounted() {
console.log("mounted");
},
beforeUpdate() {
console.log("beforeUpdate");
},
updated() {
console.log("updated");
},
beforeDestroy() {
console.log("beforeDestroy");
},
destroyed() {
console.log("destroyed");
},
});
</script>
</body>
</html>
2.生命周期的常用应用场景
常用的生命周期钩子:
mounted
: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等(初始化操作)beforeDestroy
: 清除定时器、解绑自定义事件、取消订阅消息等(收尾操作)
关于销毁Vue实例:
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
<!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="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="app">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
<script>
Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。
new Vue({
el: "#app",
data: {
opacity: 1,
},
methods: {
stop() {
this.$destroy(); // 销毁vm
},
},
//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted() {
// 初始化操作:启动定时器
console.log("mounted", this);
this.timer = setInterval(() => {
console.log("setInterval");
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1;
}, 16);
},
beforeDestroy() {
// 收尾操作:清除定时器
clearInterval(this.timer);
},
});
</script>
</body>
</html>
运行结果:
未点击按钮停止变换前,点击设置透明度有效;点击按钮停止变换后,再点击设置透明度则无效,因为vm被销毁了。
15. Vue的组件化编程
15.1 模块与组件
1.模块与模块化:
- 模块:一个模块就是一个js文件,向外提供特定功能的 js 程序,作用是可以复用 js、简化 js 的编写、提高 js 运行效率。
- 模块化:当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用。
2.组件与组件化:
- 组件:用来实现局部(特定)功能效果的代码集合(html/css/js/image……),作用是复用编码、简化项目编码、提高运行效率。
- 组件化:当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用。
传统方式编写应用:
组件化方式编写应用:
15.2 非单文件组件
组件的分类:
- 非单文件组件:模板编写没有提示, 没有构建过程,无法将 ES6 转换,不支持组件的 CSS,真正开发中几乎不用。
- 单文件组件:单文件组件为
.vue
文件,由<template>
模板页面、<script>
JS模块对象和<style>
样式组成。
虽然非单文件组件在开发中几乎不用,但是要想学会单文件组件(
.vue
) ,必须先从非单文件组件开始入手。
15.2.1 组件的基本使用
Vue中使用组件的三大步骤:
- 创建组件
- 注册组件
- 使用组件
1.创建组件:
- 使用
Vue.extend(options)
创建组件 Vue.extend(options)
和new Vue(options)
时传入的options
几乎一样,但有区别如下:el
不能写,因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器data
必须写成函数,为了避免组件被复用时,数据存在引用关系
注:使用template可以配置组件结构
2.注册组件
- 局部注册:靠
new Vue
的时候传入components
配置项 - 全局注册:靠
Vue.component('组件名',组件)
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>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="app">
<!-- 第三步:使用student组件 -->
<student></student>
</div>
<script>
Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。
// 第一步:创建student组件
const student = Vue.extend({
template: `
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<button @click="showName">点我提示姓名</button>
</div>
`,
// 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
data() {
return {
name: "张三",
age: 18,
};
},
methods: {
showName() {
alert(this.name);
},
},
});
// 第二步:注册student组件(局部注册)
new Vue({
el: "#app",
components: {
student,
},
});
</script>
</body>
</html>
以上局部注册也可改为全局注册:
// 第二步:注册student组件
Vue.component("student", student);
new Vue({
el: "#app",
});
15.2.2 组件的注意事项
1.组件名的取名:
- 组件名由一个单词组成:
- 第一种写法(首字母小写):
school
- 第二种写法(首字母大写):
School
(推荐)
- 第一种写法(首字母小写):
- 组件名由多个单词组成:
- 第一种写法(kebab-case命名):
my-school
- 第二种写法(CamelCase命名):
MySchool
(推荐,且该写法需要Vue脚手架支持)
- 第一种写法(kebab-case命名):
2.组件标签的两种写法
- 双标签:
<school></school>
- 单标签:
school/>
备注:不用使用脚手架时,
<school/>
会导致后续组件不能渲染
3.Vue.extend()
可以简写:const school = Vue.extend(options)
,可简写为 const school = options
<!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="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="app">
<!-- 第三步:使用student组件 -->
<Student />
</div>
<script>
Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。
// 第一步:创建student组件,简写省略了Vue.extend()
const Student = {
template: `
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
<button @click="showName">点我提示姓名</button>
</div>
`,
// 组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
data() {
return {
name: "张三",
age: 18,
};
},
methods: {
showName() {
alert(this.name);
},
},
};
// 第二步:注册student组件
new Vue({
el: "#app",
components: {
Student,
},
});
</script>
</body>
</html>
15.2.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>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root"></div>
<script>
Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。
//定义student组件
const student = Vue.extend({
name: "student",
template: `
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
name: "张三",
age: 18,
};
},
});
//定义school组件
const school = Vue.extend({
name: "school",
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data() {
return {
name: "北京大学",
address: "北京",
};
},
//注册组件(局部),school中嵌套student
components: {
student,
},
});
//定义hello组件
const hello = Vue.extend({
template: `<h1>{{msg}}</h1>`,
data() {
return {
msg: "欢迎来到北京!",
};
},
});
//创建vm
new Vue({
template: `
<div>
<hello></hello>
<school></school>
</div>
`,
el: "#root",
//注册组件(局部)
components: {
hello,
school,
},
});
</script>
</body>
</html>
组件之间结构如下:
15.2.4 VueComponent
关于VueComponent:
1.school
组件本质是一个名为 VueComponent
的构造函数,且不是程序员定义的,是 Vue.extend
生成的
2.我们只需要编写 <school/>
或 <school></school>
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行了 new VueComponent(options)
3.特别注意,每次调用 Vue.extend
,返回的都是一个全新的 VueComponent
4.关于this指向:
- 组件配置中的this指向:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是
VueComponent
实例对象 - new Vue(options)配置中的this指向:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是
Vue
实例对象
15.2.5 一个重要的内置关系
一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
,这个关系可以让VueComponent组件实例对象访问到 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>Document</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root"></div>
<script>
Vue.config.productionTip = false; //阻止 vue 在启动时生成生产提示。
Vue.prototype.x = 99;
//定义school组件
const school = Vue.extend({
name: "school",
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showX">点我输出x</button>
</div>
`,
data() {
return {
name: "北京大学",
address: "北京",
};
},
methods: {
showX() {
alert(this.x);
},
},
});
new Vue({
template: `<school></school>`,
el: "#root",
components: { school },
});
</script>
</body>
</html>
15.3 单文件组件
15.3.1 单文件组件的组成
单文件组件:即一个 .vue
文件,由以下三部分组成
<template>
: 模板页面<script>
:JS模块对象<style>
: 样式
1.模板页面
<template>
页面模板
</template>
2.JS模块对象
<script>
export default {
data() {
return {};
},
methods: {},
computed: {},
components: {},
};
</script>
3.样式
<style>
样式定义
</style>
15.3.2 单文件组件的示例
本节仅展示代码和文件之间的关联逻辑,此处的代码无法直接运行,需要搭建脚手架环境以后才能运行。
示例:有以下文件结构,作为单文件组件的实际开发使用
文件之间关系如下:
index.html
:主页面main.js
:程序入口App.vue
:汇总所有组件,此处包括School和Student组件School.vue
:学校组件Student.vue
:学生组件
index.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root"></div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>
main.js
:
import App from './App.vue'
new Vue({
el:'#root',
template:`<App></App>`,
components:{App},
})
App.vue
:
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
//引入组件
import School from "./School.vue";
import Student from "./Student.vue";
export default {
name: "App",
components: {
School,
Student,
},
};
</script>
School.vue
:
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
name:'北京大学',
address:'北京'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>
<style>
.demo{
background-color: orange;
}
</style>
Student.vue
:
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
}
</script>