一、列表渲染
1. v-for 指令:用于循环。
- 用于展示列表数据。
- 语法:
v-for="(item, index) in xxx" :key="yyy"
。- 可遍历:数组、对象、字符串(用得少)、指定次数(用得少)。
遍历数组
- p.id:遍历出所有人员的 id
- index:索引。
<div id="root"> <h3>人员列表(遍历数组)</h3> <ul> <li v-for="(p, index) in persons" :key="index">{{p.id}} - {{p.name}} - {{p.age}}</li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { persons: [ {id: 1, name: '张三', age: 18}, {id: 2, name: '李四', age: 19}, {id: 3, name: '王五', age: 20} ] } } })
遍历对象:注意遍历时的写法 (value, k) in xxx
- value:值。
- k:键。
<div id="root"> <h3>汽车信息(遍历对象)</h3> <ul> <li v-for="(value, k) in car" :key="k">{{k}} - {{value}}</li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { car:{ name: 'BMW', price: '60w', color: 'skyblue' } } } })
遍历字符串(很少用)
- char:每个字符。
- index:索引。
<div id="root"> <h3>字符串信息(遍历字符串)</h3> <ul> <li v-for="(char, index) in str" :key="index">{{index}} - {{char}}</li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { str: 'hello' } } })
遍历指定次数(很少用)
- number:每一个数字。
- index:索引。
<div id="root"> <h3>遍历指定次数(很少用)</h3> <ul> <li v-for="(number, index) in 4" :key="index">{{index}} - {{number}}</li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { } } })
2. key 的原理
- 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 是没有问题的。
实例:通过 id 唯一标识符,添加成员
<div id="root"> <h3>人员列表(遍历数组)</h3> <ul> <button @click.once="add">添加老刘</button> <li v-for="(p, index) in persons" :key="p.id"> {{p.name}}--{{p.age}} <input type="text" :value=`${p.name}——${p.age}`> </li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { persons:[ {id: 1, name: '张三', age: 20}, {id: 2, name: '李四', age: 21}, {id: 3, name: '王五', age: 22}, ] } }, methods:{ add(){ const p = {id: 4, name: '老刘', age: 83} this.persons.unshift(p) } } })
3. 列表过滤
实例:输入框输入信息,过滤出有其中关键字的人员信息。(计算属性实现)
- 在计算属性中 filPersons 是一个函数,返回出来过滤的结果。
<div id="root"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <ul> <li v-for="(p, index) in filPersons" :key="index"> {{p.name}}--{{p.age}}--{{p.sex}} </li> </ul> </div> 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 => p.name.includes(this.keyWord.trim())) }) } } })
(监听方法实现)
- 在监听方法中 filPersons 是一个数组,把过滤出来的信息放到数组里。
<div id="root"> <h3>人员列表(遍历数组)</h3> <input type="text" placeholder="请输入名字" v-model="keyWord"> <ul> <li v-for="(p, index) in filPersons"> {{p.name}} - {{p.age}} - {{p.sex}} </li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { 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 => p.name.includes(val.trim())) } } } })
初始状态:
查询:
4. 列表排序
对成员按年龄升序、年龄降序和原顺序排序。
- 先把过滤出来的信息放进数组。
- 利用三元表达式,对数组进行按需排序。
- 注意:添加点击事件的条件 'sortType = n' 不是
===
<div id="root"> <h3>人员列表</h3> <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) in filPersons" :key="p.id"> {{p.name}} - {{p.age}} - {{p.sex}} <input type="text"> </li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { keyWord: '', sortType: 0, persons:[ {id:'001',name:'马冬梅',age:18,sex:'女'}, {id:'002',name:'周冬雨',age:25,sex:'女'}, {id:'003',name:'周杰伦',age:45,sex:'男'}, {id:'004',name:'温兆伦',age:21,sex:'男'}, ], } }, computed:{ filPersons(){ const arr = this.persons.filter(p => p.name.includes(this.keyWord)) if(this.sortType){ arr.sort((p1, p2) => { return this.sortType === 2 ? p1.age - p2.age : p2.age - p1.age }) } return arr } } })
年龄升序:
年龄降序:
原顺序:
注意:如果 :key='index',即以索引为唯一值的话,就会出现以下情况。(所对应文本框内容不会跟随着前面顺序的变化而变化)
5. 列表更新(两种方法)
方法一:一个个改进来。
注意:如果用以下写法写,不奏效
this.person[0] = {id:'001', name:'马老师', age:50,sex:'男'}
<div id="root"> <h3>人员列表</h3> <input type="text" placeholder="请输入名字" v-model="keyWord"> <button @click="updatedMei">更新马冬梅信息</button> <ul> <li v-for="(p, index) in filPersons" :key="index"> {{p.name}} - {{p.age}} - {{p.sex}} </li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { keyWord: '', sortType: 0, persons:[ {id:'001',name:'马冬梅',age:18,sex:'女'}, {id:'002',name:'周冬雨',age:25,sex:'女'}, {id:'003',name:'周杰伦',age:45,sex:'男'}, {id:'004',name:'温兆伦',age:21,sex:'男'}, ], } }, methods:{ updatedMei(){ this.persons[0].name = '马老师', this.persons[0].age = 50, this.persons[0].sex = '男' } }, computed:{ filPersons(){ return this.persons.filter(p => p.name.includes(this.keyWord)) } } })
点击更新马冬梅信息
方法二:使用 splice() 方法,删除添加。
updateMei(){ this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'}) }
6. Vue.set 的使用
Vue.set():向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi' )
Vue.set( target, propertyName/index, value )
- target:要更改的数据源(可以是对象或者数组)
- key:要更改的具体数据
- value :重新赋的值
- 注意:对象不能是 Vue 实例,或者 Vue 实例的根数据对象。
<div id="root"> <h1>学生信息</h1> <button @click="addSex">添加一个性别属性,默认值男</button> <h2>姓名:{{student.name}}</h2> <h2 v-if="student.sex">性别:{{student.sex}}</h2> <h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2> </div> const vm = new Vue({ el: '#root', data: function () { return { student: { name: 'tom', age: { rAge: 40, sAge: 29, }, } } }, methods: { addSex() { Vue.set(this.student,'sex','男') // this.$set(this.student, 'sex', '男') } } })
7. 总结 vue 监听数据的原理
- vue会监视 data 中所有层次的数据。
- 如何检测对象中的数据?
- 通过 setter 实现监视,且要在 new Vue 时就传入要检测的数据。
对象中后追加的属性,Vue默认不做响应式处理。
如需给后添加的属性做响应式,请使用如下API:
(1)Vue.set(target.propertyName/index.value) 或
(2)vm.$set(target.propertyName/index.value)
- 如何检测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新。
- 重新解析模板,进而更新页面。
在 Vue 修改数组中的某些元素一定要用如下方法:
使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set 不能给vm 或 vm的根数据对象 添加属性!!
8. 总结练习
<div id="root"> <h3>学生信息</h3><br/> <button @click="student.age++">年龄+1岁</button><br/> <button @click="addSex">添加性别属性,默认值:男</button><br/> <button @click="changeSex">修改性别</button><br/> <button @click.once="addStudent">在列表首位添加一个名字</button><br/> <button @click="updateFirstName">修改第一个朋友的名字为:张三</button><br/> <button @click.once="addHobby">添加一个爱好</button><br/> <button @click="updateFirstHobby">修改第一个爱好为:开车</button><br/> <button @click.once="deleteFirstHobby">过滤掉爱好中的抽烟</button><br/> <h3>姓名:{{student.name}}</h3> <h3>年龄:{{student.age}}</h3> <h3 v-if="student.sex">性别:{{student.sex}}</h3> <h3>朋友们</h3> <ul> <li v-for="(friend) of student.friends" :key="friend.name"> {{friend.name}} - {{friend.age}} </li> </ul> <h3>爱好</h3> <ul> <li v-for="(hobby) of student.hobbies" :key="hobby"> {{hobby}} </li> </ul> </div> const vm = new Vue({ el: '#root', data: function () { return { student:{ name: 'tom', age: 20, sex: '', friends: [ {name: 'jerry', age: 18}, {name: 'tony', age: 20} ], hobbies: ['抽烟','喝酒','烫头'] } } }, methods: { addSex(){ Vue.set(this.student,'sex','男') }, changeSex(){ this.student.sex = '未知' }, addStudent(){ this.student.friends.unshift({name: 'mike', age: 22}) }, updateFirstName(){ this.student.friends.splice(0, 1 , {name: '张三', age: 18}) }, addHobby(){ this.student.hobbies.push('学习') }, updateFirstHobby(){ this.student.hobbies.splice(0, 1, '开车') }, deleteFirstHobby(){ this.student.hobbies.shift() } } })
初始页面
点击所有按钮后页面:
二、收集表单数据
- <input type="text">
v-model 收集的是 value 值,用户输入的就是 value 值。
<input type="radio">
v-model 收集的是 value 值,且要给标签配置 value 值。
<input type="checkbox">
没有配置 input 的 value 属性,那么收集的就是 checked (勾选 or 未勾选,是布尔值)
配置input的value属性:
(1)v-model 的初始值是非数组,那么收集的就是 chenked (勾选 or 未勾选,是布尔值)
(2)v-model 的初始值是数组,那么收集的就是 value 组成数组。
- 备注:v-model的三个修饰符:
- lazy:失去焦点再收集数据。
- number:输入字符串转为有效的数字。
- trim:输入首尾空格过滤。
实例:把表单的数据收集起来,并且以 JSON 的形式打印输出。
<div id="root"> <form @submit.prevent="demo"> 账号:<input type="text" v-model.trim="userInfo.account"><br /><br /> 密码:<input type="text" 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="学习"> 打游戏<input type="checkbox" v-model="userInfo.hobby" value="打游戏"> 吃饭<input type="checkbox" v-model="userInfo.hobby" value="吃饭"> <br /><br /> 所属校区 <select v-model="userInfo.city"> <option value="">请选择校区</option> <option value="北京">北京</option> <option value="上海">上海</option> <option value="深圳">深圳</option> <option value="青岛">青岛</option> </select><br /><br /> 其他信息: <textarea v-model.lazy="userInfo.other"></textarea><br /><br /> <input type="checkbox" v-model="userInfo.agree"> 阅读并接受 <a href="javascript:;">《用户协议》</a> <button>提交</button> </form> </div> const vm = new Vue({ el: '#root', data: { userInfo: { account: '', password: '', age: '', sex: 'female', hobby: [], city: '北京', other: '', agree: '' } }, methods: { demo() { console.log(JSON.stringify(this.userInfo)) } } })
积跬步至千里,积小流成江海