近况
最近把vue网课快看完了,还差最后的实训项目部分,总之所有vue所有知识点都学完了,
想着先不着急跟着敲项目,把之前学习的内容都整理整理,写几篇vue博客再去学写项目,
本篇就是关于vue学习的第一篇,vue的基础指令,看看都有哪些内容吧。
基础介绍
vue是一个响应式的JS框架,要说什么是响应式,我还是先说说一个叫mvvm的概念,
mvvm指的是Model,View,ViewModel,前端把项目分成这三块,Model指的是数据,前端从后端获取的数据
View指的是Dom元素,就是用户看到的网页,Html标签,ViewModel你可以理解为一个助手,或者说是vue
它的作用是将Model数据及时刷新到Dom元素中,让页面展示数据,或者当用户点击了按钮,对页面发生了改变
ViewModel可以及时的将数据保存起来,总之,ViewModel在View和Model中起到中间桥梁的作用。
vue就是实现了mvvm架构的框架,vue将数据传递到页面,让页面及时刷新数据,vue也可以根据页面的更改,
动态更新数据,vue就是做了ViewModel的事情,而响应式说的就是,当数据发生改变时,页面能立即发生变化。
还有一个双向绑定的概念,其实上面已经说了,当页面发生改变,或者数据发生改变,ViewModel都能动态
更新双方,这就是双向绑定,你可能会好奇,既然这就是双向绑定,那有没有单向绑定的概念呢?不急接着看下去
说完mvvm,响应式的概念,你可能还是不太理解具体内容,下面举个栗子。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="vue.js"></script> //页面中直接导入vue依赖
<title>vue</title>
</head>
<body>
<div id="app">
<span>{{message}}</span> //vue将data中数据渲染到页面中,{{}}操作就是从data中获取数据
</div>
<script>
let app = new Vue({
el: '#app', //el绑定id为app的div标签
data: {
message:'加油!'
},
});
</script>
</body>
</html>
上面示例中,引入vue的依赖,打开浏览器就能看到message的信息,View指的就是body中标签元素,整个页面
Model指的是data中的数据,当然不止data,只要是数据都属于Model,ViewModel指的是Vue实例,它内部做
的就是将data中数据及时响应到Dom中,每当数据发生变化,都会及时更新Dom,这就是响应式。
插值操作
插值操作说的是,vue通过什么指令将数据响应到Dom元素中,看本文目录还会讲绑定属性,绑定属性和插值操作
不同的是:插值操作指将数据插入到DOM元素之中,可以展示给用户看,而绑定属性则是将数据插入到DOM元素
的属性中。下面关于插值操作的几个指令:
Mustache语法
mustache语法指的是标签中的{{}},在Dom元素标签内使用双括号,就能取到vue实例的data中的数据。 例如:<span>{{message}}</span>,当然你还可以写简单的表达式:{{message+"万一"}},这里就当是在JS中写语句。
v-once指令
表示该DOM只会更改一次,之后并不会随着data数据改变而改变DOM中的数据。
如果没有该指令,则只要data中数据改变后,对应的DOM会响应该数据,从而DOM中数据发生改变。
使用方式:<span v-once>{{message}}</span>
,该数据只会渲染一次。v-html指令
解析数据中的标签属性,如果data对象中的变量值本身就是一个标签,则使用该指令可以对其进行解析
使用如下:<span v-html="message"></span> //dom页面,将在span标签中生成a标签,点击跳转页面 data: { //vue实例中data对象 message:'<a href="http://www.keyi.world">个人网站</a>' }
v-text指令
与Mustache语法类似,将数据插入到DOM中,相对而言,没有Mustache语法好
使用如下:<span v-text="message"></span> data: { message:'你好啊' } //该指令和mustache作用一样,不过双括号的方式更加简单易懂。
v-pre指令
表示该DOM不进行DOM数据绑定,将该DOM中的数据原封不动的展示出来
使用:<span v-pre>{{message}}</span>
,将会在页面显示{{message}}
,说白了,不对双括号进行解析。v-cloak指令
解决插值闪烁问题,插值闪烁指的是当js代码还未被加载时,页面显示,当vue.js加载后,
才会显示实际数据,就会存在这种画面:{{message}}
,过了一段时间变成你好啊
,这就是插值闪烁。
而当DOM标签使用v-cloak标签后,配合css的使用,即可解决插值闪烁。案例如下:<style> [v-cloak] { display: none; } </style> <div id="app"> <span v-cloak>{{message}}</span> //vue.js还没被加载时,存在该属性,加载后,vue删除该属性 </div> <script> setTimeout(function () { //模拟延迟加载vue.js let app = new Vue({ el: '#app', //绑定id为app的div标签,该div内归vue管理 data: { message:'你好啊' }, }); },1000) </script> /* 当js代码未被加载时,DOM标签因为存在v-cloak属性,被css限制展示,隐藏起来了 当js代码加载后,vue会删除v-cloak属性,使得DOM标签再次被展示,同时数据也被渲染出来。 不过听说这个指令已经不用了,都用的是虚拟DOM,继续学习吧. */
绑定属性
插值操作是把数据绑定到文本中,用于展示数据,而绑定属性是将数据绑定到DOM标签的属性中
绑定属性就一个重要指令:v-bind
v-bind的基本使用
<img v-bind:src="imgUrl" alt=""> data: { imgUrl:'http://图片地址' } //v-bind还可以使用简写方式,即去掉v-bind,只保留:属性方式,就是从data中获取数据,是个语法糖 <img :src="imgUrl" alt="">
v-bind绑定class属性
v-bind绑定属性有对象语法和数组语法两种对象语法
使用对象语法则class属性值是一个对象,对象的属性表示class名称,对象的属性值是一个boolean值
当boolean值为true时,则BOM标签的class值就会应用对象的对应属性名,否则不应用,下面是简单的案例:<style> .a{ color: red; } .b{ color: aqua; } </style> <h2 :class="{a:isA,b:isB}">{{message}}</h2> data: { message : '你好,李银河', isA: true, isB: false } //isB为false,则h2标签的class属性只有a样式生效,<h2 class="a">{{message}}</h2>
你还可以添加一个固定的class值,这个值经过vue解析后,会和动态的class值进行合并,
编译后,class属性值为title,a,b(如果a,b都为true的话),例如下面这样:<h2 class="title" :class="{a:isA,b:isB}">{{message}}</h2>
另外,值得注意的是,如果对象语法方式要添加的字段太长,你也可以写成methods的形式或者computed(计算属性)的形式,
下面同样是一个案例,不过把对象抽离到methods中了//vue解析时,调用该getClasses方法,会返回一个对象,和上面案例一样的。 <h2 :class="getClasses()">{{message}}</h2> let app = new Vue({ el: '#app', data: { message : '你好,李银河', isA: true, isB: false }, methods:{ getClasses:function () { /* methos中获取data中的数据,需要this this指的是vue实例,因为是vue调用这个方法 */ return {a:this.isA,b:this.isB}; } } });
数组语法
除了在class属性值使用对象,你还可以使用数组,例如:<h2 :class="['a','b']">{{message}}</h2>
,
这种方式class的a,b是固定的死值,字符串,如果去掉单引号,则表示一个变量,引用data中的实际数据了。
同样的,数组语法的方式也可以使用methods或者computed的形式。
值得注意的是:千万不要忘记v-bind最初的使用方式,仅仅使用固定值和变量即可,v-bind初始方法适用于所有属性绑定。
v-bind绑定style属性
绑定style当然也可以使用对象语法或数组语法对象语法
值得注意的是,我们在写key的时候,可以写font-size或者FontSize,另外对象的值可以是固定值'50px'
,也可以是data中的变量如果是固定值,就是字符串,一定要用单引号包含,变量才不用单引号包含,这个规则同样适用于绑定class。
最后,就像绑定class属性一样,你也可以写成函数的形式去调用,自定义函数写在methods中。
案例:<h2 :style="{fontSize:'50px'}">{{message}}</h2>
,这种固定值一般很少写,可能用引用变量方式多点。数组语法
与绑定class属性一致,值得说明的是,class属性绑定的是单个值,而style属性要绑定一个对象,
因为style样式必须有key和value又是怎么实现的呢?下面是示例代码:<div id="app"> <span :style="[baseStyle]">{{message}}</span> //数组中可以有更多的元素,该元素是一个引用data中的对象 </div> <script> let app = new Vue({ el: '#app', data: { message:'加油!', baseStyle:{ fontSize:'100px', color: 'red' } }, }); </script>
2021年9月24日更新
绑定class样式的三种方式
1)字符串写法,适用于:样式的类名不确定,需要动态指定 2)数组写法,适用于:要绑定的样式个数不确定,名字也不确定 3)对象写法,适用于:要绑定的样式个数确定,名字也确定,但要动态决定用不用 下面是一个案例对应上面的3种方式: <style> .a{ width: 100px; height: 100px; background-color: #0e90d2; } .b{ width: 100px; height: 100px; background-color: #3ca1ff; } .c{ width: 100px; height: 100px; background-color: #9acfea; } </style> <body> <div id="app"> <div :class="bgColor" @click="changeColor1">字符串方式</div> <div :class="colorArr" @click="changeColor2">数组方式</div> <div :class="colorObj" @click="changeColor3">对象方式</div> </div> <script> let app = new Vue({ el: '#app', data: { bgColor: 'a', colorArr: ['a','b','c'], colorObj: { a:false, b:false, c:false } }, methods: { //字符串方式 changeColor1(){ let colors =['a','b','c']; //随机取数组中样式 this.bgColor=colors[Math.floor(Math.random()*3)] }, //数组方式 changeColor2(){ this.colorArr.pop(); }, //对象方式 changeColor3(){ this.colorObj.a=true this.colorObj.b=true this.colorObj.c=true } } }); </script> </body>
绑定style的两种方式
对象方式,数组方式,下面是一个案例: <div id="app"> <span :style="styleObj">{{message}}</span> <span :style="styleArr">{{message}}</span> </div> <script> let app = new Vue({ el: '#app', data: { message:'加油!', //对象语法 styleObj: { color: 'red', fontSize: '30px' }, //数组语法 styleArr: [ { color: 'blue', fontSize: '50px' },{ backgroundColor: 'orange', } ] } }); </script>
计算属性
计算属性指的是在插值的过程中,为了方便,将值重新设置成一个新值,插入到DOM结点中。
如果你从后端拿到数据,需要先处理一下数据,才能在DOM页面中展示出来,那么你可能就需要使用计算属性。
计算属性的基本使用
<div id="app"> <span>{{firstName}} {{lastName}}</span><br> <span>{{firstName+" "+lastName}}</span><br> <span>{{getFullName()}}</span><br> <span>{{fullName}}</span><br> //计算属性是不需要使用fullName()方式调用 </div> <script> let app = new Vue({ el: '#app', data: { firstName:'万', lastName:'一' }, computed:{ //fullName是计算后的属性,所以虽然是函数类型,但命名时变量名应该写成属性方式 fullName:function () { return this.firstName+" "+this.lastName; } }, methods:{ getFullName(){ return this.firstName+" "+this.lastName; } } }); </script> /* 小提示:对象中的方法书写在es6中可以简化 computed:{ fullName:function () { return this.firstName+" "+this.lastName; } } 可以简化成下面这种,和上面是等效的,是es6方法的增强写法 computed:{ fullName(){ return this.firstName+" "+this.lastName; } } */
计算属性setter和getter
上个案例中,你可能会很好奇,为什么fullName明明是个函数的形式定义,却在使用的时候当做属性的方式使用
其实计算属性本身是个对象,这个对象有set方法和get方法,而我们定义的方法是简写的get方法,
set方法被我们省略掉了,一般很少使用完整版的计算属性是下面这种形式:<div id="app"> <span>{{fullName}}</span><br> <!--计算属性是不需要使用fullName()方式调用--> </div> <script> let app = new Vue({ el: '#app', data: { firstName:'万', lastName:'一' }, computed:{ fullName:{ set:function (newValue) { let names = newValue.split(" "); this.firstName=names[0]; this.lastName=names[1]; }, get:function () { return this.firstName+" "+this.lastName; } } } }); </script>
因为我们大多时候并不使用set方式,所以使用省略的方式写计算属性,以下是省略版本,省略了set方法,并简写get方法
fullName:function () { return this.firstName+" "+this.lastName; }
计算属性computed与方法methods有什么区别?
原因:计算属性会进行缓存,如果多次使用时,计算属性只会调用一次,而函数多次执行,就会多次调用函数
另外,计算属性在vue内部中确实是一个属性,而我们编写的函数其实是重写了这个属性的get方法。
监视属性
监视属性的基本使用
<script> let app = new Vue({ el: '#app', data: { message:'加油!' }, //监视属性第一种写法 watch: { message: { //初始化时让handler调用一下,此时oldvalue是undefined immediate: true, //newValue:message改变后的值,oldValue:改变之前的值 handler(newValue,oldValue){ console.log(newValue,oldValue); } } } }); //监视属性第二种写法 app.$watch('message',{ //immediate: true, handler(newValue, oldValue) { console.log(newValue,oldValue); } }) </script>
深度监视
<div id="app"> <span>{{message}}</span> <button @click="number.a++">增加a</button> <button @click="number.b++">增加b</button> </div> <script> let app = new Vue({ el: '#app', data: { message:'加油!', number: { a: 1, b: 2 } }, watch:{ number:{ /* 开启深度监视配置项,如果没有,则监视整个number对象改变,有了deep则监视number中所有属性的变化 监视整个number对象改变指的是,整个对象换了才监听的到,而其中某个属性发生改变,检测不到 */ deep: true, handler(newValue,oldValue){ console.log("number中任一属性发生了变化") } } } }); </script>
监视的简写形式
当监视属性时不需要其他配置项,比如deep,immediate等等,则你可以使用简写方式,简写有两种方式: <script> let app = new Vue({ el: '#app', data: { message:'加油!', }, watch:{ //简写的第一种方式 message(newValue,oldValue){ console.log("message改变了"); } } }); //简写的第二种方式,直接使用handler函数,不需要像之前一样给个对象 app.$watch('message',function(newValue,oldValue) { console.log("message改变了"); }) </script> 注意:事件函数,计算属性方法,监视的方法,都不能写成箭头函数,这些函数都属于vue管理, 如果写成箭头函数则其中的this是window对象,而不是vue实例对象,因为箭头函数的this取自vue的上下文环境 而vue是window的属性,所以箭头函数中的this是Window对象
watch和computed的区别
1)computed能完成的功能,watch都可以完成 2)watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作 两个重要的小原则: 1)所被vue管理的函数,最好写成普通函数,这样this指向才是vm(vue实例),或组件实例对象 2)所有不被vue所管理的函数(定时器的回调函数,ajax的回调函数,Promise的回调函数,这些都是浏览器异步组件调用,不归vue管理) 最好写成箭头函数,这样this的指向才是vm或组件实例对象。
过滤器的使用
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑处理)
语法:
- 注册过滤器:
Vue.filter(name,callback)或new Vue({filters:{}})
- 使用过滤器:
{{xxx | 过滤器名}} 或 v-bind属性="xxx | 过滤器名"
- 备注:
- 过滤器也可以接收额外参数,多个过滤器也可以串联
- 过滤器并没有改变原本的数据,而是产生新的对应的数据
下面是过滤器的案例:
/*
本次案例演示了,全局过滤器(可多个组件使用),局部过滤器,过滤器传递参数,过滤器串联使用
*/
<div id="app">
<h3>当前时间戳:{{nowTime}}</h3>
<h3>当前时间,格式1:{{nowTime | change()}}</h3>
<h3>当前时间,格式2:{{nowTime | change("YYYY/MM/DD hh:mm:ss")}}</h3>
<h3>当前时间,格式3:{{nowTime | change("YYYY/MM/DD hh:mm:ss") | change2}}</h3>
<h3>当前时间,格式4:{{nowTime | change("YYYY/MM/DD hh:mm:ss") | change2 | change3}}</h3>
</div>
<script>
//全局过滤器
Vue.filter('change3',function(t3) {
return t3.substring(0,4);
})
let app = new Vue({
el: '#app',
data: {
nowTime: Date.now()
},
//局部过滤器
filters: {
/*
value指的是管道符传递过来的参数,str是过滤器调用时传递的参数,如果没传,则使用默认值格式化时间戳
*/
change(value,str="YYYY-MM-DD HH:mm:ss"){
return dayjs(value).format(str);
},
//多个过滤器串联使用
change2(t2){
return t2.substring(0,10)
}
}
});
</script>
/*
本次案例中使用到了第三方库:dayjs,Day.js是一个轻量的处理时间和日期的JavaScript库
安装:npm install dayjs --save
*/
//值得注意的是:Vue3中过滤器已经被遗弃了,推荐用计算属性或者方法实现。
事件监听
事件监听使用的是v-on指令,为BOM元素绑定事件函数,可以使用语法糖写成@
v-on参数
事件监听函数的参数问题值得我们注意,下面是几种参数情况:如果事件函数不需要额外的参数,则DOM中@click=”函数名”不需要添加()
<div id="app"> <button @click="ceshi">{{num}}</button> //点击按钮,调用ceshi函数,不需要() </div> <script> let app = new Vue({ el: '#app', data: { num:0 }, methods:{ ceshi(){ this.num++; //在data对象外访问data中的变量,都需要使用this,Dom中除外 } } }); </script>
当然我们也可以给事件函数一个参数,当点击时,传递一个参数到事件函数中
<button @click="ceshi(50)">{{num}}</button> methods:{ ceshi(param){ this.num=param; } }
值得注意的是,如果你的事件函数有参数接收,但是DOM元素中写成
@click="ceshi"
,并没有指定参数,甚至没有括号。
vue还是会传递一个对象到事件函数参数中,事件函数会接收一个event对象,这个对象是浏览器生成的,
如果想传入event事件对象进事件函数,则Dom元素中要么只写事件函数名(就是上面所说的),要么使用$event传递参数最后,如果需要我们的事件函数同时需要某个参数和event对象,那么传递参数的时候应该使用$event传递参数
如果参数直接使用event传递,则vue会以为该event参数是变量,就去data对象中找对应的属性,最后报错找不到。<button @click="ceshi('万①',$event)">{{num}}</button> methods:{ ceshi(param1,event){ console.log(param1+" "+event); } }
v-on的修饰符
修饰符指的是在v-on指令的基础上加上一些命令,使得可以改变事件的触发规则,例如下面这些:@click.stop,解决事件冒泡的问题 @click.prevent,移除默认的事件,例如表单提交,点击submit按钮时会自动提交, 现在我手动为该按钮绑定事件函数,等我检查完毕,我再提交,不需要这个按钮默认的事件提交了, 使用v-on.prevent="事件函数名",表示移除该DOM元素默认的事件,转而执行我绑定的事件函数。 @click.enter,监听键盘的确认键,也可以@click.键盘上键帽对应的编码/别名,来监听某个键 @click.native,监听组件根元素的原生事件 @click.once,只触发一次回调,只能点击一次才有用, 值得说明的是,并不是只有点击事件才有修饰符,所有事件可以使用修饰符,另外,修饰符可以多个连用,@click.stop.prevent="xxx"
条件判断和循环遍历
条件判断
条件判断主要使用:v-if,v-else-if,v-else,v-show,v-if=”true”,显示DOM元素,反之不显示,案例如下:<div id="app"> <h3 v-if="score>=90">优秀</h3> <h3 v-else-if="score>=70">挺好</h3> <h3 v-else-if="score>=60">平庸</h3> <h3 v-else="score<60">不行哦</h3> </div> data: { score:95 } //不过这种计算方式不太好,可以使用计算属性代替。
注意:在使用v-if等条件判断指令时,可能会出现控件复用问题,因为vue底层处于性能考虑,会尽量复用之前存在的控件
下面是一个关于控件复用的案例:<body> <div id="app"> <span v-if="byUsername"> <label for="username">用户账号</label> <input type="text" id="username" placeholder="用户账号"> </span> <span v-else> <label for="email">用户邮箱</label> <input type="text" id="email" placeholder="用户邮箱"> </span> <button @click="byUsername =! byUsername">切换登陆方式</button> </div> </body> <script> const app = new Vue({ el: '#app', data: { byUsername: true } }) </script> /* 本案例中,当用户在id为username的input控件中输入用户账户,输入到一半,点击按钮切换到邮箱登录时 因为vue控件复用问题,用户会看到id为email的input控件上仍然存在之前的账号值,为了解决这个问题 就需要在这些控件上加上key属性,并填写不同的key属性值。 */
最后还有一个与v-if类似的指令:v-show,它与v-if的区别是,如果判断为false,v-if中根本不会有DOM元素,
而v-show则只是将DOM元素的display属性设置为none而已,浏览器页面按f12,还是可以看到DOM元素的。循环遍历
v-for指令用于遍历数组和对象,遍历数组很简单,不仅可以拿到数组的元素,还可以拿到数组的下标/索引<li v-for="(item,index) in books">{{index}} {{item}}</li> data: { books:[ '大主宰', '斗破苍穹', '莽荒纪', '星辰变', '完美世界' ] }
如果遍历的是对象,则也可以获取到对象的key和value,如下所示:
<li v-for="(value,key) in book">{{key}} {{value}}</li> data: { book: { bookName:'大主宰',price:20 } }
注意:括号内中第一个参数永远是数组元素或者对象value,第二个参数永远是数组下标或者对象的key,跟参数名没关系
当然了,如果遍历的是对象,还可以有第三个参数,是对象的下标,第一个属性的下标是0,以此类推,不过对象下标很少使用。最后,vue官方建议,在使用v-for时,推荐在DOM元素中绑定一个key元素,绑定key元素可以提高DOM渲染效率,涉及到虚拟DOM
并且这个key属性值必须是唯一的,并且不能是数组下标或对象下标,最好是数组元素值或者对象value。
下面来解释一下原因:就拿上面遍历数组的例子来看,为什么需要绑定key属性,如果没有绑定key底层是怎么操作的? 首先如果我们没有绑定key,则输出如下 0 大主宰 1 斗破苍穹 2 莽荒纪 3 星辰变 4 完美世界 如果此时我们想插入一个DOM元素在"斗破苍穹"和"莽荒纪"之间,我们在控制台输入app.books.splice(2,0,'武动乾坤'),结果正常显示出来 0 大主宰 1 斗破苍穹 2 武动乾坤 3 莽荒纪 4 星辰变 5 完美世界 但是底层是如何操作的呢? 实际上是将"莽荒纪"DOM元素的值改成"武动乾坤",然后将"星辰变"改成"莽荒纪",依次改下去,最后生成新的DOM元素,它的值为"完美世界"。 和java数组中间新增一个元素,其后所有元素往后推一位一样,这样做的效率非常的低, 我们想要的是,直接新增一个DOM元素插入到"斗破苍穹"和"莽荒纪"之间不需要改变其他DOM元素的位置, 就和java中链表一样,这个时候就需要为这些DOM元素绑定一个唯一的key,这个key不能是数组的索引。 因为索引是会变的,如果你的属性key是索引,那插入新增DOM元素后,其后DOM元素的索引值就会发生改变, 触发vue的diff算法后,也会更新后面的DOM元素。所以属性key值要为数组元素值(元素要唯一), 使得每一个key于自身DOM元素绑定,才能实现和链表一样的效果。key的作用主要是为了高效更新虚拟BOM。
数组响应问题
我们知道,通过修改data中的数据就可以使得界面中元素数据实时发生改变,这被称为响应式,但并不是所以方法都能够使得DOM元素及时更新。也就是说并不是一定修改data数据,DOM元素就能立即响应更改,有些方法是响应式的,有些方法并不是响应式的
数组中响应式的方法:push(),pop(),shift(),unshift(),splice(),sort(),reverse()
如果通过下标直接修改方法,则这种方式并不是响应式的,也就是说DOM元素并不立即发生改变,虽然实际上数据已经被改变了。
例如下面这个案例:
<li v-for="(value,key) in books">{{key}} {{value}}</li>
<li><button @click="update">点击修改</button></li>
methods:{
update(){
this.books[0]='武动乾坤';
console.log(this.books);
}
}
上面这种方式修改数组元素,数据确实被修改了,但是DOM元素并没有实时更新,如果想实时更新,
可以使用上面的响应式的数组方法去更新数组的元素。
另外vue也提供了方法去实时更新数据:vue.set(要修改的对象,索引值,修改后的值),或者this.$set(要修改的对象,索引值,修改后的值)
Vue.set(this.books,0,'武动乾坤');
this.$set(this.books,0,'武动乾坤');
/*
其实学到后面才发现,这两个是一样的,如果你需要某个方法,模块在每个组件内都能访问到,
你只需要,Vue.prototype.$名称=方法或者某个模块,例如Vue.prototype.$set=Vue.set
因为组件都是Vue构造函数的实例,所以自然继承了Vue原型对象中的属性和方法,
所以我们在其他组件可以直接使用this.$set方法,组件内容会在下一篇博客中讲解,这里暂做了解。
*/
阶段案例
实现购物车案例
<div id="app">
<div v-if="books.length">
<table>
<thead>
<th>书籍名称</th>
<th>出版日期</th>
<th>价格</th>
<th>购买数量</th>
<th>操作</th>
</thead>
<tbody>
<tr v-for="(book,index) in books">
<td>{{book.name}}</td>
<td>{{book.time}}</td>
<td>{{book.price | showPrice}}</td>
<td>
<button @click="sub(index)" :disabled="book.count<=1">-</button>
{{book.count}}
<button @click="add(index)">+</button>
</td>
<td><button @click="remove(index)">移除</button></td>
</tr>
</tbody>
</table>
<span>总价{{allPrice}}</span>
</div>
<div v-else>购物车为空</div>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
books:[
{name:'大主宰',price:20.00,count:1,time: '1998-8'},
{name:'斗破苍穹',price:30.00,count:1,time: '1998-8'},
{name:'莽荒纪',price:50.00,count:1,time: '1998-8'},
{name:'星辰变',price:20.00,count:1,time: '1998-8'},
{name:'完美世界',price:30.00,count:1,time: '1998-8'},
],
},
computed:{
allPrice(){
//页面解析可能是先解析计算属性,然后再渲染数据
let sum=0;
this.books.map(function(value) {
sum+=(value.price*value.count);
});
return sum;
}
},
filters:{ //设置过滤器
showPrice(price){
return '$'+price.toFixed(2); //保留两位小数
}
},
methods:{
remove(index){
//方式1
/*let newBooks = this.books.filter(function(book,index2) {
return !(index===index2);
});
this.books=newBooks;
*/
//方式2
this.books.splice(index,1);
},
add(index){
this.books[index].count++;
},
sub(index){
this.books[index].count--;
}
},
});
</script>
双向绑定
用于绑定控件,被绑定的控件与data中的数据实现双向绑定,准确的说,被绑定控件的value值和data中的数据
实现双向绑定,当我们修改控件的值时,data中的数据也会被修改,这是v-bind实现不了的,v-bind绑定value值
只是将data中的数据取出来显示,当我们修改控件的value值时,data中数据并不会被更改,而v-model却可以。
v-model的基本使用
<input type="text" v-model="message"> data: { message:'加油!', } //当我们手动修改文本框中的数据时,data中数据也会被更改,而v-bind绑定控件的value可做不到这一点,这就是双向绑定
v-model原理
实际上v-model可以通过v-bind绑定value属性加上事件监听可以实现,当每次修改控件文本框时,
会触发input事件(input控件有input事件),然后执行我们的事件函数,最后将输入的值写到data数据中。
下面就是模拟v-model<input type="text" :value="message" @input="changeValue"> data: { message:'加油!', }, methods:{ changeValue(event){ this.message=event.target.value; } } //或者input事件并不执行事件函数,直接使用表达式即可: <input type="text" :value="message" @input="message=$event.target.value">
v-model在radio中的使用,单选框
<div id="app"> <label> <input type="radio" name="sex" id="male" value="男" v-model="sex">男 </label> <laber> <input type="radio" name="sex" id="female" value="女" v-model="sex">女 </laber> </div> data: { sex:'男' //默认男 } /* 小提示,当使用v-model在单选框时,name的属性可以去掉,name的本意是将两个选项绑定在一起, 而v-model的值相同,已经表示为这两个控件被绑定在一起。 */
v-model在checkbox中的使用
checkbox分为单选框和多选框,单选框表示要么选中,要么不选择,多选框则是同时选中多个单选框
<label for="agree"> <input type="checkbox" id="agree" v-model="isAgree">同意协议 </label> data: { isAgree: false //当用户点击单选框,则isAgree的值为true }
多选框
<input type="checkbox" value="篮球" v-model="hobbies">篮球 <input type="checkbox" value="足球" v-model="hobbies">足球 <input type="checkbox" value="兵乓球" v-model="hobbies">兵乓球 <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球 data: { hobbies: [] //v-model有多个值时,使用数组进行接收 }
上面例子使用checkbox出现的问题
上面v-model使用在checkbox的问题是,我们的input写死在页面中,实际上应该动态的<div id="app"> <label v-for="ball in originHobbies" > <input type="checkbox" :value="ball" v-model="hobbies">{{ball}} </label> {{hobbies}} </div> data: { hobbies:[], originHobbies:['篮球','足球','羽毛球','兵乓球'] }
v-model在select中的使用
select也有单选和多选之分单选
<select v-model="fruit"> <option value="芒果">芒果</option> <option value="火龙果">火龙果</option> <option value="草莓">草莓</option> <option value="葡萄">葡萄</option> </select> data: { fruit:'草莓' }
多选
只需要在select上加上multiple属性即可,<select v-model="fruit" multiple>
v-model的修饰符
之前讲过事件有它的修饰符,而v-model也有自己的修饰符,修饰符主要是用来帮助我们处理一些数据的,类似语法糖的概念- lazy修饰符
默认情况下,v-model默认是在input事件中同步输入框的数据的,也就是说,一旦有数据发生改变,
对应的data数据就会自动发生改变,lazy修饰符可以让数据在失去焦点或者回车时才会更新<input type="text" v-model.lazy="message">
- number修饰符
默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串进行处理,但是如果我们希望处理的是数字类型,
那么最好将内容直接当做数字进行处理,number修饰符可以让在输入框输入的内容自动转成数字类型<input type="number" v-model.number="message">
该文本框只能输入数字,并且绑定在data数据中时,message类型也为数字,
如果没有number修饰符,则message会是字符串类型 - trim修饰符
如果输入的内容首尾有很多空格通常我们希望将其去除,trim修饰符可以过滤内容左右两边的空格<input type="text" v-model.trim="message">
,绑定到data数据时,会去除用户输入的两边的空格
- lazy修饰符
混入mixin
混入mixin是用于复用组件的配置,可以把多个组件共用的配置提取成一个混入对象,下面是一个简单的案例:
//app.vue
<template>
<div id="app">
<test1/>
<hr>
<test2/>
</div>
</template>
<script>
import Test1 from './components/Test1';
import Test2 from './components/Test2';
export default {
name: 'App',
components: {
Test1,Test2
}
}
</script>
//CommonConfig,多个组件共同的配置抽离成一个js文件
export default {
data(){
return {
msg: '测试混入mixin'
}
}
}
//Test1.vue
<template>
<div>
Test1组件:{{msg}}
</div>
</template>
<script>
import comConfig from './CommonConfig'
export default {
name: "Test1",
mixins: [comConfig]
}
</script>
//Test2.vue
<template>
<div>
Test2组件:{{msg}}
</div>
</template>
<script>
import comConfig from './CommonConfig'
export default {
name: "Test2",
mixins: [comConfig]
}
</script>
/*
如果在复用的文件中,你的data中数据或者方法与复用js文件中的配置冲突
以你的为主,但是如果是钩子函数冲突了,则复用的钩子函数和你的钩子函数都起作用,并且复用的钩子函数先执行
上面的混合方式是局部混合,即每个组件中都需要导入复用文件,并配置mixin属性使用
而全局混合指的是,在main.js中导入复用js文件,然后Vue.mixin(导入的复用对象),则整个应用所有组件都可以使用复用配置
如果是全局的混合,则不需要配置mixin属性,所有组件都有了复用文件的配置
*
自定义指令
vue中的指令指的是DOM元素标签中标注的v-xxx,只要在标签中标注vue的指令,就能够完成一些事情,本质上
是vue做了一些DOM操作,例如:v-text指令,就是vue拿到data数据,将数据交给标签的innerText属性,
那么自定义指令就是我们自己去做一些DOM操作,自定义指令有两种实现方式,函数式和对象式,下面来看看函数式:
//实现一个指令,只要标注在标签上,就能够显示一些我们想要的内容
<div id="app">
<span v-wanyi></span>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
},
directives: {
/*
指令何时会被调用?
1,指令与元素成功绑定时(一上来,第一次时)
2,指令所在的模板被重新解析时(data数据发生改变时)
*/
wanyi(element,banding){
element.innerText='万一的秃头时光';
}
},
});
</script>
以上是函数式实现一个指令,下面是对象式实现自定义指令:
//使用对象式定义自定义指令v-waner,使用在input标签上,并可以获取到value,并且一上来就可以获取input的焦点focus
<div id="app">
<span v-wanyi="n"></span>
<hr>
<input v-waner="n"/>
<hr>
<button @click="n++">点击n+1</button>
</div>
<script>
let app = new Vue({
el: '#app',
data: {
n: 1
},
directives: {
wanyi(element,banding){ //函数式实现自定义指令时,这个函数相当于bind+update
element.innerText=banding.value;
},
waner: {
//指令与元素成功绑定时(一上来,第一次时)
bind(element,banding){
element.value=banding.value
},
//指令所在元素被插入页面时,mounted的时间差不多
inserted(element,banding){
element.focus() //focus方法必须在DOM被挂载之后才能执行,所以只能用对象方式实现这个功能
},
//指令所在的模板被重新解析时(data数据发生改变)
update(element,banding){ //update逻辑与bind逻辑一样
element.value=banding.value
}
}
},
});
</script>
/*
函数式和对象式实现自定义指令的区别在于:对象式实现自定义指令多了一个钩子函数,可以在页面加载完毕时做一些操作
*/
自定义指令上的一些坑:
因为html模板不支持大小写,所以你的指令名不能有大写,使用-表示驼峰,并且在自定义指令函数名上使用字符串包裹函数名
比如在上面的例子上,将wanyi,如果写成wanYi,肯定会报错的,你可以写成下面这种形式://使用: <span v-wan-yi="n"></span> //定义自定义函数,因为有-,所以用''包裹,因为对象的key都是字符串 'wan-yi'(element,banding){ element.innerText=banding.value; }
指令中函数,里面的this都是window,不是vue,所以想访问vue中的data数据,只能通过banding参数
全局指令:
Vue.directive('指令名',function(){}) //函数式
Vue.directive('指令名',{}) //对象式
全局指令的创建和全局过滤器的创建类似,在多个vue实例中可以使用
下面是关于自定义指令的总结:
插件
之前都是直接导入插件依赖,然后Vue.use(插件对象),就可以直接使用了,原来当我们调用Vue.use()时,
vue会调用插件中的install方法并且install方法可以接收两个参数,第一个参数是Vue,第二个参数及以后,
就是插件使用者传递的数据,交给插件作者使用在插件的install方法,我们可以定义一些全局的东西放到Vue中,
就是一直说的全局注册,注册到Vue中,然后,我们使用插件就可以使用一些功能了
过度与动画
Vue封装的过度和动画,作用是:在插入,更新或移除DOM元素时,在合适的时候给元素添加样式类名
原生css实现动画
<template> <div class="home"> <button @click="isShow=!isShow">点击显示或隐藏</button> <h1 v-show="isShow" class="come" >万一</h1> </div> </template> <script> export default { name: 'Home', data(){ return { isShow: true } } } </script> <style scoped> h1{ background-color: #1da1f2; } .come{ animation: wanyi 1s; } .go{ animation: wanyi 1s reverse; } @keyframes wanyi { from{ transform: translateX(-100%); } to{ transform: translateX(0px); } } </style> //通过点击事件指定对应的回调函数,在回调函数中改变class属性,从而达到动画的效果
使用vue中对动画的支持,使得我们无需再去手动更改class属性,vue会在合适的时候加上动画
要求是class样式名不能随意写,不能写成上面come或者go<template> <div class="home"> <button @click="isShow=!isShow">点击显示或隐藏</button> <transition name="wan" appear> <!--appear表示刷新页面立马执行enter样式--> <h1 v-show="isShow" class="come" >万一</h1> </transition> </div> </template> <script> export default { name: 'Home', data(){ return { isShow: true } } } </script> <style scoped> h1{ background-color: #1da1f2; } /*默认样式名*/ /* .v-enter-active{ animation: wanyi 1s; } .v-leave-active{ animation: wanyi 1s reverse; } */ /*如果给transition指定了name,则样式名改成:name-enter/leace-active*/ .wan-enter-active{ animation: wanyi 1s; } .wan-leave-active{ animation: wanyi 1s reverse; } /*动画*/ @keyframes wanyi { from{ transform: translateX(-100%); } to{ transform: translateX(0px); } } </style>
使用过度效果实现上面的案例
<style scoped> h1{ background-color: #1da1f2; } /* .wan-enter,表示进入的起点, .wan-enter-to,表示进入的终点 .wan-leave,表示离开的起点 .wan-leave-to,表示离开的终点 .wan-enter-active,表示进入的过程中 .wan-leave-active,表示离开的过程中 */ /*进入的起点,离开的终点*/ .wan-enter,.wan-leave-to{ transform: translateX(-100%); } /*进入的终点,离开的起点*/ .wan-leave,.wan-enter-to{ transform: translateX(0); } /*进入的过程中,离开的过程中,在其中增加效果*/ .wan-enter-active ,.wan-leave-active{ transition: 0.5s linear; } </style> /* 提示:如果有多个标签都需要使用相同的动画,则使用<transform-group>标签包裹, 并且给其中每个元素加上key值,值为不同的数字即可 */
第三方库,animate.css,千万不要下载错了,4版本的!下面是简单的使用:
<template> <div class="home"> <button @click="isShow=!isShow">点击显示或隐藏</button> <transition name="animate__animated animate__bounce" appear enter-active-class="animate__backInDown" leave-active-class="animate__backOutDown"> <h1 v-show="isShow">万一</h1> </transition> </div> </template> <script> import 'animate.css' export default { name: 'Home', data(){ return { isShow: true } } } </script> <style scoped> h1{ background-color: #1da1f2; } </style>