第三节:Vue3向下兼容2(v-for、数组方法、v-model、计算属性、监听器)
一. 基本指令
1. v-for
数据准备
data() {
return {
userInfo: { name: 'ypf', age: 18, school: '北大' },
movies: ["星际穿越", "盗梦空间", "大话西游", "教父", ],
userList: [
{ name: 'ypf1', age: 18, school: '北大1' },
{ name: 'ypf2', age: 19, school: '北大2' },
{ name: 'ypf3', age: 20, school: '北大3' }
]
}
}
(1). 遍历对象
格式:
<h4>1.遍历对象</h4> <ul> <li v-for="(value,key,index) in userInfo" :key='index'> {{index}}-{{key}}--{{value}} </li> </ul>
(2). 遍历数字
格式: item in 数字
<h4>2.遍历数字</h4> <ul> <li v-for="value in 5"> {{value}} </li> </ul>
(3). 遍历数组【重点】
格式:item in 数组
(item, index) in 数组
A. 普通数组
<h5>3.1 普通数组</h5> <ul> <li v-for="(item,index) in movies" :key='index'>{{index}}-{{item}}</li> </ul>
B. 数组对象
<h5>3.2 数组对象</h5> <ul> <li v-for="(item,index) in userList" :key='index'> {{index}}-{{item.name}}-{{item.age}}-{{item.school}} </li> </ul>
补充:
我们发现,每个v-for,我们都赋值了一个key属性,key属性里的内容建议用唯一值,这里涉及到diff算法,提高效率,后面章节重点剖析。
2. 数组方法
(1).变异方法:
背景:在 Vue 中,直接修改对象属性的值无法触发响应式。当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变。所以:Vue中引入变异数组方法,即保持数组方法原有功能不变的前提下对其进行功能拓展,使其支持响应式。
常用方法:
a. push: 往数组最后面添加一个元素,成功返回当前数组的长度。
b. pop: 删除数组的最后一个元素,成功返回删除元素的值
c. shift: 删除数组的第一个元素,成功返回删除元素的值
d. unshift: 往数组最前面添加一个元素,成功返回当前数组的长度
e. splice: 删除,有三个参数,第一个要删除元素的下标(必选),第二个要删除元素的个数(必选),第三个删除后想在原位置替换的值
f. sort: 将数组按照字符编码从小到大排序,成功返回排序后的数组
g. reverse: 将数组倒序排列,并返回倒叙后的数组
(2).替换数组
含义: 不会影响原始的数组数据,而是形成一个新的数组.
常用方法:
a. filter: 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
b. concat: 用于连接两个或多个数组,该方法不会改变现有的数组。
c. slice: 从已有的数组中返回选定的元素。该方法并不会修改数组,而是返回一个子数组
eg:从第0个开始,获取两个元素 this.list = this.list.slice(0, 2);
案例参考:
https://www.cnblogs.com/yaopengfei/p/12315704.html 底部的图书管理代码。
二. v-model
1. 基本使用和原理
v-model指令可以在表单 input、textarea以及select元素上创建双向数据绑定;它会根据控件类型自动选取正确的方法来更新元素;尽管有些神奇,但 v-model 本质上不过是语法糖,它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理;
<body> <div id="app"></div> <template id="myApp"> <div> <input v-model="msg" /> <h5>等价于</h5> <!-- 等价于 --> <input :value="msg" @input="change1"> </div> </template> <script src="../js/vue3.js" ></script> <script> Vue.createApp({ template: '#myApp', data() { return { msg: 'Hello Vue3!' } }, methods:{ change1(event){ this.msg=event.target.value; } } }).mount('#app'); </script> </body>
剖析本质:v-model的原理其实是背后有两个操作:
(1). v-bind绑定value属性的值;
(2). v-on绑定input事件监听到函数中,函数会获取最新的值赋值到绑定的属性中;
2. 绑定常用标签
v-model还可以绑定:textarea、checkbox、radio、select。
代码分享:
<body> <div id="app"></div> <template id="myApp"> <!-- 1.绑定textarea --> <label for="intro"> 自我介绍 <textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea> </label> <h2>intro: {{intro}}</h2> <!-- 2.checkbox --> <!-- 2.1.单选框 --> <label for="agree"> <input id="agree" type="checkbox" v-model="isAgree"> 同意协议 </label> <h2>isAgree: {{isAgree}}</h2> <!-- 2.2.多选框 --> <span>你的爱好: </span> <label for="basketball"> <input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球 </label> <label for="football"> <input id="football" type="checkbox" v-model="hobbies" value="football"> 足球 </label> <label for="tennis"> <input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网 球 </label> <h2>hobbies: {{hobbies}}</h2> <!-- 3.radio --> <span>你的爱好: </span> <label for="male"> <input id="male" type="radio" v-model="gender" value="male">男 </label> <label for="female"> <input id="female" type="radio" v-model="gender" value="female">女 </label> <h2>gender: {{gender}}</h2> <!-- 4.select --> <span>喜欢的水果: </span> <select v-model="fruit" multiple size="2"> <option value="apple">苹果</option> <option value="orange">橘子</option> <option value="banana">香蕉</option> </select> <h2>fruit: {{fruit}}</h2> </template> <script src="../js/vue3.js"></script> <script> Vue.createApp({ template: '#myApp', data() { return { intro: "Hello World", isAgree: false, hobbies: ["basketball", "football"], gender: "", fruit: "orange" } } }).mount('#app'); </script> </body>
3. 修饰符
lazy修饰符的作用:
默认情况下,v-model在进行双向绑定时,绑定的是input事件,那么会在每次内容输入后就将最新的值和绑定的属性进行同步;
如果我们在v-model后跟上lazy修饰符,那么会将绑定的事件切换为 change 事件,只有在提交时(比如回车)才会触发;
number修饰符
代码分享:
<body> <div id="app"></div> <template id="myApp"> <!-- 1.lazy修饰符 敲击回车才有效--> <input type="text" v-model.lazy="message"> <br> <!-- 2.number修饰符 --> <input type="text" v-model.number="message"> <h2>{{message}}</h2> <button @click="showType">查看类型</button> <!-- 3.trim修饰符 --> <input type="text" v-model.trim="message"> <button @click="showResult">查看结果</button> </template> <script src="../js/vue3.js"></script> <script> Vue.createApp({ template: '#myApp', data() { return { message: "Hello World" } }, methods: { showType() { console.log(this.message, typeof this.message); }, showResult() { console.log(this.message); } } }).mount('#app'); </script> </body>
三. 计算属性computed
1. 什么时候用计算属性?
对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性;
计算属性看起来像是一个函数,但是我们在使用的时候不需要加(),这个后面讲setter和getter时会讲到;
2. 基本用法
<body> <div id="app"></div> <template id="myApp"> <div> {{fullName}} </div> <div> {{finalResult}} </div> </template> <script src="../js/vue3.js"></script> <script> Vue.createApp({ template: '#myApp', data() { return { firstName: 'BBB', lastName: 'AAA', message: "Hello Vue3" } }, computed: { fullName() { return this.lastName + ' ' + this.firstName; }, finalResult() { return this.message.split(' ').reverse().join(' '); } } }).mount('#app'); </script> </body>
3. get 和 set方法
计算属性在大多数情况下,只需要一个get方法即可,所以我们会将计算属性直接写成一个函数。
但是,如果我们确实想设置计算属性的值呢?这个时候我们也可以给计算属性设置一个set的方法
<body> <div id="app"></div> <template id="myApp"> <div> {{fullName1}} </div> <div> {{fullName2}} </div> </template> <script src="../js/vue3.js"></script> <script> Vue.createApp({ template: '#myApp', data() { return { firstName: 'BBB', lastName: 'AAA', } }, computed: { // 相当于get方法 fullName1() { return this.lastName + ' ' + this.firstName; }, fullName2: { get() { return this.lastName + ' ' + this.firstName; }, set(newValue) { this.firstName = newValue + this.firstName; } } } }).mount('#app'); </script> </body>
4. 计算属性的缓存
计算属性computed是有缓存的,计算属性会基于它们的依赖关系进行缓存;
在数据不发生变化时,计算属性是不需要重新计算的;但是如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算;
PS. 与method的本质区别:method每次调用都需要重新计算,没有缓存。
代码分享:
<body> <div id="app"></div> <template id="myApp"> <!-- 1. 计算属性调用--> <div>{{getFullName1()}}</div> <div>{{getFullName1()}}</div> <div>{{getFullName1()}}</div> <!-- 2. 方法调用 --> <div>{{getFullName2}}</div> <div>{{getFullName2}}</div> <div>{{getFullName2}}</div> <!-- 3.修改数据内容 --> <input type="text" v-model='firstName' /> </template> <script src="../js/vue3.js"></script> <script> Vue.createApp({ template: '#myApp', data() { return { firstName: 'BBB', lastName: 'AAA' } }, methods: { getFullName1() { console.log('我是方法调用'); return this.lastName + ' ' + this.firstName; } }, computed:{ getFullName2(){ console.log('我是计算属性调用'); return this.lastName + ' ' + this.firstName; } } }).mount('#app'); </script> </body>
运行结果:
计算属性只调用了1次,方法调用了三次。
修改数据值,计算属性再次被调用了1次,方法调用了3次。
5. 扩展get和set的源码
详见章节:https://www.cnblogs.com/yaopengfei/p/15251328.html
四. 监听器
1. 什么是监听器(侦听器)?
PS: 可以监听data和computed中的数据。
2. 普通用法
<body> <div id="app"></div> <template id="myApp"> <h3>{{userInfo1.userName}}</h3> <div><button @click="Edit1">修改对象</button></div> <div><button @click="Edit2">修改userInfo1.userName</button></div> </template> <script src="../js/vue3.js"></script> <script> Vue.createApp({ template: '#myApp', data() { return { userInfo1: { userName: 'test1', age: 20, }, } }, watch: { // 普通监听 // 默认情况下我们的侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听) userInfo1(newValue, oldValue) { console.log(oldValue, newValue); }, }, methods: { Edit1() { this.userInfo1 = { userName: 'test2' }; }, Edit2() { this.userInfo1.userName = 'test2'; } }).mount('#app'); </script> </body>
剖析:
Edit1方法中直接修改了对象,则普通用法中能监听到,但像Edit2中的修改对象中的内容,是监听不到的!!!
3. 深度监听 和 立即执行
(1). 深度监听
采用另一种写法,并且需要配置 deep: true ,就可以监听到对象内任何一个属性的修改了。
<body> <div id="app"></div> <template id="myApp"> <h3>{{userInfo2.userName}}</h3> <div><button @click="Edit3">修改对象</button></div> <div><button @click="Edit4">修改userInfo2.userName</button></div> <div><button @click="Edit5">修改userInfo2.childInfo.cName</button></div> </template> <script src="../js/vue3.js"></script> <script> Vue.createApp({ template: '#myApp', data() { return { userInfo2: { userName: 'test1', age: 20, childInfo: { cName: 'ypf' } }, } }, watch: {// 深度监听 // 内部发生的改变也侦听到 // 发现一问题:新对象和旧对象中的userName值是相同的,这是因为引用类型赋值的问题,都是指向同一个内存地址(详见后面的知识补充) userInfo2: { handler(newValue, oldValue) { console.log(oldValue, newValue); }, deep: true }, }, methods: { Edit3() { this.userInfo2 = { userName: 'test2' }; }, Edit4() { this.userInfo2.userName = 'test2'; }, Edit5() { this.userInfo2.childInfo.cName = 'lmr'; } } }).mount('#app'); </script> </body>
剖析:
无论是修改 userInfo2.userName 还是 userInfo2.childInfo.cName ,都可以监听到。
发现一问题:
新对象和旧对象中的userName值是相同的,这是因为引用类型赋值的问题,都是指向同一个内存地址(详见后面的知识补充)
(2). 立即执行
通过设置参数immediate: true,即不发生变化前,默认执行一次。
4. 其它写法
(1). 直接监听对象中某个属性值
watch: {
// 特殊的监听方式
// 直接监听userName的值
'userInfo2.userName'(newValue, oldValue) {
console.log(oldValue, newValue);
}
},
(2). 在created生命周期中进行监听
!
- 作 者 : Yaopengfei(姚鹏飞)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 声 明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
- 声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。