目录
- 第1章:Vue核心
- 第2章:Vue组件化编程
- 第3章:使用Vue脚手架
- 第4章 :Vue中的ajax
- 第5章:Vuex
- 第6章:Vue-router
- 第7章:Vue UI组件库
第1章:Vue核心
1-1.Vue简介
构建用户界面: 把拿到的数据通过某种办法变成用户可以看到的界面
渐进式: 渐即逐渐,进即递进,也就是说从一个轻量小巧的核心库逐渐递进到各式各样的Vue插件库
1-2.Vue官网使用指南
API:就相当于Vue的字典,遇到不会的方法就可以去找API
Awesome:Vue 官方整理的比较好用的包
1-3.搭建Vue开发环境
关掉Vue控制台的提示:
1.安装Vue开发工具(安装开发工具的提示)
2.Vue.config.productionTip = false;
阻止 vue 在启东市生成生产提示
1-4.初识Vue
按shift强刷报错:
原因是没有找到页签图标,在根目录放一个页签图标就可以了
Hello小案例:
<!-- 准备好一个容器 -->
<div id="root">
<!-- 差值:即插入值,用{{}}表示,可直接访问data数据 -->
<h1>Hello,{{name}}</h1>
<h1>我的年龄是:18</h1>
</div>
<script>
// 创建Vue实例
new Vue({
// el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
// 也可以是节点(document.getElementById('root')
el: '#root',
// data中用于存储数据,数据供el所指定的容器去使用,值暂时先写成一个对象(函数)
data: {
name: '尚硅谷',
age: 18
}
});
</script>
总结:
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
- root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
- root容器里的代码被称为【Vue模板】;
- Vue实例和容器是一一对应的;
- 真实开发中只有一个Vue实例,并且会配合着组件一起使用;
- {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
- 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新
注意区分:JS表达式和JS代码(语句):
-
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
(1)a
(2)a+b
(3)demo(1)
(4)x === y ‘a’ : ‘b’ -
js代码(语句)
(1)if(){}
(2)for(){}
1-5.模板语法
概念: 容器里面的代码就是模板语法
模板语法分为两大类:
- 差值语法:
功能:用于解析标签体内容。
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。 - 指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件。。。)
备注:Vue中有很多的指令,且形式都是:v-。
举例:v-bind:hred="xxx"
或 简写为:href="xxx"
,xxx同样要写js表达式,且可以直接读取到data中的所有属性。
错误示例:
<a href="url"></a> <!-- 此时的url只是一个字符串 -->
<a href={{url}}></a> <!-- 差值写在属性的写法已经被Vue弃用 -->
<a :href="url"></a> <!-- 正确用法 -->
1-6.数据绑定
Vue中有2种数据绑定的方式:
- 单向绑定(v-bind):数据只能从data流向页面。
- 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
备注:
1.双向绑定一般都应用在表单元素上(如:input、select等)
2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
演示:
<!-- 单向数据绑定 !-->
<input type="text" :value="name">
<!-- 双向数据绑定 !-->
<input type="text" v-model="name">
错误示例:
以下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上
<h2 v-model:x="name">hello</h2>
1-7.el和data的两种写法
-
el有2种写法:
(1)new Vue时候el属性;
(2)先创建Vue实例,随后再通过vm.$mount(’#root’)指定el的值 -
data有2种写法:
(1)对象式
(2)函数式
如何选择?目前哪种写法都可以,以后学到组件时,data必须使用函数式,否则会报错 -
一个重要的原则:
只要是Vue所管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了,而是windowconst v = new Vue({ // el: '#root', //el第一种写法 // data: { //data第一种写法 // name: '尚硅谷' // }, data() { //data第二种写法 return { name: '尚硅谷' } } }) v.$mount('#root') //el第二种写法
1-8.MVVM模型
MVVM模型:
- M:模型(Model):data中的数据
- V:视图(View):Vue模板
- VM:视图模型(ViewModel):Vue实例对象(绑定数据,dom监听)
观察发现:
- data中所有的属性,最后都出现在了vm身上
- vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接访问
1-9.数据代理
1-9-1.回顾Object.defineProperty方法
作用: 给对象定义属性,并对该属性进行一些高级的设置
参数: 1. 需要添加属性的对象
2. 添加属性名称
3. 配置项(配置属性值或一些其它的参数)
注意: 定义的属性是浅紫色的,说明它是不可枚举(遍历)的
常用配置属性
作用
value
设置属性值
enumerable
控制属性是否可以枚举,默认值为false
writable
控制属性是否可以被修改,默认值为false
configurable
控制属性是否可以被删除,默认值为false
get函数
当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
set函数
当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
怎样让一个变量和一个对象的值关联起来?
let number = 18;
let person = {
name: '张三',
sex: '男'
}
Object.defineProperty(person, 'age', {
get: function() {
return number;
}
})
为什么无法直接修改person.age的值,怎么修改?
let number = 18;
let person = {
name: '张三',
sex: '男'
}
Object.defineProperty(person, 'age', {
get: function() {
return number;
},
set: function(value) {
number = value;
}
})
方法一:因为age是不可写的,可以设置writable属性或直接修改与age关联的number
方法二:通过set函数把修改的值赋值给number
知识拓展:
Object.keys
作用:这个方法可以把传入对象所有属性的属性名提取出来变成数组
参数:对象
1-9-2.何为数据代理
定义: 通过一个对象代理对另一个对象中属性的操作(读/写)
// 通过obj2访问并且修改obj的x
let obj = {x: 100}
let obj2 = {y: 200}
Object.defineProperty(obj2, 'x', {
get() {
return obj.x;
},
set(value) {
obj2.x = value;
}
})
1-9-3.Vue中的数据代理
- Vue中的数据代理:
通过 vm对象 代理 vm._data对象(就是你配置的data对象) 中的属性操作(读/写) - Vue中数据代理的好处:
更加方便的操作data中的数据 - 基本原理:
通过Object.defineProperty把data对象中所有属性都添加到vm上,并为其指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性。
备注:vm._data展开后你会发现它和data并不一致,这是因为Vue底层做了数据劫持而非数据代理,其目的就是为实现响应式的操作,后面会讲到。
1-10.事件处理
1-10-1.事件的基本使用
-
使用
v-on:xxx
或@xxx
绑定事件,其中xxx是事件名; -
事件的回调需要配置在methods对象中,最终会再vm上;
-
methods中配置的函数,不要用箭头函数!否则this就不是vm了而是window;
-
methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
-
@click="demo"
和@click="demo($event)"
效果一致,前者默认参数就是事件对象,后者要用$event才能生成事件对象,并且可以传多个参数;<button @click=“demo”>点我提示信息
<button @click=“showInfo(66)”>点我提示信息
<button @click=“showInfo(66,$event)”>点我提示信息
备注:method上面的方法最终也会出现在vm上面,但它并没有作数据代理,因为没有必要,函数一旦定义好就直接拿来用,不会再改。
1-10-2.事件修饰符
事件修饰符
描述
prevent
阻止默认事件(常用)
stop
阻止事件冒泡(常用)
once
事件只触发一次(常用)
capture
使用事件的捕获模式
self
只有event.target是当前操作的元素时才触发事件
passive
先执行默认行为,后执行回调函数
示例:
1.阻止默认事件
<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a> <!-- 加了阻止了链接跳转 -->
2.阻止事件冒泡
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button> <!-- 没加会触发外层事件,加了则不会 -->
</div>
3.事件只触发一次
<button @click.once="showInfo">点我提示信息</button> <!-- 第一次点会触发事件,第二次点则不会 -->
4.使用事件的捕获模式(事件流 分为 捕获 和 冒泡)
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)"> <!-- 没加输出1,2,加了输出2,1 -->
div2
</div>
</div>
5.只有event.target是当前操作的元素时才触发事件
<div class="demo1" @click.self="showInfo">
<!-- 点击button,div和button的事件源都是button -->
<!-- 没加会触发div的事件,没加则不会,因为self要求事件源是本身 -->
<button @click="showInfo">点我提示信息</button>
</div>
6.事件的默认行为立即执行,无需等待事件回调执行完毕
<ul @wheel.passive="demo" class="list"> <!--加了先滚动后回调,没加先回调后滚动-->
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
为什么滚动了鼠标,滚动条却不往下走?
因为wheel事件会先触发滚动事件,然后会去执行回调回调执行完了才会执行默认行为,而passive就是为了解决这种问题,它会先执行默认行为,然后再走回调(scrool同效)
好处:可以一定程度上进行优化,常用在移动端的项目(手机、平板),但用的比较少。
1-10-3.键盘事件
1.Vue中常用的按键别名:
回车 => enter
删除 => delete(捕获“删除(delete)”和“退格(BackSpace)”键)
退出 => esc
空格 => space
换行 => tab(特殊,必须配合 keydown 去使用)
上 => up
下 => down
左 => left
右 => right
2.Vue未提供别名的案件,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
<input type="text" placeholder="按下回车提示输入" @keyup.caps-lock="showInfo">
3.系统修饰键(用法特殊):ctrl、alt、shift、meta
(1)配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其它键,事件才被触发。(此时的e.key值是其它键,而不是系统修饰键)
(2)配合keydown使用:正常触发事件。
4.也可以使用keyCode去指定具体的按键(不推荐,因为不同的键盘编码可能会不统一,尽量用键名)
<input type="text" placeholder="按下回车提示输入" @keyup.13="showInfo">
5.Vue.config.keyCodes.自定义别名 = 键码,可以去指定按键别名
<!-- Vue.config.keyCodes.huiche = 13; -->
<input type="text" placeholder="按下回车提示输入" @keyup.huiche="showInfo">
1-10-4.事件总结
修饰符小技巧:事件修饰符可以连着写
<!-- 阻止默认事件和冒泡 -->
<div class="demo1" @click="showInfo">
<a href="http://www.atguigu.com" @click.stop.prevent="showInfo">点我提示信息</a>
</div>
<!-- 按 ctrl + y 才触发(系统修饰符 + 键名) -->
<input type="text" placeholder="按下回车提示输入" @keyup.ctrl.y="showInfo">
1-11.计算属性与监视
1-11-1.姓名案例
差值语法实现:
<div id="root">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
姓名:<span>{{firstName.slice(0,3)}}-{{lastName}}</span>
</div>
<script>
const vm = new Vue({
el: '#root',
data: { firstName: '张', lastName: '三' }
})
</script>
methods实现:
<div id="root">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
姓名:<span>{{fullName()}}</span>
<button @click="fullName">点我</button>
</div>
<script>
const vm = new Vue({
el: '#root',
data: { firstName: '张', lastName: '三' },
methods: {
fullName() { return this.firstName + '-' + this.lastName } // this指向vm
}
})
</script>
computed实现:
<div id="root">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
姓名:<span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el: '#root',
data: { firstName: '张', lastName: '三' },
computed: {
fullName: {
get() {
// 此处的this是vm
return this.firstName + '-' + this.lastName
}
}
}
})
</script>
1-11-2.计算属性
对于Vue来说,data里的配置项就是属性 。
而计算属性,就是拿着写完的属性去加工计算,生成一个全新的属性。
计算属性直接被挂载到vm上,直接读取使用即可(_data里面没有计算属性)。
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName: {
get() {
// 此处的this是vm
return this.firstName + '-' + this.lastName;
}
}
}
})
get有什么作用?
当有人读取fullName时,get就会被调用,且返回值就作为fullName的值。
(底层就是用Object.defineProperty的getter/setter实现的)
get什么时候被调用?
1.初次读取时会执行一次(往后就会取缓存里的数据)
2.所依赖的数据发生变化时会被再次调用(所以不用担心修改了值还会从缓存里获取)
<!-- Vue模板里有4个fullName,为什么get只被调用了一次?
因为Vue底层为computed做了一个缓存机制,重复的计算属性会到缓存里面获取 -->
<div id="root">
<span>{{fullName}}</span>
<span>{{fullName}}</span>
<span>{{fullName}}</span>
<span>{{fullName}}</span>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName: {
get() {
console.log('get被调用了'); // 只输出了一次 “get被调用了”
return this.firstName + '-' + this.lastName
}
}
}
})
</script>
set什么时候被调用?
当fullName被修改时。
如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生变化。
<!-- 当fullName被修改时页面并没有发生变化,原因是set并没有改到firstName和lastName,它只做了输出。
所以要想页面也发生变化,那就得给set做一个加工,让它修改到firstName和lastName。 -->
<script>
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName: {
get() {
console.log('get被调用了'); // 只输出了一次 “get被调用了”
return this.firstName + '-' + this.lastName
},
set(value) {
console.log('set', value);
// 格式:张-三
const arr = value.split('-');
this.firstName = arr[0];
this.lastName = arr[1];
}
}
}
})
</script>
computed对比methods:
computed有缓存机制(复用),效率更高、调试方便(devtools上会有一个computed的分类)。
methods函数出现几次就调用几次,效率比较低。
计算属性的简写:
一般来说计算属性是不修改的,更多是读取出来展示。
并不是什么时候都能用简写,只有考虑读取,不考虑修改的时候才能用简写形式。
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName() {
console.log('get被调用了');
return this.firstName + '-' + this.lastName
}
}
})
1-11-3.天气案例
<div id="root">
<h2>今天天气很{{info}}</h2>
<!-- <button @click="isHot = !isHot">切换天气</button> -->
<button @click="changeWeather">切换天气</button>
</div>
<script>
Vue.config.productionTip = false;
new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽';
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
})
</script>
1-11-4.监视属性
监视属性watch:
1.当被监视的属性变化时,回调函数自动调用,进行相关操作;
2.监视的属性必须存在,才能进行监视!!(除了data属性,computed属性也能监视)
3.监视的两种写法:
(1)new Vue时传入watch配置;
(2)通过vm.$watch监视;
(明确要监视哪些属性的时候用第一个。创建实例的时候不知道要监视谁,后续根据用户的一些行为然后才知道要监视哪个属性就用第二个)
watch配置属性:
属性
描述
immediate (立即的)
初始化时让handlder调用一下
handler(newVal, oldVal)
当监视的属性发生改变的时候调用,参数可以拿到改变前后的值
deep
深度监听,可以监测多层级数据的改变
// 写法一
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
methods: {
watch: {
isHot: {
immediate: true,
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue);
}
}
}
})
// 方法二
vm.$watch('isHot', { // 注意这里监视的属性要写字符串,配置对象的写法和上一种完全一致
immediate: true,
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue);
}
})
深度监视:
1.Vue中的watch默认不监测对象内部值的变化(监测一层)
2.配置deep:true可以监测对象内部值变化(监测多层)
备注:
(1)Vue默认是可以监视到多层级数据的改变的(修改number.a页面发生改变可以看出)。但是watch属性默认是不可以的,要想可以就得打开深度监视(为了效率)。
(2)使用watch时根据数据的具体结果,决定是否采用深度监视。
const vm = new Vue({
el: '#root',
data: {
isHot: true,
numbers: {
a: 1,
b: 1
}
},
computed: {
methods: {
watch: {
isHot: {
// 监视多级结构中某个属性的变化————加上引号(原始写法)
// 'numbers.a': {
// handler() {
// console.log('a被改变了');
// }
// }
// 监视多级结构中所有属性的变化————开启深度模式
'numbers': {
deep: true,
handler() {
console.log('numbers被改变了');
}
}
}
})
监视属性的简写:
当配置项只有handler的时候才可以简写。
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽';
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
watch: {
// 正常写法:
/* isHot: {
// immediate: true,
// deep:true,
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue);
}
}, */
// 简写:
isHot(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue);
}
}
});
// 正常写法:
vm.$watch('isHot', {
// immediate: true,
// deep:true,
handler(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue);
}
});
// 简写:
vm.$watch('isHot', function(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue);
});
watch对比computed:
1.computed能完成的功能,watch都能完成。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
两个重要的小原则:
1.所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数等),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
还是拿上一个天气案例作对比:
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三',
fullName: '张-三'
},
watch: {
firstName(val) {
this.fullName = val + '-' + this.lastName;
},
lastName(val) {
this.fullName = this.firstName + '-' + val;
}
}
})
上面代码是命令式且重复的。将他们与计算属性的版本进行比较:
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName() {
return this.firstName + '-' + this.lastName
}
好得多了,不是吗?
延迟一秒钟后再响应:
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三',
fullName: '张-三'
},
watch: {
firstName(val) {
// 注意这里用普通函数自带this 默认指向window,箭头函数没有this 会往上继承
setTimeout(() => {
this.fullName = val + '-' + this.lastName;
}, 1000)
},
lastName(val) {
setTimeout(() => {
this.fullName = this.firstName + '-' + val;
}, 1000)
}
}
})
watch轻松完成。
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三'
},
computed: {
fullName() {
// fullName值等于空,因为fullName没有返回值,返回值给了定时器的回调
setTimeout(() => {
return this.firstName + '-' + this.lastName;
}, 1000)
}
相比较之下,computed却无法实现,因为它无法执行异步任务。
1-12.class与style绑定
class样式:
写法:class="xxx"
xxx可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
style样式:
:style="{fontSize: xxx}"
其中xxx是动态值。(注意样式名得是小驼峰)
:style="[a,b]"
其中a、b是样式对象。
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/>
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br/>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br/>
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br/>
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: '尚硅谷',
mood: 'normal',
classArr: ['atguigu1', 'atguigu2', 'atguigu3'],
classObj: {
atguigu1: false,
atguigu2: false
},
styleObj: {
fontSize: '40px',
color: 'red'
},
styleObj2: {
backgroundColor: 'orange'
},
styleArr: [{
fontSize: '40px',
color: 'red'
}, {
backgroundColor: 'gray'
}]
},
methods: {
changeMood() {
const arr = ['normal', 'happy', 'sad'];
const index = Math.floor(Math.random() * 3);
this.mood = arr[index];
}
},
})
</script>
1-13.条件渲染
-
v-if
写法:
(1)v-if=“表达式”
(2)v-else-if=“表达式”
(3)v-else=“表达式”
适用于:切换频率较低的场景。
特点:不展示的DOM元素直接被移除。
注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。 -
v-show
写法:v-show=“表达式”
适用于:切换频率较高的场景。
特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉 -
备注: 使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到。
当前的n是{{n}}
1-14.列表渲染
1-14-1.基本列表
v-for指令:
1.用于展示列表数据
2.语法:v-for="(item, index) in xxx" :key=“yyy”
3.可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>
<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,k) of car" :key="k">
{{k}}-{{value}}
</li>
</ul>
<!-- 遍历字符串 -->
<h2>测试遍历字符串(用得少)</h2>
<ul>
<li v-for="(char,index) of str" :key="index">
{{char}}-{{index}}
</li>
</ul>
<!-- 遍历指定次数 -->
<h2>测试遍历指定次数(用得少)</h2>
<ul>
<li v-for="(number,index) of 5" :key="index">
{{index}}-{{number}}
</li>
</ul>
</div>
<script type="text/javascript">
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>
1-14-2.key的原理
面试题:react、vue中的key有什么作用?(key的内部原理)
1.虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2.对比规则:
(1)旧虚拟DOM中找到了与新虚拟DOM相同的key:
① 若虚拟DOM中内容没变, 直接使用之前的真实DOM!
② 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2)旧虚拟DOM中未找到与新虚拟DOM相同的key,创建新的真实DOM,随后渲染到到页面。
3.用index作为key可能会引发的问题:
(1)若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 => 界面效果没问题, 但效率低。
(2)如果结构中还包含输入类的DOM:会产生错误DOM更新 => 界面有问题。
4.开发中如何选择key:
(1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
(2)如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
示例:
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<button @click.once="add">添加一个老刘</button>
<ul>
<li v-for="(p,index) of persons" :key="index"> <!-- 这里不要用index哈 -->
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
</div>
<script type="text/javascript">
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>
1-14-3.列表过滤
当computed和watch都能实现的时候,优先使用computed
<div id="root">
<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 type="text/javascript">
// watch实现:
/* const vm = new Vue({
el: '#root',
data: {
keyWord: '',
persons: [{
id: '001',
name: '马冬梅',
age: 18,
sex: '女'
}, {
id: '002',
name: '周冬雨',
age: 19,
sex: '女'
}, {
id: '003',
name: '周杰伦',
age: 20,
sex: '男'
}, {
id: '004',
name: '温兆伦',
age: 21,
sex: '男'
}],
filPersons: []
},
watch: {
keyWord: {
immediate: true,
handler(val) {
this.filPersons = this.persons.filter((p) => {
return p.name.indexOf(val) !== -1;
})
}
}
}
}) */
// computed实现:
const vm = new Vue({
el: '#root',
data: {
keyWord: '',
persons: [{
id: '001',
name: '马冬梅',
age: 18,
sex: '女'
}, {
id: '002',
name: '周冬雨',
age: 19,
sex: '女'
}, {
id: '003',
name: '周杰伦',
age: 20,
sex: '男'
}, {
id: '004',
name: '温兆伦',
age: 21,
sex: '男'
}]
},
computed: {
filPersons() {
return this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
})
}
}
})
</script>
1-14-4.列表排序
这个案例充分体现了computed功能的强大,只要有任何一个属性发生了变化,整个计算属性都会重新办进行计算。
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyWord">
<button @click="sortType = 1">年龄升序</button>
<button @click="sortType = 2">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="(p,index) of filPersons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false
// computed实现:
const vm = new Vue({
el: '#root',
data: {
keyWord: '',
persons: [{
id: '001',
name: '马冬梅',
age: 30,
sex: '女'
}, {
id: '002',
name: '周冬雨',
age: 31,
sex: '女'
}, {
id: '003',
name: '周杰伦',
age: 18,
sex: '男'
}, {
id: '004',
name: '温兆伦',
age: 21,
sex: '男'
}],
sortType: 0 //0原顺序 @click="sortType = 0"1降序 2升序
},
computed: {
filPersons() {
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
});
// 判断一下是否需要排序
if (this.sortType) {
arr.sort((p1, p2) => {
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
})
}
return arr;
}
}
})
</script>
知识拓展:
sort(a,b)数组方法,a-b升序,b-a降序,更改原数组。