Vue03-样式操作+列表数据渲染+数据监视
1.绑定class和style
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="../js/vue.js"></script>
<style>
.basic {
width: 300px;
height: 100px;
border: 1px solid;
}
.borderRedColor {
border: 1px solid red;
}
.borderGreenColor {
border: 1px solid green;
}
.bigFont {
font-size: 50px;
}
</style>
</head>
<body>
<div id="app">
<!--
1 绑定class样式。写法::class='xxx',xxx可以是字符串、数组和对象。
字符串使用于类名不确定,需要动态获取的场景;
数组适用于要绑定的多个样式,个数不确定,类名也不确定的场景;
对象适用于要绑定的多个样式个数确定,类名也取确定的场景。
2 绑定style样式。写法::style='xxx',其中xxx可以是对象;
也可以是数组,如果是数组,数组中应该包含的是样式对象。
-->
<!-- 绑定class样式,:class字符串写法。适用于样式的类型不确定,需要动态指定。 -->
<div class="basic" :class="borderColor" @click="changeBorderColor">test</div> <br>
<!-- 绑定class样式,:class数组写法。适用于绑定的样式个数不确定,名字也不确定时。 -->
<div class="basic" :class="borderColorArr" @click="changeBorderColorAndFontSize">test</div>
<!-- 绑定class样式,:class对象写法。适用于绑定的样式个数确定,名字也确定时。 -->
<!-- 使用对象写法时,如果对象中属性的值为boolean类型,如果希望应用某个属性,就将给属性的值设为true。 -->
<div class="basic" :class="classObject" @click="change">test</div>
<!-- 绑定style样式对象写法。 -->
<!-- 当使用style对象写法时需要注意,样式font-size需要写为fontSize,
其他的包含两个单词的样式也需要进行同样的操作。 -->
<div class="basic" :style="styleObject">test</div>
<!-- 绑定style样式数组写法,数组中写样式的对象即可。style样式数组写法不经常使用。 -->
<div class="basic" :style="styleArray">test</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
borderColor: 'borderGreenColor',
borderColorArr: ['borderRedColor', 'bigFont'],
classObject: {
borderGreenColor: false,
bigFont: false
},
styleObject: {
fontSize: '50px'
},
styleArray: [
{
backgroundColor: 'red'
},
this.styleObject
]
},
methods: {
changeBorderColor() {
if (this.borderColor === 'borderGreenColor') {
this.borderColor = 'borderRedColor';
} else {
this.borderColor = 'borderGreenColor';
}
},
changeBorderColorAndFontSize() {
if (this.borderColorArr.indexOf('borderRedColor') !== -1) {
this.borderColorArr.shift();
} else {
this.borderColorArr.unshift('borderRedColor');
}
},
change() {
this.classObject.borderGreenColor = !this.classObject.borderGreenColor;
this.classObject.bigFont = !this.classObject.bigFont;
}
}
});
</script>
</body>
</html>
2.条件渲染v-if和v-show
<body>
<div id="app">
<!--
1 v-if。
写法:v-if="xxx" v-else-if="xxx" v-else="xxx"。
v-if适用于切换频率较低的场景,同时v-if会将不展示的元素从DOM中移除。
v-if可以和v-else-if、v-else一起使用时,但是要求元素的结构不能被打断。
2 v-show。
写法。v-show="xxx"。
v-show适用于元素是否显示切换频率较高的场景。
v-show不会将不展示的元素从DOM中移除,仅仅是样式隐藏。
3 v-if可能会导致元素无法获取到,因为在不展示时会将元素从DOM中移除。
而使用v-show在元素不展示时,依然可以获取到。
4 当某个条件下需要同时展示多个元素,可以使用template和v-if。
-->
<!-- v-show。
1 v-show可以控制元素是否显示。
2 v-show底层是通过设置 style='display: none',来使得元素不显示的。
所以对于不显示的元素依然会存在于DOM中。
3 v-show='xxx',xxx可以是一个boolean值true或者false;xxx也可以是一个表达式,这个表达式返回一个布尔值。
-->
<p v-show="false">v-show 1</p>
<p v-show="1 === 1">v-show 2</p>
<!-- v-if -->
<!-- v-if可以用来控制元素是否显示。如果v-if的值为false,则会在DOM中将该元素删除。 -->
<!-- 对于v-if,如果'm === 0'成立了,依然会走'm === 1'的判断逻辑。 -->
<p v-if="m === 0">0 - {{m}}</p>
<p v-if="m === 1">1 - {{m}}</p>
<button @click="m++">m+1</button>
<!-- v-if...v-else-if...v-else -->
<!--
1 v-if...v-else-if...v-else和JavaScript中的if...else的作用相同。
2 如果v-if="n === 0"成立,则不会走其他的v-else-if的逻辑判断。
3 如果要使用v-if...v-else-if...v-else,这几个逻辑判断的元素要连续才能生效。
-->
<p v-if="n === 0">0 - {{n}}</p>
<!-- 如果v-if...v-else-if...v-else使用的元素没有连续,则会导致vue产生错误。 -->
<!--<p>不连续</p>-->
<p v-else-if="n === 1">1 - {{n}}</p>
<p v-else-if="n === 2">2 - {{n}}</p>
<p v-else>其他 - {{n}}</p>
<button @click="n++">n+1</button>
<!-- 1 v-if和template配置使用,当在某个条件时需要同时展示多个元素,
可以将这多个元素都放在template中,让其同时展示或者同时隐藏。
2 template的使用不影响元素的结构,在vue实例化后会将template去掉。
3 template只能和v-if配合使用,不能和v-show配合使用。
-->
<template v-if="1 === 1">
<h3>hello</h3>
<h3>lisi</h3>
<h3>wangwu</h3>
</template>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
m: 0,
n: 0
}
});
</script>
</body>
3.列表渲染
<body>
<div id="app">
<!--
v-for用于遍历列表数据。语法v-for="(item,index) in xxx" :key="yyy"。:key,key需要唯一。
v-for用于遍历数组、对象、字符串和指定次数。
-->
<!-- 1 v-for遍历数组。 -->
<ul>
<li v-for="(person,index) in persons" :key="index">
{{person.name}} -- {{person.age}} -- {{index}}
</li>
</ul>
<ul>
<li v-for="(person,index) of persons" :key="index">
{{person.name}} -- {{person.age}} -- {{index}}
</li>
</ul>
<!-- 2 v-for遍历对象。 -->
<ul>
<li v-for="(value,key) in student" :key="key">
{{key}} --- {{value}}
</li>
</ul>
<!-- 3 v-for遍历字符串。 -->
<ul>
<li v-for="(char,index) of name" :key="index">
{{char}} --- {{index}}
</li>
</ul>
<!-- 4 v-for遍历指定的次数。 -->
<ul>
<li v-for="(number,index) of 5" :key="index">
{{number}} --- {{index}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
persons: [
{id: '001', name: 'tom', age: 10},
{id: '002', name: 'alice', age: 11},
{id: '003', name: 'lisi', age: 12},
],
student: {
id: 'A001',
name: 'zhangsan'
},
name: 'name',
}
})
</script>
</body>
4.v-for变量时key的作用
<body>
<div id="app">
<!--
react和vue中key的作用。
1 虚拟DOM中key的作用。
key是虚拟DOM的标识,当数据发生变化时,Vue会根据[新数据]生成[新的虚拟DOM],
随后Vue进行[新虚拟DOM]和[旧虚拟DOM]的差异比较,比较规则如下:
2 虚拟DOM替换时的比较规则。
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是没有问题的。
-->
<!-- 当指定key不合适时存在的问题。
1 指定key为遍历数据的索引。
2 每个li中添加input输入框。
3 输入框中添加内容。
4 点击按钮给数组头部添加数据。
5 之后会发现当前填充的数据发生了错乱。
-->
<ul>
<li v-for="(person,index) in persons" :key="index">
{{person.name}}
<input type="text">
</li>
</ul>
<button @click="add">添加</button>
<!-- 列表中key的正确使用,使用数据的唯一id作为key,即使进行逆序添加、逆序删除也不会有问题。 -->
<ul>
<li v-for="(person,index) in persons" :key="person.id">
{{person.name}}
<input type="text">
</li>
</ul>
<button @click="add">添加</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
persons: [
{id: '001', name: 'tom', age: 10},
{id: '002', name: 'alice', age: 11},
{id: '003', name: 'lisi', age: 12},
],
},
methods: {
add() {
let person = {id: '004', name: 'wangwu', age: 20};
this.persons.unshift(person);
}
}
});
</script>
</body>
5.列表过滤
<body>
<div id="app01">
<input type="text" v-model="keyword">
<ul>
<li v-for="person in usePersons">
{{person.name}}
</li>
</ul>
</div>
<div id="app02">
<input type="text" v-model="keyword">
<ul>
<li v-for="person in usePersons">
{{person.name}}
</li>
</ul>
</div>
<script>
// 列表过滤watch实现。
const vm01 = new Vue({
el: '#app01',
data: {
keyword: '',
persons: [
{id: '001', name: '马冬梅', sex: '女'},
{id: '002', name: '周冬雨', sex: '女'},
{id: '003', name: '周杰伦', sex: '男'},
{id: '004', name: '温兆伦', sex: '男'}
],
usePersons: []
},
watch: {
keyword: {
// 初始化时也执行handler。
immediate: true,
handler(newValue) {
// 数组过滤,将name包含newValue的数据过滤出来。
this.usePersons = this.persons.filter((person) => {
return person.name.indexOf(newValue) !== -1
})
}
}
}
});
// 通过计算属性实现列表过滤。
const vm02 = new Vue({
el: '#app02',
data: {
keyword: '',
persons: [
{id: '001', name: '马冬梅', sex: '女'},
{id: '002', name: '周冬雨', sex: '女'},
{id: '003', name: '周杰伦', sex: '男'},
{id: '004', name: '温兆伦', sex: '男'}
]
},
computed: {
// 计算属性,当get中所依赖的属性(keyword)发生变化时触发计算。
usePersons() {
return this.persons.filter((person) => {
return person.name.indexOf(this.keyword) !== -1
})
}
}
})
</script>
</body>
6.列表排序
<body>
<div id="app">
<input type="text" v-model="keyword">
<button @click="sortType = 2">升序</button>
<button @click="sortType = 1">降序</button>
<button @click="sortType = 0">原来的顺序</button>
<ul>
<li v-for="person in usePersons">
{{person.name}} --- {{person.age}}
</li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
sortType: 0,
keyword: '',
persons: [
{id: '001', name: '马冬梅', sex: '女', age: 30},
{id: '002', name: '周冬雨', sex: '女', age: 31},
{id: '003', name: '周杰伦', sex: '男', age: 22},
{id: '004', name: '温兆伦', sex: '男', age: 23}
]
},
computed: {
// 计算属性,当get中所依赖的属性(keyword)发生变化时触发计算。
usePersons() {
// 根据关键字过滤,数组过滤不会改变原来的数字,而是返回一个新数组。
const arr = this.persons.filter((person) => {
return person.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>
</body>
7.更新列表数据存在的问题
<body>
<div id="app01">
<button @click="update">更新马冬梅数据</button>
<ul>
<li v-for="person in persons">
{{person.name}} --- {{person.age}} --- {{person.sex}}
</li>
</ul>
</div>
<div id="app02">
<button @click="update">更新马冬梅数据</button>
<ul>
<li v-for="person in persons">
{{person.name}} --- {{person.age}} --- {{person.sex}}
</li>
</ul>
</div>
<script>
const vm01 = new Vue({
el: '#app01',
data: {
persons: [
{id: '001', name: '马冬梅', sex: '女'},
{id: '002', name: '周冬雨', sex: '女'},
{id: '003', name: '周杰伦', sex: '男'},
{id: '004', name: '温兆伦', sex: '男'}
],
},
methods: {
update() {
// 数据正常更新
this.persons[0].name = '马老师';
this.persons[0].sex = '男';
}
}
});
const vm02 = new Vue({
el: '#app02',
data: {
persons: [
{id: '001', name: '马冬梅', sex: '女'},
{id: '002', name: '周冬雨', sex: '女'},
{id: '003', name: '周杰伦', sex: '男'},
{id: '004', name: '温兆伦', sex: '男'}
],
},
methods: {
update() {
// 使用这样方式更新,就会出现数据更新的问题。
// 在浏览器窗口中输出vm02.persons[0],发现数据正常更新,
// 但是Vue中数据是没有更新的,导致界面的数据依然没有更新。
// 原因是直接进行数组的修改无法被Vue检测到。
// 如果对数组的修改需要被Vue检测到,可以使用数组中的相关方法进行修改。
this.persons[0] = {id: '001', name: '马老师', sex: '男'};
}
}
});
</script>
</body>
8.模拟vue对象数据监视
let data = {
name: 'tom',
age: 20
};
// 1 创建监视器对象,用于监视data数据的变化,相当与vue中对Vue实例中_data封装一样。
const obs = new Observser(data);
console.log(obs);
// 2 将监视对象赋值给_data。
const vm = {};
vm._data = data = obs;
// vue中对象的监视
function Observser(val) {
// 获取对象中所有的属性名。
const keys = Object.keys(val);
// 遍历数据中所有的属性,然后通过Object.defineProperty(),将这些属性添加到this中,
// 这里的this为Observser对象的实例,并且在Observser对象中为这些属性提供get和set,
// set就可以用于属性变化时的监控。
keys.forEach(k => {
Object.defineProperty(this, k, {
// get返回真实数据的值。
get() {
return val[k];
},
// set去修改真实数据的值。
set(newValue) {
console.log('当数据发生变化修改真实DOM中的值。');
val[k] = newValue;
}
});
})
}
9.Vue.set()
<body>
<div id="app">
<button @click="add01">给学生添加性别属性</button>
<br>
<p>姓名 {{student.name}}</p>
<p>性别 {{student.sex}}</p>
<button @click="add02">给学生添加性别属性</button>
<br>
<p>姓名 {{student.name}}</p>
<p>性别 {{student.sex}}</p>
<p>年龄 {{student.age}}</p>
<p>地址 {{student.address}}</p>
<p>手机号 {{student.phone}}</p>
<button @click="add03">添加属性</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
student: {
name: 'tom'
}
},
methods: {
// 直接给data中的student添加sex属性是不可取的,因为在data中没有给sex添加get和set方法,
// 所以就会导致添加的属性不能够读取或者访问。
add01() {
this.student.sex = '男';
},
// 通过Vue.set()添加的属性会在data内生成get和set,即所添加的属性是响应式的。
// Vue.set(target, key, value)方法的参数。target给哪个对象添加属性;key属性的key;value属性的值。
add02() {
Vue.set(this.student, 'sex', '男');
// 其他几种添加属性的方式。
// this.student和this._data.student是相同的,所以给this._data.student添加属性和给this.student添加效果是一样的。
Vue.set(this._data.student, 'age', 10);
// 调用Vue实例对象的$set()和调用Vue.set()效果是一样的。
this.$set(this.student, 'address', 'bj');
this.$set(this._data.student, 'phone', '123456');
},
add03(){
// 不能将添加的属性直接保存到Vue实例中,或者保存到data中,会报错,因为Vue无法探测到新增属性。
// 可以将添加的属性保存到Vue实例中的对象中(如student),
// 或者保存到data中的对象中(如data中的student。)。
Vue.set(this, 'name', 'alice');
this.$set(this._data, 'age', 10);
}
}
})
</script>
</body>
10.Vue中数组数据更新原理
<body>
<div id="app">
<ul>
<li v-for="(student,index) in students" :key="index">
{{student}}
</li>
</ul>
<button @click="update01">修改数据</button>
<button @click="update02">修改数据</button>
<button @click="update03">修改数据</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
students: ['tom', 'alice']
},
methods: {
// 1 直接修改数组数据存在的问题。
// 1 数组中的数据,Vue没有提供对应的get和set。
// 2 由于Vue没有给数组中的数据提供get和set,所以Vue就无法检测到数组数据被修改了,
// 导致即使数组数据修改了,但是页面依然显示之前的数据。
update01() {
this.students[0] = '张三';
},
// 2 如果使用数组中的方法修改数据就可以被Vue检测到。
// 1 Vue中的数组中已经不是JavaScript中原生的数组对象了,而是包装后的数组对象。
// 包装后的数组对象重写7个对数组有破坏的方法,重写之后的执行逻辑可以分为两步,
// 以splice为例,会先调用原生数组中的splice方法完成对数据的更新,然后重新渲染DOM。
// 2 包装后的数组重写的7个方法。push() pop() shift() unshift() splice() sort() reverse()
update02() {
console.log(this.students.splice === Array.prototype.splice); // false
this.students.splice(0, 1, '李四');
},
update03() {
// 除了使用方法修改数组可以被Vue检测到,也可以使用Vue.set()来完成对数组值的修改。
Vue.set(this.students, 0, '王五');
}
}
})
</script>
</body>
11.Vue数据监视总结
<body>
<div id="app">
<!--
Vue监视数据原理。
1 Vue会监视data中所有层次的数据。
2 Vue监视对象中的数据。
Vue通过set实现对象中数据的检查,并且要在new Vue()时传入要检测的数据。
对象中后添加的数据Vue默认不进行响应式处理。
如果需要给后添加的数据做响应式处理,需要通过两个API:
Vue.set(target, propertyName/index, value) 或者
vm.$set(target, propertyName/index, value)
3 Vue检测数组中的数据。
通过包装更新数组的元素的方法实现,本质就是做了两件事情:
1 调用原生数组方法对数组进行更新。
2 重新解析模板,进行页面更新。
4 在Vue中对数组进行修改时,需要使用如下方法:
1 可以使用的API。push() pop() shift() unshift() splice() sort() reverse()
2 Vue.set()、vm.$set()
5 Vue.set()和vm.$set()不能给vm或者vm的根数据直接添加属性。
-->
<h1>学生信息</h1>
<button @click="student.age++">年龄+1</button>
<button @click="addSex">添加性别属性,默认值 男</button>
<button @click="student.sex = '女'">修改性别</button>
<button @click="addFriend">在列表头部添加朋友</button>
<button @click="updateFirstFriendName">修改第一个朋友的名字为 张三</button>
<button @click="addHobby">添加一个爱好</button>
<button @click="updateFirstHobby">修改第一个爱好为 开车</button>
<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>
const vm = new Vue({
el: '#app',
data: {
student: {
name: 'tom',
age: 10,
hobby: ['学习', '游戏'],
friends: [
{name: '李四', age: 21},
{name: '王五', age: 22},
]
}
},
methods: {
addSex() {
// Vue.set(this.student, 'sex', '男');
this.$set(this.student, 'sex', '男')
},
addFriend() {
this.student.friends.unshift({name: 'alice', age: 26});
},
updateFirstFriendName() {
this.student.friends[0].name = '张三';
},
addHobby() {
this.student.hobby.push('篮球');
},
updateFirstHobby() {
this.student.hobby.splice(0, 1, '开车');
}
}
})
</script>
</body>