电影网站免费建设,苏州百度推广公司,跨境电商diy定制平台,网站源码下载免费源码一、Vue基础知识点总结
开发vue项目的模式有两种#xff1a;
基于vue.js#xff0c;在html中引入vue.js#xff0c;让vue.js管理div#app元素。基于脚手架环境#xff1a;通过vue脚手架环境可以方便的创建一个通用的vue项目框架的模板#xff0c;在此基础之上开发vue项目…一、Vue基础知识点总结
开发vue项目的模式有两种
基于vue.js在html中引入vue.js让vue.js管理div#app元素。基于脚手架环境通过vue脚手架环境可以方便的创建一个通用的vue项目框架的模板在此基础之上开发vue项目更加便捷。适合工程化开发。
制作web项目需要安装vue脚手架环境 VueCLI。 需要先安装及配置该脚手架环境并创建项目包。
# 配置npm源 国内npm镜像仓库地址
npm config set registry https://registry.npmmirror.com # 安装vuecli
npm install -g vue/cli# 安装完毕后执行
vue -V使用VueCLI创建项目请参照 VueCLI_installation_guide.pdf
项目创建完毕后在vscode安装两个开发vue2项目所需要使用的插件
1、基本使用
下面先来看一段最简单的代码如下所示
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /titleVue基本使用/title/headbodydiv idapp{{msg}}/divscript srcvue.js/scriptscript//创建vue实例const app new Vue({el: #app,data() {return {msg: hello world,};},});setTimeout(() {app.msg hello Vue;}, 1000);/script/body
/html在上面的代码中,创建了vue的实例并且指定了数据最终数据展示在id 为app的这个div中并且在停顿了1秒中以后通过Vue的实例来修改对应的msg数据。
通过上面的代码我们能够够看到Vue的核心理念是数据驱动的理念所谓的数据驱动的理念当数据发生变化的时候用户界面也会发生相应的变化开发者并不需要手动的去修改dom.
简单的理解就是vue.js帮我们封装了数据和dom对象操作的映射我们只需要关心数据的逻辑处理数据的变化就能够自然的通知页面进行页面的重新渲染。
这样做给我们带来的好处就是我们不需要在代码中去频繁的操作dom了这样提高了开发的效率同时也避免了在操作Dom的时候出现的错误。
Vue.js的数据驱动是通过MVVM这种框架来实现的MVVM 框架主要包含三部分Model,View,ViewMode
Model:指的是数据部分对应到前端就是JavaScript对象。
View:指的就是视图部分
ViewModel: 就是连接视图与数据的中间件(中间桥梁)
以上三部分对应到代码中的位置如下图所示
bodydiv idapp{{message}}/div //viewscript src./vue2.is/scriptscriptvar vin new Vue({ // vin ViewModeel:#appdata:{message: hello world // message Model}})/script
/body下面我们再来看一张图来理解一下MVVM框架的作用
数据(Model)和视图(View)是不能直接通讯的而是需要通过ViewModel来实现双方的通讯。当数据(Model)变化的时候ViewModel能够监听到这种变化并及时通知View视图做出修改。同样的当页面有事件触发的时候ViewModel也能够监听到事件并通知数据(Model)进行响应。所以ViewModel就相当于一个观察者监控着双方的动作并及时通知对方进行相应的操作。
简单的理解就是MVVM 实现了将业务(数据)与视图进行分离的功能。
在这里还需要注意的一点就是
MVVM框架的三要素响应式模板引擎渲染
响应式vue如何监听数据的变化
模板Vue的模板如何编写和解析怎样将具体的值替换掉{{msg}}内容这就是模板引擎的解析。
渲染Vue如何将模板转换成html? 其实就是有虚拟DOM 向真实DOM的转换。
在后面的课程中我们还会深入探讨这块内容包括我们自己模拟实现一个数据驱动的框架。
以上内容也是面试的时候会问到的问题。
2、模板语法
2.1 属性绑定
属性的绑定下面先来看一下关于对属性的绑定
div idapph2 v-bind:titlemsg{{msg}}/h2/div在上面的代码中我们通过v-bind的方式给h2绑定了一个title属性。
当然上面的代码我们也可以使用如下的方式来进行简化 div idapph2 :titlemsg{{msg}}/h2/div为了避免闪烁的问题也就是最开始的时候出现:{{msg}}的情况可以使用如下的绑定方式。 div idapph2 :titlemsg!-- {{msg}} --span v-textmsg/span/h2/div3、 列表渲染
我们可以使用v-for指令基于一个数组来渲染一个列表.v-for指令需要使用item in items形式的语法。其中items 是源数组而item则是被迭代的数组元素的别名。
基本实现的代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/title/headbodydiv idappul!-- users表示数组item表示从数组中取出的对象,这个名字可以随意取 --!-- 注意 v-for必须结合key属性来使用它会唯一标识数组中的每一项未来当数组中的那一项改变的时候它会只更新那一项好处就是提升性能。注意key的值唯一不能重复 --!-- index表示数组的索引值该名字可以随意定义 --li v-for(item,index) in users :keyitem.id编号{{item.id}} 姓名:{{item.name}}---索引:{{index}}/li/ul/divscript srcvue.js/scriptscriptnew Vue({el: #app,data: {users: [{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},],},});/script/body
/html
注意为了能够保证列表渲染的性能我们需要给v-for添加key属性。key值必须唯一而且不能使用index与random作为key的值。
关于这一点是与虚拟DOM算法密切相关的。在后面的课程中会最为一个重点来探讨虚拟DOM的内容。这也是面试的时候经常被问到的问题。
4、v-model
在前面讲解vue简介的时候我们说过如果model中的数据发生了改变会通过ViewModel通知View更新数据这个效果前面我们也已经演示了现在演示一下当view中的数据发生了变化后怎样通过ViewModel来通知model来完成数据的更新。
其实这就是我们常说的“双向数据绑定”
怎样实现这种效果呢可以通过v-model来实现。 !-- v-model指令用来双向数据绑定就是model和view中的值进行同步变化 --!-- v-model只能在input/textarea/selet 也就是表单元素--具体代码实现如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /meta http-equivX-UA-Compatible contentieedge /title双向数据绑定/titlescript src./vue.js/script/headbodydiv idappinput typetext v-modeluserName //divscriptconst vm new Vue({el: #app,data: {userName: zhangsan,},});/script/body
/html
怎样验证v-model实现了双向数据绑定呢
可以打开控制台然后输入vm.userName 发现输出的值为zhangsan, 取的是模型中的数据。
当在文本框中输入新的值后在敲一下vm.userName发现对应的数据发生了变化也就是视图中的数据发生了变化模型中的数据也 会发生变化。
那么在控制台中直接给vm.userNamelisi,发现文本框中的值也发生了变化。
关于v-model 这个知识点面试的时候经常会被问到的一个问题就是自己能否模拟实现一个类似于v-model的双向数据绑定的效果。关于这个问题你可以先思考一下在后面的课程中我们会详细的讲解。
5、v-on
怎样监听dom的事件呢可以通过v-on指令完成具体的代码如下
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0meta http-equivX-UA-Compatible contentieedgetitleDocument/titlescript src./vue.js/script
/headbodydiv idappspan{{name}}/span!-- 通过v-on来指定对应的事件然后后面跟上对应的方法名方法的定义在methods完成 --button v-on:clickchangeName更换姓名/button/divscriptvar vm new new Vue({el: #app,data: {name: zhangsan},// 通过methods完成函数或方法的定义methods: {changeName() {// 在methods中要获取data中的属性需要通过this来完成this表示的是vue实例。this.name itcast}}})/script
/body
/html还可以通过简写的形式。建议以后都使用简写的形式
button clickchangeName更换姓名/button带参数的形式如下
button clickchangeNameByArg(laowang)带参数的情况/buttonscriptvar vm new new Vue({el: #app,data: {name: zhangsan},// 通过methods完成函数或方法的定义methods: {changeName() {// 在methods中要获取data中的属性需要通过this来完成this表示的是vue实例。this.name itcast},changeNameByArg(userName) {this.name userName}}})/script除了绑定鼠标的单击事件以外也可以绑定键盘的实际。
例如页面有有一个文本框用户在该文本框中输入内容按下回车键获取到用户输入的内容。
div idappspan{{name}}/span!-- 通过v-on来指定对应的事件然后后面跟上对应的方法名方法的定义在methods完成 --button clickchangeName更换姓名/buttonbutton clickchangeNameByArg(laowang)带参数的情况/button!--给文本框添加键盘事件--input typetext keydown.enterchangeUserName v-modelname //div在mehtods中定义changeUserName方法 // 通过methods完成函数或方法的定义methods: {changeName() {// 在methods中要获取data中的属性需要通过this来完成this表示的是vue实例。this.name itcast;},changeNameByArg(userName) {this.name userName;},//定义处理文本框键盘事件的方法。changeUserName() {console.log(this.name);},},在上面的案例中我们使用了按键的修饰符.enter,在官方文档中还有其它的按键修饰符如下所示
https://cn.vuejs.org/v2/guide/events.html#%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6与之相关的就是事件修饰符如下所示
https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6以上内容大家可以在课下的时候仔细看一下。
6、Class与Style绑定
这块主要内容主要与样式设置有关。
操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute所以我们可以用 v-bind 处理它们只需要通过表达式计算出字符串结果即可。不过字符串拼接麻烦且易错。因此在将 v-bind 用于 class 和 style 时Vue.js 做了专门的增强。表达式结果的类型除了字符串之外还可以是对象或数组。
下面先来看一下Class的绑定。
在列表渲染中给每个列表项添加对应的样式。 style.actived {background-color: #dddddd;}/style下面给li列表添加上面所定义的样式。 liv-for(item,index) in users:keyitem.id:class{actived:true}编号{{item.id}} 姓名:{{item.name}}---索引:{{index}}/li在上面的代码中我们可以看到给li标签绑定了class属性同时actived的值为true,表示给li添加actived样式。
现在有一个需求就是当鼠标移动到列表项上的时候更改对应的背景色。 liv-for(item,index) in users:keyitem.id:class{actived:selectItemitem}mousemoveselectItemitem在对class进行绑定的时候做了一个判断判断一下selectItem是否与item相等如果相等添加样式。
当鼠标移动到某个li 列表上的时候触发mousemove事件将item的值给selectItem.
在data中定义selectItem.
如下所示 data: {selectItem: ,users: [{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},],},完整 代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}/style/headbodydiv idappul!-- users表示数组item表示从数组中取出的对象,这个名字可以随意取 --!-- 注意 v-for必须结合key属性来使用它会唯一标识数组中的每一项未来当数组中的那一项改变的时候它会只更新那一项好处就是提升性能。注意key的值唯一不能重复 --!-- index表示数组的索引值该名字可以随意定义 --liv-for(item,index) in users:keyitem.id:class{actived:selectItemitem}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---索引:{{index}}/li/ul/divscript srcvue.js/scriptscriptnew Vue({el: #app,data: {selectItem: ,users: [{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},],},});/script/body
/html
下面我们再来看一下Style的绑定。
liv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---索引:{{index}}/li通过上面的代码可以看到通过绑定style的方式来处理样式是非常麻烦的。
7、条件渲染
v-if和v-show指令可以用来控制元素的显示和隐藏
下面我们先来看一下v-if的应用。
这里还是对用户数据进行判断。 div idappp v-ifusers.length0没有任何用户数据/pul v-else!-- users表示数组item表示从数组中取出的对象,这个名字可以随意取 --!-- 注意 v-for必须结合key属性来使用它会唯一标识数组中的每一项未来当数组中的那一项改变的时候它会只更新那一项好处就是提升性能。注意key的值唯一不能重复 --!-- index表示数组的索引值该名字可以随意定义 --!-- liv-for(item,index) in users:keyitem.id:class{actived:selectItemitem}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---索引:{{index}}/li --liv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---索引:{{index}}/li/ul/div在上面的代码中我们首先对users数组做了一个判断如果没有数据就在页面上展示“没有任何用户数据”
否则渲染整个列表。
上面是关于v-if的使用下面看一下v-show.
v-show 是通过css属性display控制元素显示元素总是存在的。
v-if:通过控制dom来控制元素的显示和隐藏,如果一开始条件为false,元素是不存在的。
什么时候使用v-show,什么时候使用v-if呢
如果需要频繁的控制元素的显示与隐藏建议使用v-show. 从而避免大量DOM操作提高性能。
而如果某个元素满足条件后渲染到页面中并且以后变化比较少可以使用v-if
8、计算属性
计算属性出现的目的是解决模板中放入过多的逻辑会让模板过重且难以维护的问题.
计算属性是根据data中已有的属性计算得到一个新的属性.
下面我们可以通过一个案例来学习一下计算属性、
在一个文本框中输入第一个名字第二个文本框中输入第二个名字然后展示全部名称。
bodydiv idappinput typetext v-modelfirstNameinput typetext v-modellastName!-- 这样是模板逻辑变得非常复杂不易维护 --div全名{{firstName lastName}}/divdiv全名{{fullName}}/div/divscriptvar vm new Vue({el: #app,data: {firstName: ,lastName: },// 创建计算属性通过computed关键字它是一个对象computed: {// 这里fullName就是一个计算属性它是一个函数但这个函数可以当成属性来使用fullName() {return this.firstName this.lastName}}})/script
/body了解了计算属性后下面对用户列表添加一个功能要求是计算总人数。
可以在ul列表下面添加如下的代码。 p总人数{{users.length个}}/p最终展示出了对应的人数但是这里在模板中做了运算在这里做了字符串拼接虽然计算简单但是最好还是通过计算属性来完成为了防止在模板中放入过多的逻辑计算这里可以使用计算属性来解决。
下面对代码进行改造 p!-- 总人数{{users.length个}} --总人数{{total}}/p计算属性实现 scriptnew Vue({el: #app,data: {selectItem: ,users: [{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},],},computed: {total() {// 计算属性是有缓存性如果值没有发生变化则页面不会重新渲染return this.users.length 个;},},});/scriptv通过上面的代码可以看到使用计算属性让界面变得更加的简洁。
使用计算属性还有一个好处
其实细心的话就会发现调用methods里的方法也能实现和计算属性一样的效果既然使用methods就可以实现那为什么还需要计算属性呢原因就是计算属性是基于他的依赖缓存的所依赖的还是data中的数据。一个计算属性所依赖的数据发生变化时他才会重新取值
也就是说只要相关依赖没有改变对此访问计算属性得到的是之前缓 存的结果不会多次执行。
下面我们测试一下 p!-- 总人数{{users.length个}} --总人数{{total}} 总人数{{total}}/p在上面的代码中我们使用total了两次。
下面在看一下关于计算属性中的代码修改 computed: {total() {console.log(aaa);// 计算属性是有缓存性如果值没有发生变化则页面不会重新渲染return this.users.length 个;},},这里我们通过console输出字符串aaa,但是在控制台上只是输出了一次因为第二次使用total的时候发现值没有变化所以直接从缓存中获取了对应的值。并没有重新进行计算这样带来的好处就是性能得到了提升。
下面我们换成methods函数的形式来看一下 p!-- 总人数{{users.length个}} --总人数{{total}} 总人数{{total}} 总人数{{getTotal()}}总人数{{getTotal()}}/p在上面的代码中调用了两次getTotal方法。
getTotal方法的实现如下 methods: {getTotal: function () {console.log(methods);return this.users.length 个;},},实现的方式是差不多的但是这里却执行了两次。注意由于本案例中给每一个li标签添加了 *mousemove*,所以只要鼠标移动到列表上就会导致页面重新渲染这时会不断的调用getTotal方法。
所以通过上面案例的演示可以明确的看出计算属性是有缓存的也就是所依赖的data属性中的数据没有变化那么是不会重新计算的。所以提升了对应的性能。
所以说在进行大量耗时计算的时候建议使用计算属性来完成。
如下代码 data: {selectItem: ,num: 100}在data中定义了num 属性并且初始值为100、
下面在计算属性中进行求和的运算代码实现如下 computed: {total() {console.log(aaa);// 计算属性是有缓存性如果值没有发生变化则页面不会重新渲染// return this.users.length 个;let count 0;for (let i 0; i this.num; i) {count i;}return count;},},通过演示可以发现计算属性只是在第一次调用的时候执行了一次后续由于所依赖的数据num没有发生变化所以即时调用多次也并没有重新进行计算而是获取上次计算的结果所以说在进行大量耗时计算的时候通过计算属性可以提升性能。
9、侦听器
侦听器就是侦听data中的数据变化如果数据一旦发生变化就通知侦听器所绑定方法来执行相应的操作。从这一点上与计算属性是非常类似的。
但是侦听器也有自己独有的应用场景。
执行异步或开销较大的操作。
下面先来看一下侦听器的基本使用
我们使用侦听器来统计总人数。 p总人数{{totalCount}}/p在data中定义totalCount属性。 data: {selectItem: ,num: 100,totalCount: 0} 使用watch来监听users数组的数据变化。 watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},}当users数组发生了变化后就会执行handler这个函数同时用于加上了immediate属性并且该属性的值为true表示的就是在初始化绑定的时候也会去执行侦听器。因为watch在初始化绑定的时候是不会执行的等到所监听的内容改变之后才会去侦听执行。
以上就是watch侦听器的基本使用但是通过这个案例我们发现还是使用计算属性来统计总人数更加的方便一些。
当然侦听器有自己的应用场景它的应用场景就是在执行异步请求或者进行开销比较大的操作的时候会使用侦听器。
下面我们在通过一个案例来体会一下watch侦听器的应用场景。
下面我们来看一个异步操作的情况。就是当用户在一个文本框中输入了用户名以后要将输入的用户名发送到服务端来检查该用户名是否已经被占用。
具体的实现代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title侦听器/title/headbodydiv idappdivspan用户名/span!--这里使用了lazy,保证当文本框失去焦点后才去执行对应操作--spaninput typetext v-model.lazyuname //spanspan{{message}}/span/div/divscript src./vue.js/scriptscriptconst vm new Vue({el: #app,data: {uname: ,message: ,},methods: {checkUserName: function (userName) {let that this;setTimeout(function () {if (userName admin) {that.message 用户名已经存在,请更改....;} else {that.message 该用户名可以使用.....;}}, 3000);},},watch: {uname: function (value) {//调用后台接口来验证用户名是被占用this.checkUserName(value);this.message 正在校验用户名....;},},});/script/body
/html
以上的案例就是通过watch来监听uname的值是否发生变化如果发生了变化就通过发送异步请求来检查uname中的值是否已经被占用。
通过以上的案例我们可以看到watch是允许异步操作的并且在我们得到最终的结果前可以设置中间状态这些都是计算属性无法做到的。
最后我们把计算属性与侦听器做一个总结看一下它们的应用场景。
第一点语境上的差异
watch适合一个值发生了变化对应的要做一些其它的事情适合一个值影响多个值的情形。
例如上面案例中的用户名检测这里是一个uname发生了变化但是这里做了很多其它的事情例如修改message的值发送异步请求。
而计算属性computed:一个值由其它的值得来其它值发生了变化对应的值也会变化适合做多个值影响一个值的情形。
例如如下代码
computed:{fullName(){return this.firstName this.lastName}
}第二点计算属性有缓存性。
由于这个特点我们在实际的应用中能用计算属性的会首先考虑先使用计算属性。
第三点侦听器选项提供了更加通用的方法适合执行异步操作或者较大开销操作。
10、生命周期简介
每个Vue实例在被创建时都要经过一系列的初始化过程例如需要设置数据的监听编译模板将实例挂载到DOM上并且在数据变化时更新DOM等这些过程统称为Vue实例的生命周期。同时在这个过程中也会运行一些叫做生命周期钩子的函数这给了用户在不同阶段添加自己的代码的机会。
下面我们来看一下这些钩子函数的应用。
通过一个异步获取列表数据的案例来查看这些生命周期的钩子函数应用。
在这里是通过异步的方式获取用户列表的数据。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}/style/headbodydiv idappp v-ifusers.length0没有任何用户数据/pul v-else!-- users表示数组item表示从数组中取出的对象,这个名字可以随意取 --!-- 注意 v-for必须结合key属性来使用它会唯一标识数组中的每一项未来当数组中的那一项改变的时候它会只更新那一项好处就是提升性能。注意key的值唯一不能重复 --!-- index表示数组的索引值该名字可以随意定义 --!-- liv-for(item,index) in users:keyitem.id:class{actived:selectItemitem}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---索引:{{index}}/li --liv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---索引:{{index}}/li/ulp!-- 总人数{{users.length个}} --!-- 总人数{{total}} 总人数{{total}} 总人数{{getTotal()}}总人数{{getTotal()}} --总人数{{totalCount}}/p/divscript srcvue.js/scriptscriptnew Vue({el: #app,data: {selectItem: ,num: 100,totalCount: 0,//指定users默认数据为一个空数组。users: [],},//组件实例已创建时执行created方法来调用getUserList方法发送异步请求获取数据//将获取到的数据交个users这个状态数组。async created() {const users await this.getUserList();this.users users;},methods: {getTotal: function () {console.log(methods);return this.users.length 个;},//在getUserList方法中模拟一个异步请求。getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},// computed: {// total() {// console.log(aaa);// // 计算属性是有缓存性如果值没有发生变化则页面不会重新渲染// // return this.users.length 个;// let count 0;// for (let i 0; i this.num; i) {// count i;// }// return count;// },// },});/script/body
/html
上面的代码还是对原有的“列表渲染”内容进行更改。
第一将users的值定义为空数组
第二定义getUserList方法在该方法中模拟异步操作最终返回的是一个Promise对象。
第三在created阶段调用getUserList方法来获取数据将获取到的数据赋值给users这个状态数组注意这里需要将created修改成async与await的形式。同时还要注意created的执行时机组件实例已创建时执行created方法。
现在已经对生命周期有了一个简单的了解下面我们继续探讨生命周期的内容。
11、生命周期探讨
在这一小节中我们看一下vue生命周期中其它的一些钩子函数内容。
其实Vue实例的生命周期主要分为三个阶段分别为
挂载(初始化相关属性,例如watch属性method属性) beforeCreatecreatedbeforeMountmounted 更新(元素或组件的变更操作) beforeUpdateupdated 销毁销毁相关属性 beforeDestroydestroyed
下面我们再来看一道面试题
关于Vue的生命周期下列哪项是不正确的()[单选题]
A、Vue 实例从创建到销毁的过程就是生命周期。
B、页面首次加载会触发beforeCreate, created, beforeMount, mounted, beforeUpdate, updated。
C、created表示完成数据观测属性和方法的运算初始化事件$el属性还没有显示出来。
D、DOM渲染在mounted中就已经完成了。
分析
选项A是没有问题的Vue实例从创建到销毁的过程就是生命周期。
关于B选项我们可以通过写一个程序来进行验证。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title生命周期/title/headbodydiv idapp{{foo}}/divscript src./vue.js/scriptscriptconst vm new Vue({el: #app,data: {foo: foo,},beforeCreate() {console.log(beforCreate);},created() {console.log(created);},beforeMount() {console.log(beforeMount);},mounted() {console.log(mounted);},beforeUpdate() {console.log(beforeUpdate);},updated() {console.log(updated);},beforeDestroy() {console.log(beforeDestroy);},destroyed() {console.log(destroyed);},});/scriptscript/script/body
/html
在上面的代码中我们将所有的钩子函数都添加上了然后打开浏览器看下执行结果
beforCreate
created
beforeMount
mounted以上就是初次加载时所执行的钩子函数并没有beforeUpdate 与updated,所以选项B是错误的。
那么beforeUpdate与updated什么时候会执行呢是在组件或者是元素更新的时候。
下面我们来测试一下看一下效果。
首先增加一个更新按钮 div idapp{{foo}}button clickupdate更新/button/div对应的update方法的实现如下 methods: {update: function () {this.foo hello;},},在update方法中修改了foo属性的值。打开浏览器单击“更新”按钮后看到的效果如下
beforeUpdate
updated通过以上的测试可以验证在更新元素的时候会执行在“更新”阶段的钩子函数。
下面我们在测试一下看一下“销毁”阶段的钩子函数的执行。 div idapp{{foo}}button clickupdate更新/buttonbutton clickdestroy销毁/button/div在上面的代码中增加了一个销毁的按钮对应的destroy方法的实现如下 methods: {update: function () {this.foo hello;},destroy: function () {//销毁资源this.$destroy();},},在destroy方法中调用了系统中的$destroy方法销毁了所有资源这时会触发销毁阶段的钩子函数所以这时会输出
beforeDestroy
destroyed这时如果你去单击“更新”按钮就会发现什么效果也没有了也就是无法完成元素的更新了因为元素已经被销毁了。
下面我们通过官方的生命周期图来再次看一下整个生命周期的流程。也是为了看一下上面所出题的C和D的选项是说法否正确。
beforeCreate: Vue实例初始化之后以及事件初始化以及组件的父子关系确定后执行该钩子函数一般在开发中很少使用
created: 在调用该方法之前初始化会被使用到的状态状态包括props,methods,data,computed,watch.
而且会实现对data中属性的监听也就是在created的时候数据已经和data属性进行了绑定。(放在data中的属性当值发生改变的时候视图也会改变)。同时也会对传递到组件中的数据进行校验。
所以在执行created的时候所有的状态都初始化完成我们也完全可以在该阶段发送异步的ajax请求获取数据。
但是在created方法中是无法获取到对应的的$el选项,也就是无法获取Dom. 所以说上题中选项c的说法是正确的。
如下代码所示 created() {console.log(created);console.log(el, this.$el);// undefinedconsole.log(data, this.$data);// 可以获取数据console.log(foo, this.foo);//可以获取数据},created方法执行完毕后下面会判断对象中有没有el选项。如果有继续执行下面的流程也就是判断是否有template选项如果没有el选项则停止整个生命周期的流程直到执行了vm.$mount(el)
后才会继续向下执行生命周期的流程。
下面我们测试一下 scriptconst vm new Vue({// el: #app, //去掉了el选项data: {foo: fooData,},methods: {update: function () {this.foo hello;},destroy: function () {//销毁资源this.$destroy();},},beforeCreate() {console.log(beforCreate);},created() {console.log(created);console.log(el, this.$el);console.log(data, this.$data);console.log(foo, this.foo);},beforeMount() {console.log(beforeMount);},mounted() {console.log(mounted);},beforeUpdate() {console.log(beforeUpdate);},updated() {console.log(updated);},beforeDestroy() {console.log(beforeDestroy);},destroyed() {console.log(destroyed);},});/script在上面的代码中我们将el选项去掉了运行上面的代码后我们发现执行完created方法后整个流程就停止了。
现在我们不添加el选项但是手动执行vm.$mount(el),也能够使暂停的生命周期进行下去。
如下代码所示 scriptconst vm new Vue({// el: #app,//去掉了el选项data: {foo: fooData,},methods: {update: function () {this.foo hello;},destroy: function () {//销毁资源this.$destroy();},},beforeCreate() {console.log(beforCreate);},created() {console.log(created);console.log(el, this.$el);console.log(data, this.$data);console.log(foo, this.foo);},beforeMount() {console.log(beforeMount);},mounted() {console.log(mounted);},beforeUpdate() {console.log(beforeUpdate);},updated() {console.log(updated);},beforeDestroy() {console.log(beforeDestroy);},destroyed() {console.log(destroyed);},});vm.$mount(#app);//添加了$mount方法/script运行上面的代码可以看到虽然vm对象中没有el参数但是通过$mount(el)动态添加的方式也能够使生命周期顺利进行。
我们继续向下看就是判断在对象中是否有template选项。
第一如果Vue实例对象中有template参数选项则将其作为模板编译成render函数来完成渲染。
第二如果没有template参数选项则将外部的HTML作为模板编译template也就是说template参数选项的优先级要比外部的HTML高
第三如果第一条,第二条件都不具备则报错
下面我们看一下添加template的情况。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title生命周期2/title/headbodyscript src./vue.js/scriptdiv idapp/divscriptconst vm new Vue({el: #app,template: pHello {{message}}/p,data: {message: vue,},});/script/body
/html以上是在Vue实例中添加template的情况。
那么这里有一个比较有趣的问题就是当模板同时放在template参数选项和外部HTML中会出现什么情况呢
如下代码所示
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title生命周期2/title/headbodyscript src./vue.js/scriptdiv idappp你好/p/divscriptconst vm new Vue({el: #app,template: pHello {{message}}/p,data: {message: vue,},});/script/body
/html在上面的代码中我们添加了template属性同时也在外部添加了模板内容但是最终在页面上显示的是Hello vue 而不是“你好”。就是因为template参数的优先级比外部HTML的优先级要高。
当然我们在开发中基本上都是使用外部的HTML模板形式因为更加的灵活。
在这里还需要你再次思考一个问题就是为什么先判断 el 选项然后在判断template选项呢
其实通过上面的总结我们是可以完全总结出来的。
就是因为Vue需要通过el的“选择器”找到对应的template.也就是说Vue首先通过el参数去查找对应的template.如果没有找到template参数则到外部HTML中查找找到后将模板编译成render
函数Vue的编译实际上就是指Vue把模板编译成render函数的过程。
下面我们继续看一下生命周期的流程图。
接下来会触发beforeMount这个钩子函数
在执行该钩子函数的时候虚拟DOM已经创建完成马上就要渲染了在这里可以更改data中的数据不会触发updated, 其实在created中也是可以更改数据也不会触发updated函数
测试代码如下 beforeMount() {console.log(beforeMount);console.log(beforeMount el, this.$el);console.log(data, this.$data);//this.foo abc; //修改数据console.log(foo, this.foo);},通过上面的代码我们可以获取el中的内容同时也可以修改数据。
但是这里需要注意的输入的el中的内容{{foo}}还没有被真正的数据替换掉。而且对应的内容还没有挂载到页面上。
下面执行了Create VM.$el and replace el with it
经过这一步后在模板中所写的{{foo}}会被具体的数据所替换掉。
所以下面执行mounted的时候可以看到真实的数据。同时整个组件内容已经挂载到页面中了数据以及真实DOM都已经处理好了可以在这里操作真实DOM了也就是在mounted的时候页面已经被渲染完毕了在这个钩子函数中我们可以去发送ajax请求。 mounted() {console.log(mounted);console.log(mounted el, this.$el);console.log(data, this.$data);console.log(foo, this.foo);}所以说最开始的问题中D选项DOM渲染在mounted中就已经完成了这句话的描述也是正确的。
下面继续看生命周期的流程如下图所示
当整个组件挂在完成后有可能会进行数据的修改当Vue发现data中的数据发生了变化会触发对应组件的重新渲染先后调用了beforeUpdate 和updated钩子函数。
在updated之前beoreUpdate之后有一个非常重要的操作就是虚拟DOM会重新构建也就是新构建的虚拟DOM与上一次的虚拟DOM树利用diff算法进行对比之后重新渲染。
而到了updated这个方法就表示数据已经更新完成dom也重新render完成。
下面如果我们调用了vm.$destroy方法后就会销毁所有的资源。
首先会执行beforeDestroy 这个钩子函数这个钩子函数在实例销毁前调用在这一步实例仍然可用。
在该方法中可以做一些清理的工作例如清除定时器等。
但是执行到destroyed钩子函数的时候Vue实例已经被销毁所有的事件监听器会被移除所有的子实例也会被销毁。
最后做一个简单的总结
beforeCreate( )// 该钩子函数执行时组件实例还未创建.
created()//组件初始化完毕各种数据可以使用可以使用ajax发送异步请求获取数据
beforeMounted()// 未执行渲染更新虚拟DOM完成真实DOM未创建
mounted()// 初始化阶段结束真实DOM已经创建可以发送异步请求获取数据,也可以访问dom元素
beforeUpdate()//更新前可用于获取更新前各种状态数据
updated()//更新后执行该钩子函数所有的状态数据是最新的。
beforeDestroy() // 销毁前执行可以用于一些定时器的清除。
destroyed()//组件已经销毁事件监听器被移除所有的子实例也会被销毁。以上为生命周期的内容。
12、组件化应用
12.1 组件概述
在这一小节中重点要理解的就是组件的编程思想。
组件表示页面中的部分功能包含自己的逻辑与样式可以组合多个组件实现完整的页面功能。
如下图所示
问题是如何确定页面中哪些内容划分到一个组件中呢
但你如何确定应该将哪些部分划分到一个组件中呢你可以将组件当作一种函数或者是对象来考虑函数的功能是单一的根据[单一功能原则]来判定组件的范围。也就是说一个组件原则上只能负责一个功能。如果它需要负责更多的功能这时候就应该考虑将它拆分成更小的组件。
当然在上图中我们发现’Name‘和’Price’ 表头 并没有单独的划分到一个组件中主要考虑的是功能简单就是展示的作用所以没有划分到单独一个组件中。如果该表头具有了一些比较复杂的功能例如排序。那么这里可以单独的将表头内容划分到一个组件中。
组件有什么特点呢
可复用、维护、可组合
可复用每个组件都是具有独立功能的它可以被使用在多个场景中。
可组合一个组件可以和其它的组件一起使用或者可以直接嵌套在另一个组件内部。
可维护每个组件仅仅包含自身的逻辑更容易被理解和维护。
下面看一下怎样创建组件
12.2 组件的基本使用
组件具体的创建过程如下 Vue.component(index, {template: div我是首页的组件/div})第一个参数指定了所创建的组件的名字第二个参数指定了模板。
组件创建好以后具体的使用方式如下
div idappindex/index
/div注意1. 模板template中只能有一个根节点;2. 组件的名字如果采用驼峰命令的话在使用的时候就要加上 “-”比如组件名字叫indexA那么在使用的时候就叫index-a。
例如 Vue.component(componentA, {template: div创建一个新的组件/div})组件的使用 component-a/component-a在Vue实例中所使用的选项在组件中都可以使用**但是要注意data,在组件中使用时必须是一个函数。**
下面创建一个about组件。 Vue.component(about, {template: div{{msg}}button clickshowMsg单击/button/div,data() {return {msg: 大家好}},methods: {showMsg() {this.msg 关于组件}}})组件的使用如下 about/about在组件中关于data不是一个对象而是一个函数的原因官方文档有明确的说明
https://cn.vuejs.org/v2/guide/components.html
组件创建完整的代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0meta http-equivX-UA-Compatible contentieedgetitle组件创建/titlescript src./vue.js/script
/headbodydiv idappcomponent-a/component-aindex/indexindex/indexabout/about/divscriptVue.component(componentA, {template: div创建一个新的组件/div})Vue.component(index, {template: div我是首页的组件/div})Vue.component(about, {template: div{{msg}}button clickshowMsg单击/button/div,data() {return {msg: 大家好}},methods: {showMsg() {this.msg 关于组件}}})var vm new Vue({el: #app,data: {}})/script
/body/html在使用组件的时候需要注意以下几点内容
第一点data必须是一个函数
关于这一点官方文档有比较详细清楚的说明https://cn.vuejs.org/v2/guide/components.html
第二组件模板中必须有一个跟元素。
第三组件模板内容可以使用模板字符串。 Vue.component(about, {template: div{{msg}}button clickshowMsg单击/button/div,data() {return {msg: 大家好,};},methods: {showMsg() {this.msg 关于VUE组件;},},});在上面的代码中我们在组件的模板中使用类模板字符串这样就可以调整对应的格式例如换行等。
第四现在我们创建的组件是全局组件可以在其它组件中使用。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title组件基本使用/title/headbodydiv idappindex/indexcomponent-a/component-aabout/about!-- 使用HelloWorld组件 --hello-world/hello-world/divscript src./vue.js/scriptscriptVue.component(index, {template: div我是Index组件/div,});// 创建了HelloWorld组件Vue.component(HelloWorld, {data() {return {msg: Hello World,};},template: div{{ msg}}/div,});// 使用HelloWorld组件Vue.component(componentA, {template: div我是一个新的组件:HelloWorld/HelloWorld/div,});Vue.component(about, {template: div{{msg}}button clickshowMsg单击/button/div,data() {return {msg: 大家好,};},methods: {showMsg() {this.msg 关于VUE组件;},},});const vm new Vue({el: #app,data: {},});/script/body
/html
在上面的代码中我们又创建了一个HelloWorld组件并且在componentA组件中去使用了HelloWorld组件这里还需要注意的一点就是在componentA这个组件中使用HelloWorld这个组件的时候可以使用驼峰命名的方式但是在div idapp/div这个普通的标签模板中必须使用短横线的方式才能使用组件。
12.3 局部组件注册
我们可以在一个组件中再次注册另外一个组件这样就构成了父子关系。
可以通过components 来创建对应的子组件。
组件的创建过程如下
scriptVue.component(father, {template: divp我是父组件/pson/son/div,components: {// 创建一个子组件son: {template: p我是子组件/p}}})var vm new Vue({el: #app,data: {}})/script组件的使用 div idappfather/father/div完整代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0meta http-equivX-UA-Compatible contentieedgetitle父子组件创建/titlescript src./vue.js/script
/headbodydiv idappfather/father/divscriptVue.component(father, {template: divp我是父组件/pson/son/div,components: {// 创建一个子组件son: {template: p我是子组件/p}}})var vm new Vue({el: #app,data: {}})/script
/body
/html在上面的代码中我们是在全局的father组件中又创建了一个子组件son.
那么son这个子组件也就是一个局部的组件。也就是它只能在father组件中使用。
当然我们在father中定义子组件son的时候直接在其内部构件模板内容这样如果代码非常多的时候就不是很直观。
所以这里我们可以将son组件单独的进行定义然后在father组件中进行注册。
改造后的代码如下所示
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title局部组件/titlescript src./vue.js/script/headbodydiv idappfather/father/divscriptconst son {data() {return {msg: Hello 我是子组件,};},template: div{{msg}}/div,};Vue.component(father, {template: divp我是父组件/pson/son/div,components: {// 创建一个子组件// son: {// template: p我是子组件/p,// },son: son,},});var vm new Vue({el: #app,data: {},});/script/body
/html
在上面的代码中我们将son组件单独的进行了定义这时注意写法是一个对象的格式在对象中包含了关于组件很重要的内容为data函数与template属性。
同时在father组件中通过components属性完成了对son组件的注册。
我们说过son组件是一个局部的组件那么只能在其注册的父组件中使用。
现在我们可以测试一下
完整代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title局部组件/titlescript src./vue.js/script/headbodydiv idappfather/father!-- 使用ComponentA组件 --component-a/component-a/divscriptconst son {data() {return {msg: Hello 我是子组件,};},template: div{{msg}}/div,};//定义ComponentA组件Vue.component(ComponentA, {template: divson/son/div,});Vue.component(father, {template: divp我是父组件/pson/son/div,components: {// 创建一个子组件// son: {// template: p我是子组件/p,// },son: son,},});var vm new Vue({el: #app,data: {},});/script/body
/html
在上面的代码中我们又创建了一个全局的组件ComponentA,并且在该组件中使用了son组件注意这里没有在ComponentA中使用components来注册son组件而是直接使用。同时在div idapp/div中使用了ComponentA组件。这时在浏览器中打开上面的程序会出现错误。
如果现在就想在ComponentA组件中使用son组件就需要使用components来注册。 Vue.component(ComponentA, {template: divson/son/div,components: {son: son,},});现在在ComponentA组件中已经注册了son组件这时刷新浏览器就不会出错了。
在上面这些案例中我们是在一个全局的组件中注册一个局部的组件其实我们也可以在Vue实例中
注册对应的局部组件。因为我们也可以将vue实例作为一个组件。
详细代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title局部组件/titlescript src./vue.js/script/headbodydiv idappfather/fathercomponent-a/component-ahello-msg/hello-msg/divscriptconst son {data() {return {msg: Hello 我是子组件,};},template: div{{msg}}/div,};// 定义HelloMsg组件const HelloMsg {data() {return {msg: Hello World,};},template: div{{msg}}/div,};Vue.component(ComponentA, {template: divson/son/div,components: {son: son,},});Vue.component(father, {template: divp我是父组件/pson/son/div,components: {// 创建一个子组件// son: {// template: p我是子组件/p,// },son: son,},});var vm new Vue({el: #app,data: {},components: {hello-msg: HelloMsg,},});/script/body
/html
在上面的代码中我们又创建了一个组件HelloMsg
然后将HelloMsg组件注册到了 Vue实例中注意在进行注册的时候的语法格式。
左侧为组件的名称由于这个组件创建的时候采用的是驼峰命名的方式所以组件的名称采用短横线的方式。
右侧为组件的内容。
下面就可以在其div idapp/div中使用了。
同理在其他的组件中是无法使用HelloMsg组件的。
13、组件通信
13.1 父组件向子组件传值
当我们将整个页面都拆分了不同的组件以后这样就会涉及到组件之间的数据传递问题。
常见的组件的通信可以分为三类
第一类: 父组件向子组件传递数据
第二类: 子组件向父组件传递数据
第三类兄弟组件的数据传递。
下面我们先来看一下父组件向子组件传递数据的情况
第一子组件内部通过props接收传递过来的值。
Vue.component(menu-item,{props:[title] // props后面跟一个数组数组中的内容为字符串这个字符串可以当做属性类使用。template:div{{title}}/div
})第二: 父组件通过属性将值传递给子组件
menu-item title向子组件传递数据 /menu-item
menu-item :titletitle/menu-item !--可以使用动态绑定的方式来传值--下面看一下具体的案例演示
bodydiv idappfather/father/divscript// 创建一个父组件Vue.component(father, {// 2、在使用子组件的地方通过v-bind指令来给子组件中的props赋值。template: divp我是父组件/pson :myNamemySonName/son/div,data() {return {mySonName: 小强}},components: {// 创建一个子组件// 1.声明props,它的作用是用来接收父组件传递过来的数据。// props可以跟一个数组数组里面的内容可以是字符串这个字符串可以当属性来使用。son: {props: [myName],template: p我是子组件,我的名字叫{{myName}}/p}}})var vm new new Vue({el: #app,data: {}})/script
/body下面我们在看一个例子这个例子是前面我们写的关于局部组件的案例我们在这个案例的基础上实现组件的传值。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title局部组件/titlescript src./vue.js/script/headbodydiv idappfather/fathercomponent-a/component-ahello-msg title你好 :pcontentcontent/hello-msg/divscriptconst son {data() {return {msg: Hello 我是子组件,};},template: div{{msg}}/div,};// 定义HelloMsg组件const HelloMsg {props: [title, pcontent],data() {return {msg: Hello World,};},template: div{{msg----------title-----------pcontent}}/div,};Vue.component(ComponentA, {template: divson/son/div,components: {son: son,},});Vue.component(father, {template: divp我是父组件/pson/son/div,components: {// 创建一个子组件// son: {// template: p我是子组件/p,// },son: son,},});var vm new Vue({el: #app,data: {content: 来自父组件中的内容,},components: {hello-msg: HelloMsg,},});/script/body
/html
在上面的代码中我们首先给hello-msg 这个组件传递了一个属性title,该属性的值是固定的。在对应的HelloMsg组件内容定义props,来接收传递过来的title属性的值。然后在template模板中展示title的值。
接下来又在vue实例中指定了一个content的属性下面要将该属性的值传递给HelloMsg组件。 hello-msg title你好 :pcontentcontent/hello-msg这里需要动态绑定的方式将content的值传递到HelloMsg组件。这里动态绑定的属性为pcontent,所以在HelloMsg组件内部需要在props的数组中添加一个pcontent,最后在template模板中展示出pcontent的内容。 // 定义HelloMsg组件const HelloMsg {props: [title, pcontent],data() {return {msg: Hello World,};},template: div{{msg----------title-----------pcontent}}/div,};通过上面的案例我们可以看到在子组件中可以使用props来接收父组件中传递过来的数据。
但是props在进行命名的时候也是有一定的规则的。
如果在props中使用驼峰形式模板中需要短横线的形式如下代码案例所示
Vue.component(menu-item,{//在JavaScript中是驼峰形式props:[menuTitle],template:div{{menuTitle}}/div
})
!--在html中是短横线方式---menu-item menu-titlehello world/menu-item下面看一下具体的代码演示
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title组件传值/title/headbodydiv idappmenu-item :menu-titleptitle/menu-item/divscript src./vue.js/scriptscriptVue.component(menu-item, {props: [menuTitle],template: div来自{{menuTitle}}/div,});const vm new Vue({el: #app,data: {ptitle: 父组件中的数据,},});/script/body
/html
下面再来看一下props属性值的类型。
props 可以接收各种类型的值。
如下
字符串String
数值(Number)
布尔值(Boolean)
数组(Array)
对象(Object)
下面将上面的类型都演示一下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /titleprops类型/title/headbodydiv idappmenu-item:strstr:num10btrue:marrarr:objobj/menu-item/divscript src./vue.js/scriptscriptVue.component(menu-item, {props: [str, num, b, marr, obj],template: divdiv{{str}}/divdiv{{typeof num}}/divdiv{{typeof b}}/divdivulli :keyitem.id v-foritem in marr{{item.userName}}/li/ul/divdiv姓名 {{obj.name}}年龄:{{obj.age}}/div/div,});const vm new Vue({el: #app,data: {str: hello,arr: [{ id: 1, userName: zhangsan },{id: 2,userName: lisi,},],obj: {name: wangwu,age: 18,},},});/script/body
/html
在上面的代码中向menu-item组件中传递了各种类型的数据。
注意 menu-item :strstr :num10 btrue :marrarr/menu-item在上面的代码中:num10表示传递的是数字如果写成num10 表示传递的是字符
同理btrue传递的是字符如果修改成:btrue表示传递的是布尔类型。
最后还传递了数组类型与对象类型的内容。
13.2 子组件向父组件传值
第一子组件通过自定义事件向父组件传递信息。
button v-on:click$emit(countSum) 计算/button第二父组件监听子组件的事件
menu-item v-on:countSumsum1/menu-item具体的实现步骤如下
1、构建基本的结构 div idapp /div
var vm new Vue({el: #app,data: {}})2、构建相应的父组件。 Vue.component(father, {template: div我的儿子叫{{mySonName}}/div,data() {return {mySonName: }}}3、 构建相应的子组件, 并且单击子组件中的按钮给父组件传值。 Vue.component(father, {template: div我的儿子叫{{mySonName}}/div,data() {return {mySonName: }},components: {son: {data() {return {myName: 小强}},template: button clickemitMyName我叫{{myName}}/button,methods: {emitMyName() {// 子组件传值给父组件需要用到$emit()方法这个方法可以传递两个参数一个是事件名称一个是需要传递的数据this.$emit(tellMyFatherMyName, this.myName)}}}}}4、父组件接收子组件传递过来的数据。
注意在父组件中引用子组件同时指定在子组件中定义的事件。 Vue.component(father, {template: div我的儿子叫{{mySonName}}son tellMyFatherMyNamegetMySonName/son/div,data() {return {mySonName: }},methods: {getMySonName(data) {this.mySonName data;}}}5、组件使用 div idappfather/father/div6、完整代码如下
bodydiv idappfather/father/divscriptVue.component(father, {template: div我的儿子叫{{mySonName}}son tellMyFatherMyNamegetMySonName/son/div,data() {return {mySonName: }},methods: {getMySonName(data) {this.mySonName data;}},components: {son: {data() {return {myName: 小强}},template: button clickemitMyName我叫{{myName}}/button,methods: {emitMyName() {// 子组件传值给父组件需要用到$emit()方法这个方法可以传递两个参数一个是事件名称一个是需要传递的数据this.$emit(tellMyFatherMyName, this.myName)}}}}})var vm new new Vue({el: #app,data: {}})/script
/body13.3 兄弟组件之间数据传递
兄弟组件传值通过事件总线完成。
1、定义父组件并且在父组件中完成两个兄弟组件的创建。 scriptVue.component(father, {template: divson/sondaughter/daughter/div,components: {son: {data() {return {mySisterName: }},template: div我妹妹叫{{mySisterName}}/div},daughter: {data() {return {myName: 小雪}},template: button clickemitMyName告诉哥哥我叫{{myName}}/button,methods: {emitMyName() {}}}}})var vm new Vue({el: #app,data: {}})/script2、创建事件总线
通过事件总线发射一个事件名称和需要传递的数据 。 // 创建一个空的vue实例作为事件总线var eventbus new Vue()daughter: {data() {return {myName: 小雪}},template: button clickemitMyName告诉哥哥我叫{{myName}}/button,methods: {emitMyName() {// 通过事件总线发射一个事件名称和需要传递的数据eventbus.$emit(tellBroMyName, this.myName)}}}
3、通过eventbus的$on()方法去监听兄弟节点发射过来的事件 son: {data() {return {mySisterName: }},template: div我妹妹叫{{mySisterName}}/div,mounted() {// 通过eventbus的$on()方法去监听兄弟节点发射过来的事件// $on有两个参数一个是事件名称一个是函数该函数的默认值就是传递过来的数据eventbus.$on(tellBroMyName, data {this.mySisterName data})}},4、组件的使用 div idappfather/father/div5、完整的代码如下
bodydiv idappfather/father/divscript// 创建一个空的vue实例作为事件总线var eventbus new Vue()Vue.component(father, {template: divson/sondaughter/daughter/div,components: {son: {data() {return {mySisterName: }},template: div我妹妹叫{{mySisterName}}/div,mounted() {// 通过eventbus的$on()方法去监听兄弟节点发射过来的事件// $on有两个参数一个是事件名称一个是函数该函数的默认值就是传递过来的数据eventbus.$on(tellBroMyName, data {this.mySisterName data})}},daughter: {data() {return {myName: 小雪}},template: button clickemitMyName告诉哥哥我叫{{myName}}/button,methods: {emitMyName() {// 通过事件总线发射一个事件名称和需要传递的数据eventbus.$emit(tellBroMyName, this.myName)}}}}})var vm new Vue({el: #app,data: {}})/script
/body
14、组件插槽应用
14.1 插槽基本使用
生活中的插槽
其实我们生活中有很多很多的插槽。比如电脑的USB插槽、插板中的电源插槽等等。每个插槽都有它们之间的价值。比如电脑的USB插槽可以用来插U盘链接鼠标链接手机、音响等等通过这些插槽大大拓展了原有设备的功能。
组件中的插槽
组件中的插槽让使用者可以决定组件内部的一些内容到底展示什么也就是插槽可以实现父组件向子组件传递模板内容。具有插槽的组件将会有更加强大的拓展性
下面看一个实际应用的例子来体会一下插槽的引用场景。
三个页面中都有导航栏基本结构都是一样的左中右分别有一个东西只是显示的内容不同而已。那我们如何来实现这种结构相似但是内容不同呢 你一定是想着直接定义三个组件然后在模板中分别显示不同的内容对不对恭喜你你就快要被炒了。 首先如果我们封装成三个组件显然不合适比如每个页面都有返回这部分的内容我们就要重复去封装 其次如果我们封装成一个还是不合理因为有些左侧是菜单栏有些中间是搜索框有些是文字。 那我们该怎么办呢其实很简单用组件插槽。
上面最佳的解决办法是将共性抽取到组件中将不同暴露给插槽一旦我们使用了插槽就相当于预留了空间空间的内容取决于使用者
如下图所示
通过上图我们可以在父组件中使用子组件同时由于在子组件中创建插槽slot也就是相当于预留了空间这时在父组件中使用子组件时可以传递不同的内容。
下面看一下插槽的应用
基本使用方式
第一确定插槽的位置
Vue.component(alert-box,{template:div classdemo-alert-boxstrong子组件/strongslot/slot/div})在子组件中通过slot确定出插槽的位置。
第二插槽内容
alert-boxHello World/alert-box向插槽中传递内容。
下面看一下具体的代码
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title插槽基本使用/title/headbodydiv idappalert-box程序出现了bug/alert-boxalert-box程序出现了警告/alert-box/divscript src./vue.js/scriptscriptVue.component(alert-box, {template: divstrongERROR:/strongslot/slot/div ,});const vm new Vue({el: #app,data: {},});/script/body
/html
通过上面的代码我们可以看到在alert-box这个组件中定义了一个插槽也就是预留了一个位置下面使用该组件的时候都可以向该插槽中传递数据。而strong标签中的内容就相当于是一个公共的内容了。
当然在插槽中也是可以添加默认的内容的。 div idappalert-box程序出现了bug/alert-boxalert-box程序出现了警告/alert-boxalert-box/alert-box/divscript src./vue.js/scriptscriptVue.component(alert-box, {template: divstrongERROR:/strongslot默认内容/slot/div ,});const vm new Vue({el: #app,data: {},});/script在上面的代码中我们给插槽添加了默认的内容如果在使用alert-box组件的时候没有给插槽传递值就会展示插槽中的默认内容。
14.2 具名插槽
所谓的具名插槽就是有名字的插槽。
第一插槽定义
div classcontainerheaderslot nameheader/slot/headermainslot/slot/mainfooterslot namefooter/slot/footer
/div第二插槽内容
base-layouth1 slotheader 标题内容/h1p主要内容 /p p主要内容 /p p slotfooter底部内容/p
/base-layout下面我们来看一下具体的代码实现
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title具名插槽/title/headbodydiv idappbase-layoutp slotheader头部内容/pp主要内容1/pp主要内容2/pp slotfooter底部信息/p/base-layout/divscript src./vue.js/scriptscriptVue.component(base-layout, {template: divheaderslot nameheader/slot/headermainslot/slot/main footerslot namefooter/slot/footer /div ,});const vm new Vue({el: #app,data: {},});/script/body
/html
在上面的代码中 p slotheader头部内容/p会插入到base-layout 组件的header这个插槽中。 p slotfooter底部信息/p会插入到footer这个插槽中。
剩余的内容会插入到默认的没有名称的插槽内。
在上面的应用中有一个问题就是我们把插槽的名称给了某个html标签例如p标签这样就只能将该标签插入到插槽中。
但是在实际的应用中有可能需要向插槽中插入大量的内容这时就需要用到template标签。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title具名插槽/title/headbodydiv idapp!-- base-layoutp slotheader头部内容/pp主要内容1/pp主要内容2/pp slotfooter底部信息/p/base-layout --base-layouttemplate slotheaderdiv标题名称/divdiv标题区域的布局/div/templatediv中间内容区域的布局实现/divtemplate slotfooterdiv底部信息/divdiv对底部内容区域进行布局/div/template/base-layout/divscript src./vue.js/scriptscriptVue.component(base-layout, {template: divheaderslot nameheader/slot/headermainslot/slot/main footerslot namefooter/slot/footer /div ,});const vm new Vue({el: #app,data: {},});/script/body
/html
在上面的代码中我们给template标签添加了插槽的名称并且在template标签中嵌入了其它的多个标签从而完成布局。
在这里可以统一查看浏览器端所生成的代码结构。
14.3 作用域插槽
应用场景父组件对子组件的内容进行加工处理。这也是作用域插槽的一个很重要特性
下面我们通过一个例子来体会一下这句话的作用。
首先我们先创建一个用户列表。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title作用域插槽/title/headbodydiv idappuser-list :listuserList/user-list/divscript src./vue.js/scriptscriptVue.component(user-list, {props: [list],template: divulli :keyitem.id v-foritem in list{{item.userName}}/li/ul /div,});const vm new Vue({el: #app,data: {userList: [{id: 1,userName: 张三,},{id: 2,userName: 李四,},{id: 3,userName: 王五,},],},});/script/body
/html
在上面的代码中我们首先创建了一个user-list组件在这个组件中接收父组件传递过来的用户数据通过循环的方式展示传递过来的用户数据。
现在这里有一个新的需求就是修改某个用户名的颜色让其高亮显示。这个需求应该怎样来处理呢
我们是否可以在子组件user-list中实现这个功能呢
虽然可以但是一般不建议你这么做因为一个组件创建好以后一般不建议修改。你可以想一下如果这个组件是其它人创建的而且很多人都在用如果直接修改这个子组件就会造成很多的问题。
所以这里还是从父组件中进行修改。也是通过父组件来决定子组件中的哪个用户名进行高亮显示。
下面对代码进行修改
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title作用域插槽/title/headbodydiv idappuser-list :listuserListtemplate slot-scopeslotPropsstrong v-ifslotProps.info.id2{{slotProps.info.userName}}/strongspan v-else{{slotProps.info.userName}}/span/template/user-list/divscript src./vue.js/scriptscriptVue.component(user-list, {props: [list],template: divulli :keyitem.id v-foritem in listslot :infoitem{{item.userName}}/slot/li/ul /div,});const vm new Vue({el: #app,data: {userList: [{id: 1,userName: 张三,},{id: 2,userName: 李四,},{id: 3,userName: 王五,},],},});/script/body
/html
通过上面的代码可以看到为了能够实现父组件决定子组件中哪个用户名能够高亮显示需要在设计子组件的时候为其添加对应的插槽。
template: divulli :keyitem.id v-foritem in listslot :infoitem{{item.userName}}/slot/li/ul /div,在子组件的template模板中添加了插槽同时为其动态绑定一个属性info(这个属性的名字是可以随意命名的)该属性的值为用户的信息。
绑定该属性的目的就是为了能够在父组件中获取用户的信息。
下面看一下父组件中的修改 div idappuser-list :listuserListtemplate slot-scopeslotPropsstrong v-ifslotProps.info.id2{{slotProps.info.userName}}/strongspan v-else{{slotProps.info.userName}}/span/template/user-list/div父组件在使用子组件user-list的时候这里为其添加了template这个标签而且这个标签的属性slot-scope是固定的为其指定了一个值为slotProps,该值中存储的就是从子组件中获取到的用户数据。
所以接下来通过slotProps获取info注意这里要与子组件中的slot属性保持一致中的用户数据。然后进行判断如果用户编号为2的为其加错否者正常展示。
通过以上的案例我们可以看到父组件通过作用域插槽实现了对子组件中数据的处理。其实这也就是为什么叫做作用域插槽的原因
是因为模板虽然是在父级作用域父组件中渲染的却能拿到子组件的数据。
14.4. 作用域插槽案例
下面我们通过一个列表的案例来体会一下作用域插槽的应用。
首先我们先来做一个基本的列表组件
这里我们首先使用的是具名插槽完成的如下代码所示
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title作用域插槽案例/title/headbodydiv idappmy-listtemplate slottitle用户列表/templatetemplate slotcontentulli v-foritem in listData :keyitem.id{{item.userName}}/li/ul/template/my-list/divscript src./vue.js/scriptscriptVue.component(my-list, {template: div classlistdiv classlist-titleslot nametitle/slot/divdiv classlist-contentslot namecontent/slot/div/div,});const vm new Vue({el: #app,data: {listData: [{ id: 1, userName: 张三 },{id: 2,userName: 李四,},{id: 3,userName: 王五,},],},});/script/body
/html
在上面的代码中我们在子组件my-list中使用了具名插槽。然后父组件在使用子组件my-list的时候可以通过template标签加上slot属性向具名插槽中传递数据。
虽然以上的写法满足了基本的需求但是作为组件的使用者这样的一个组件会让我们感觉非常的麻烦也就是我们在使用my-list这个组件的时候还需要自己去编写content区域的循环逻辑。这样就比较麻烦了下面对上面的代码在做一些修改。
为了解决这个问题我们可以把循环写到子组件中这样我们在使用的时候不需要写循环了只是传递数据就可以了这样就方便多了。其实这里我们就可以不用具名插槽了。
所以修改后的代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title作用域插槽案例/title/headbodydiv idappmy-list title用户列表 :contentlistData/my-list/divscript src./vue.js/scriptscriptVue.component(my-list, {props: [title, content],template: div classlistdiv classlist-title{{title}}/divdiv classlist-contentul classlist-contentli v-foritem in content :keyitem.id{{item.userName}}/li/ul /div/div,});const vm new Vue({el: #app,data: {listData: [{ id: 1, userName: 张三 },{id: 2,userName: 李四,},{id: 3,userName: 王五,},],},});/script/body
/html
在上面的代码中我们没有使用插槽直接将数据传递到子组件my-list中然后在该子组件中接收到数据并进行了循环遍历。
经过这一次的改造满足了我们前面所提到的易用性问题但是现在又有了新的问题组件的拓展性不好。
每次只能生成相同结构的列表一旦业务需要发生了变化组件就不再使用了。比如我现在有了新的需求在一个列表的每个列表项前面加上一个小的logo,我总不能又写一个新的组件来适应需求的变化吧
这里就可以使用作用域插槽来解决这个问题。
具体的实现代码如下所示
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title作用域插槽案例/title/headbodydiv idapp!-- 如果没有传递模板那么子组件的插槽中只会展示用户名 --my-list title用户列表 :contentlistData/my-list!-- 传递模板 --my-list title用户列表2 :contentlistDatatemplate slot-scopescopeimg src./one.png width20/span{{scope.item.userName}}/span/template/my-list/divscript src./vue.js/scriptscriptVue.component(my-list, {props: [title, content],template: div classlistdiv classlist-title{{title}}/divdiv classlist-contentul classlist-contentli v-foritem in content :keyitem.id!--这里将content中的每一项数据绑定到slot的itemb变量上在父组件中就可以获取到item变量-- slot :itemitem{{item.userName}}/slot/li/ul /div/div,});const vm new Vue({el: #app,data: {listData: [{ id: 1, userName: 张三 },{id: 2,userName: 李四,},{id: 3,userName: 王五,},],},});/script/body
/html
在上面的代码中我们首先在子组件my-list中添加了作用域的插槽。 ul classlist-contentli v-foritem in content :keyitem.id!--这里将content中的每一项数据绑定到slot的itemb变量上在父组件中就可以获取到item变量-- slot :itemitem{{item.userName}}/slot/li/ul 同时在父组件中使用对应的插槽 div idapp!-- 如果没有传递模板那么子组件的插槽中只会展示用户名 --my-list title用户列表 :contentlistData/my-list!-- 传递模板 --my-list title用户列表2 :contentlistDatatemplate slot-scopescopeimg src./one.png width20/span{{scope.item.userName}}/span/template/my-list/div再回到开始的问题作用域插槽到底是干嘛用的很显然它的作用就如官网所说的一样将组件的数据暴露出去。而这么做给了组件的使用者根据数据定制模板的机会组件不再是写死成一种特定的结构。
以上就是作用域插槽的应用需要你仔细体会。
那么在这里再次问一个问题就是在你所使用的Vue插件或者是第三方的库中有没有遇到使用作用域插槽的情况呢
其实比较典型的就是element-ui的table组件它就可以通过添加作用域插槽改变渲染的原始数据。
如下图所示
14.5 插槽应用总结
为什么要使用插槽
组件的最大特性就是复用性而用好插槽能大大提高组件的可复用能力。
组件的复用性常见情形如在有相似功能的模块中他们具有类似的UI界面通过使用组件间的通信机制传递数据从而达到一套代码渲染不同数据的效果。
然而这种利用组件间通信的机制只能满足在结构上相同渲染数据不同的情形假设两个相似的页面他们只在某一模块区域有不同的UI效果(例如前面所做的列表发现可以显示不同的ui效果)以上办法就做不到了。可能你会想使用 v-if 和 v-else来特殊处理这两个功能模块不就解决了很优秀解决了但不完美。极端一点假设我们有一百个这种页面就需要写一百个v-if、v-else-if、v-else来处理那组件看起来将不再简小精致维护起来也不容易。
而 插槽 “SLOT”就可以完美解决这个问题
什么情况下使用插槽
顾名思义插槽即往卡槽中插入一段功能块。还是举刚才的例子。如果有一百个基本相似只有一个模块功能不同的页面而我们只想写一个组件。可以将不同的那个模块单独处理成一个卡片在需要使用的时候将对应的卡片插入到组件中即可实现对应的完整的功能页。而不是在组件中把所有的情形用if-else罗列出来这里还是体会用户列表的案例
可能你会想那我把一个组件分割成一片片的插槽需要什么拼接什么岂不是只要一个组件就能完成所有的功能思路上没错但是需要明白的是卡片是在父组件上代替子组件实现的功能使用插槽无疑是在给父组件页面增加规模如果全都使用拼装的方式和不用组件又有什么区别例如用户列表案例中需要其他的显示方式需要在父组件中进行添加。因此插槽并不是用的越多越好。
插槽是组件最大化利用的一种手段而不是替代组件的策略当然也不能替代组件。如果能在组件中实现的模块或者只需要使用一次v-else 或一次v-else-ifv-else就能解决的问题都建议直接在组件中实现。
15、Vue组件化的理解
关于Vue组件的内容我们已经学习很多了那么你能谈一下对Vue组件化的理解吗
其实这也是一个比较常见的面试题。
当然这个问题的面是非常广的。可以通过以下几点来描述
定义组件是可复用的Vue实例准确讲它是VueComponent的实例继承自Vue
优点组件化可以增加代码的复用性可维护性和可测试性。
使用场景什么时候使用组件以下分类可以作为参数
第一通用组件实现最基本的功能具有通用性复用性。例如按钮组件输入框组件布局组件等。(Element UI组件库就是属于这种通用的组件)
第二业务组件用于完成具体的业务具有一定的复用性。例如登录组件轮播图组件。
第三页面组件组织应用各部分独立内容需要时在不同页面组件间切换例如商品列表页详情页组件。
如何使用组件 定义Vue.component()components选项 分类有状态组件(有data属性)functional 通信props$emit()/$on()provide/inject 内容分发slottemplatev-slot 使用及优化iskeep-alive异步组件(这些内容在后面的课程中会详细的讲解)
组件的本质
vue中的组件经历如下过程 组件配置 VueComponent实例 render() Virtual DOM DOM 所以组件的本质是产生虚拟DOM
关于这块内容在后面的课程中还会深入的探讨包虚拟dom,以及vue的源码。
16、常用API说明
16.1 Vue.set
向响应式对象中添加一个属性并确保这个新属性同样是响应式的且会触发视图更新。
使用方法Vue.set(target,propertyName,value)
下面通过一个案例来演示一下,这个案例是在以前所做的用户列表的案例上进行修改的
这里需求是给每个用户动态的添加身高。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}/style/headbodydiv idappp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ulp总人数{{totalCount}}/p/divscript srcvue.js/scriptscriptnew Vue({el: #app,data: {selectItem: ,num: 100,totalCount: 0,users: [],},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {c.height 0;});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});/script/body
/html
在上面的代码中我首先把列表中展示的内容做了一个修改这里不在显示索引值而是展示身高。 编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}但是我们知道在users中是没有height这个属性的所以下面可以动态添加这个属性。
所以在create方法中调用了batchUpdate方法来动态更新。 //组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},在methods中添加了batchUpdate方法。 //批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {c.height 0;});},
在上面的代码中对users进行遍历每遍历一次取出一个对象后动态添加一个属性height,并且初始值为0.
这样刷新浏览器可以看到对应的效果。
下面我们在做一个功能就是用户在一个文本框中输入一个身高值单击按钮统一把所有用户的身高进行更新。
首先在data中添加一个属性height,该属性会与文本框进行绑定。 data: {selectItem: ,num: 100,totalCount: 0,users: [],height: 0,},下面创建文本框以及更新按钮 pinput typetext v-model.numberheight /button clickbatchUpdate批量更新用户身高/button/p在这里我们需要在文本框中输入的值为数字类型所以添加了一个number的后缀。现在文本框与height属性绑定在一起了。下面单击按钮后还是去执行batchUpdate方法。 //批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {c.height this.height;});},这里我们可以看到我们是用文本框中输入的值更新了users数组中的height属性的值。
但是当我们在浏览器中单击按钮进行更新的时候发现不起作用。
因为现在动态所添加的height属性并不是响应式的。
但是当把鼠标移动到列表项的时候数据发生了变化就是因为这时触发了我们给列表所添加的mousemove
这个事件导致页面重新刷新这时发现数据发生变化了。
那么我们应该怎样解决这个问题呢
这就需要在batchUpdate方法中使用Vue.set()方法 batchUpdate() {this.users.forEach((c) {// c.height this.height;Vue.set(c, height, this.height);});},修改的代码含义就是通过Vue.set方法给users数组中每个对象设置一个height属性这时该属性就变成了响应式的同时把 data中的height属性的值赋值给height.
完整代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}/style/headbodydiv idapp!-- 批量更新身高 --pinput typetext v-model.numberheight /button clickbatchUpdate批量更新用户身高/button/pp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ulp总人数{{totalCount}}/p/divscript srcvue.js/scriptscriptnew Vue({el: #app,data: {selectItem: ,num: 100,totalCount: 0,users: [],height: 0,},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});/script/body
/html
16.2 Vue.delete
删除对象的属性如果对象是响应式的确保删除能触发更新视图。
使用方式Vue.delete(target,propertyName)
如果使用delete obj[property] 是不能更新页面的。
以上两个方法Vue.set()和Vue.delete()等同于以下两个实例方法。
vm.$set()
vm.$delete()vm 表示的是Vue的实例。
所以我们在batchUpdate中也可以采用如下的方式来批量更新用户的身高数据。 batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},16.3 vm.$on与vm.$emit
16.3.1 列表组件设计
这两个api在前面的课程中我们也已经讲解过主要用来实现:事件总线。
下面我们将这两个API应用到用户列表这个案例中。主要是把事件总线这个应用再次复习一下。
当然这里首先是把用户列表这个案例按照我们前面所学习的组件的知识进行拆分一下实现组件化的应用。
初步改造后的代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}/style/headbodydiv idapp!-- 批量更新身高 --pinput typetext v-model.numberheight /button clickbatchUpdate批量更新用户身高/button/p!-- 用户列表组件 --user-list :usersusers/user-listp总人数{{totalCount}}/p/divscript srcvue.js/scriptscript// 用户列表组件创建Vue.component(user-list, {data() {return {selectItem: ,};},props: {users: {type: Array,default: [],},},template: divp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ul/div,});new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});/script/body
/html
在上面的代码中我们首先创建了一个user-list组件该组件首先会通过props接收传递过来的用户数据。
在这里我们将props定义成了对象的形式这样更容易进行数据类型的校验同时还可以设置默认值。
接下来将原来定义在div idapp/div 中的用户列表要剪切到user-list组件的template属性中同时我们知道在列表中会用到selectItem属性所以在user-list的data中定义该属性父组件就不用在定义该属性了。
下面我们在div idapp/div中使用该组件,并且传递了用户数据。 !-- 用户列表组件 --user-list :usersusers/user-list现在用户列表的组件在这里我们就创建好了。
16.3.2 用户添加组件设计
下面我们在创建一个组件该组件封装了一个文本框和添加用户信息的按钮。
代码如下 //新增用户组件Vue.component(user-add, {data() {return {userInfo: ,};},template: divpinput typetext v-modeluserInfo v-on:keydown.enteraddUser //pbutton clickaddUser新增用户/button/div,methods: {addUser() {//将输入的用户数据通知给父组件来完成新增用户操作.this.$emit(add-user, this.userInfo);this.userInfo ;},},});在上面的代码中我们创建了user-add 这个组件该组件最终呈现的就是就是一个文本框与一个添加按钮。并且通过v-model将userInfo属性与文本框进行了绑定。同时单击按钮的时候执行addUser方法在该方法中通过$emit想父组件发送了一个事件同时将用户在文本框中输入的数据也传递过去。
然后清空文本框
下面看一下父组件的处理。 !-- 新增用户 --user-add add-useraddUser/user-add在div idapp/div 中使用user-add这个组件同时接受传递过来的事件add-user,然后执行addUser方法。
下面看一下addUser这个方法的具体实现。
在vue 实例的methods属性中添加addUser这个方法。
//添加用户的信息addUser(userInfo) {this.users.push({id: this.users[this.users.length - 1].id 1,name: userInfo,});},接受用户在文本框中输入的数据然后添加到users数组中。
完整代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}/style/headbodydiv idapp!-- 批量更新身高 --pinput typetext v-model.numberheight /button clickbatchUpdate批量更新用户身高/button/p!-- 新增用户 --user-add add-useraddUser/user-add!-- 用户列表组件 --user-list :usersusers/user-listp总人数{{totalCount}}/p/divscript srcvue.js/scriptscript//新增用户组件Vue.component(user-add, {data() {return {userInfo: ,};},template: divpinput typetext v-modeluserInfo v-on:keydown.enteraddUser //pbutton clickaddUser新增用户/button/div,methods: {addUser() {//将输入的用户数据通知给父组件来完成新增用户操作.this.$emit(add-user, this.userInfo);this.userInfo ;},},});// 用户列表Vue.component(user-list, {data() {return {selectItem: ,};},props: {users: {type: Array,default: [],},},template: divp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ul/div,});new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//添加用户的信息addUser(userInfo) {this.users.push({id: this.users[this.users.length - 1].id 1,name: userInfo,});},//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});/script/body
/html
16.3.3 自定义组件实现双向绑定
在上一个案例中我们创建了一个user-add这个组件完成用户信息的添加。
并且在该组件的内部维护了所添加的用户信息。
假如我不想让user-add这个组件来维护这个用户信息而是让父组件来维护应该怎样处理呢 !-- 新增用户 --user-add add-useraddUser v-modeluserInfo/user-add将userInfo的值给v-model.
所以在父组件中要定义userInfo new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,},下面看一下user-add组件的修改 Vue.component(user-add, {// data() {// return {// userInfo: ,// };// },props: [value],template: divpinput typetext :valuevalue inputonInput v-on:keydown.enteraddUser //pbutton clickaddUser新增用户/button/div,methods: {addUser() {//将输入的用户数据通知给父组件来完成新增用户操作.// this.$emit(add-user, this.userInfo);this.$emit(add-user);// this.userInfo ;},onInput(e) {this.$emit(input, e.target.value);},},});在user-add组件中定义props接收传递过来的值也就是userInfo的值会传递给value
下面修改user-add组件中的模板文本框绑定value值。通过给其添加input事件在文本框中输入值后调用onInput方法在该方法中获取用户在文本框中输入的值然后发送input事件。对应的值传递给父组件中的userInfo
同时单击“新增用户”按钮的时候执行addUser方法在该方法中发送事件add-user,也不需要传递数据了。
同时父组件中的addUser方法实现如下 addUser() {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;},
直接从data中获取userInfo的数据。
总结
以下的写法 user-add add-useraddUser v-modeluserInfo/user-add等价以下的写法 user-addv-bind:valueuserInfov-on:inputuserInfo $event
/user-add
也就是说v-model就是v-bind与v-on的语法糖。
在这里我们将userInfo的值给了value属性而value属性传递到了user-add组件中所以在user-add组件中要通过props来接收value属性的值。
在user-add组件的文本中输入内容后触发input 事件对应的会调用onInput方法在该方法中执行了 this.$emit(input, e.target.value);发送了input事件并且传递了用户在文本框中输入的值。
那很明显这时会触发下面代码中的input事件将传递过来的值给userInfo属性。
user-addv-bind:valueuserInfov-on:inputuserInfo $event
/user-add以上就是v-model的原理希望仔细体会这也是面试经常会被问到的问题。
16.3.4. 使用插槽完成内容分发
关于插槽的内容在前面的的课程中我们已经学习过了那么什么是内容分发呢
其实就是在使用组件的时候我们提供具体的数据内容然后这些内容会插入到组件内部插槽的位置这就是所谓的内容分发。
下面要做的事情就是创建一个信息的提示窗口。例如当添加用户成功后给出相应的提示。
首先先创建样式 style.actived {background-color: #dddddd;}.message-box {padding: 10px 20px;background-color: #4fc;border: 1px solid #42b;}.message-box-close {float: right;}/style下面创建对应的组件。 //创建弹出的组件Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。props: [show],template: div classmessage-box v-ifshowslot/slotspan classmessage-box-close关闭/span/div,});使用上面的组件 div idapp!-- 弹窗组件 --message :showisShow添加用户成功/message!-- 批量更新身高 --/div 在data 中定义isShow属性初始值为false. new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,isShow: false,},下面就是当用户完成添加的时候弹出该窗口。 //添加用户的信息addUser() {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;//完成用户添加后给出相应的提示信息this.isShow true;},在addUser方法中完成了用户信息的添加后将isShow的属性值设置为true.
这时弹出了对应的窗口。
下面要考虑的就是单击窗口右侧的“关闭”按钮将窗口关闭这个效果应该怎样实现。
首先给关闭按钮添加单击事件。
如下所示 //创建弹出的组件Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。props: [show],template: div classmessage-box v-ifshowslot/slotspan classmessage-box-close click$emit(close,false)关闭/span/div,});
当单击关闭按钮后会发送一个close事件同时传递的值为false.
下面回到父组件中对close事件进行处理。 !-- 弹窗组件 --message :showisShow closecloseWindow添加用户成功/message当close事件触发后执行closeWindow方法。 //关闭窗口closeWindow(data) {this.isShow data;},在closeWindow方法中根据子组件传递过来的值false,修改isShow属性的值这时isShow的值为false.这时窗口关闭。
下面要解决的问题就是在使用弹窗组件的时候不仅能传递窗口的内容还能传递其它的内容例如标题等。
那应该怎样处理呢?
这里可以使用具名插槽
代码如下 !-- 弹窗组件 --message :showisShow closecloseWindow!-- titile的插槽 --template v-slot:titleh2恭喜/h2/template!-- 默认插槽 --template添加用户成功/template/message下面修改一下message组件中的内容。 //创建弹出的组件Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。props: [show],template: div classmessage-box v-ifshow!--具名插槽--slot nametitle默认标题/slotslot/slotspan classmessage-box-close click$emit(close,false)关闭/span/div,});在上面定义message组件的时候指定了具名插槽名称为title.要与在父组件中使用message组件的时候指定的名称保持一致同时这里如果没有传递任何内容将会显示默认标题。
完整代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}.message-box {padding: 10px 20px;background-color: #4fc;border: 1px solid #42b;}.message-box-close {float: right;}/style/headbodydiv idapp!-- 弹窗组件 --message :showisShow closecloseWindow!-- titile的插槽 --template v-slot:titleh2恭喜/h2/template!-- 默认插槽 --template添加用户成功/template/message!-- 批量更新身高 --pinput typetext v-model.numberheight /button clickbatchUpdate批量更新用户身高/button/p!-- 新增用户 --user-add add-useraddUser v-modeluserInfo/user-add!-- 用户列表组件 --user-list :usersusers/user-listp总人数{{totalCount}}/p/divscript srcvue.js/scriptscript//创建弹出的组件Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。props: [show],template: div classmessage-box v-ifshow!--具名插槽--slot nametitle默认标题/slotslot/slotspan classmessage-box-close click$emit(close,false)关闭/span/div,});//新增用户组件Vue.component(user-add, {// data() {// return {// userInfo: ,// };// },props: [value],template: divpinput typetext :valuevalue inputonInput v-on:keydown.enteraddUser //pbutton clickaddUser新增用户/button/div,methods: {addUser() {//将输入的用户数据通知给父组件来完成新增用户操作.// this.$emit(add-user, this.userInfo);this.$emit(add-user);// this.userInfo ;},onInput(e) {this.$emit(input, e.target.value);},},});// 用户列表Vue.component(user-list, {data() {return {selectItem: ,};},props: {users: {type: Array,default: [],},},template: divp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ul/div,});new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,isShow: false,},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//关闭窗口closeWindow(data) {this.isShow data;},//添加用户的信息addUser() {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;//完成用户添加后给出相应的提示信息this.isShow true;},//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});/script/body
/html
16.3.5 vm.$on与vm.$emit应用
现在关于用户管理这个案例的一些组件拆分以及插槽的应用在这我们已经构建好了。
下面就看一下vm.$on与vm.$emit的应用。
根据前面的学习我们知道vm.$on与vm.$emit的典型应用就是事件总线。
也就是通过在Vue 原型上添加一个Vue实例作为事件总线实现组件间相互通信而且不受组件间关系的影响
Vue.prototype.$busnew Vue()在所有组件最上面创建事件总线
这样做的好处就是在任意组件中使用this.$bus访问到该Vue实例。
下面我们来看一下事件总线的用法。
首先我们这里先把事件总线创建出来。 //创建事件总线Vue.prototype.$bus new Vue();下面在创建一个警告的窗口也就是当单击“新增用户”按钮的时候如果用户没有填写用户名给出相应册错误提示。
在这里先把样式修改一下 style.actived {background-color: #dddddd;}.message-box {padding: 10px 20px;}.success {background-color: #4fc;border: 1px solid #42b;}.warning {background-color: red;border: 1px solid #42b;}.message-box-close {float: right;}/style然后创建出对应的窗口。
!-- 警告 --message :showshowWarn closecloseWindow classwarning!-- titile的插槽 --template v-slot:titleh2警告/h2/template!-- 默认插槽 --template请输入用户名/template/message注意在上面的代码中我们使用showWarn这个属性控制警告窗口的显示与隐藏。
同时为其添加了warning样式对应的成功的窗口需要添加success 样式。
同时在data中定义showWarn属性。
new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,isShow: false,showWarn: false, // 控制警告窗口的显示与隐藏},下面要修改的就是当单击新增用户按钮的时候对addUser方法的修改。 //添加用户的信息addUser() {if (this.userInfo) {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;//完成用户添加后给出相应的提示信息this.isShow true;} else {// 显示错误警告信息this.showWarn true;}},判断userInfo中是否有值如果没有值展示出错误警告信息。
通过浏览器进行测试。发现如果用户没有在文本框中输入用户名直接单击了“新增用户”这时给出了错误提示的窗口。
但是用户没有关闭错误提示的窗口而是直接在文本框中输入了用户名然后又点击了新增用户按钮这时“成功窗口”与“警告窗口”都显示出来了。
下面需要解决这个问题。
Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。props: [show],template: div classmessage-box v-ifshow!--具名插槽--slot nametitle默认标题/slotslot/slotspan classmessage-box-close click$emit(close,false)关闭/span/div,mounted() {//给总线绑定message-close事件//也就是监听是否有message-close事件被触发。this.$bus.$on(message-close, () {this.$emit(close, false);});},});在message组件加载完后给事件总线绑定了message-close事件当该事件触发后还是向父组件发送了close事件这一点与单击关闭按钮是一样的。
下面怎样触发总线的message-close事件呢
我们可以在窗口中添加一个“清空提示栏”按钮单击该按钮的时候可以触发message-close事件从而关闭提示窗口。 !-- 清空提示栏 --div classtoolbarbutton click$bus.$emit(message-close)清空提示栏/button/div单击清空提示栏按钮后触发事件总线的message-close事件。
最后完善一下closeWindow方法该方法控制整个提示窗口的关闭 //关闭窗口closeWindow(data) {this.isShow data;this.showWarn data;},完整代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}.message-box {padding: 10px 20px;}.success {background-color: #4fc;border: 1px solid #42b;}.warning {background-color: red;border: 1px solid #42b;}.message-box-close {float: right;}/style/headbodydiv idapp!-- 弹窗组件 --message :showisShow closecloseWindow classsuccess!-- titile的插槽 --template v-slot:titleh2恭喜/h2/template!-- 默认插槽 --template添加用户成功/template/message!-- 警告 --message :showshowWarn closecloseWindow classwarning!-- titile的插槽 --template v-slot:titleh2警告/h2/template!-- 默认插槽 --template请输入用户名/template/message!-- 清空提示栏 --div classtoolbarbutton click$bus.$emit(message-close)清空提示栏/button/div!-- 批量更新身高 --pinput typetext v-model.numberheight /button clickbatchUpdate批量更新用户身高/button/p!-- 新增用户 --user-add add-useraddUser v-modeluserInfo/user-add!-- 用户列表组件 --user-list :usersusers/user-listp总人数{{totalCount}}/p/divscript srcvue.js/scriptscript//创建事件总线Vue.prototype.$bus new Vue();//创建弹出的组件Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。props: [show],template: div classmessage-box v-ifshow!--具名插槽--slot nametitle默认标题/slotslot/slotspan classmessage-box-close click$emit(close,false)关闭/span/div,mounted() {//给总线绑定message-close事件//也就是监听是否有message-close事件被触发。this.$bus.$on(message-close, () {this.$emit(close, false);});},});//新增用户组件Vue.component(user-add, {// data() {// return {// userInfo: ,// };// },props: [value],template: divpinput typetext :valuevalue inputonInput v-on:keydown.enteraddUser //pbutton clickaddUser新增用户/button/div,methods: {addUser() {//将输入的用户数据通知给父组件来完成新增用户操作.// this.$emit(add-user, this.userInfo);this.$emit(add-user);// this.userInfo ;},onInput(e) {this.$emit(input, e.target.value);},},});// 用户列表Vue.component(user-list, {data() {return {selectItem: ,};},props: {users: {type: Array,default: [],},},template: divp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ul/div,});new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,isShow: false,showWarn: false, // 控制警告窗口的显示与隐藏},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//关闭窗口closeWindow(data) {this.isShow data;this.showWarn data;},//添加用户的信息addUser() {if (this.userInfo) {if (this.users.length 0) {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;//完成用户添加后给出相应的提示信息this.isShow true;}} else {// 显示错误警告信息this.showWarn true;}},//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});/script/body
/html
16.4 vm.$once与vm.$off
关于这两个方法大家只需要了解一下就可以了。
vm.$once 监听一个自定义事件但是只触发一次。一旦触发之后监听器就会被移除。
vm.$on(test, function (msg) { console.log(msg) })
vm.$off
移除自定义事件监听器。 如果没有提供参数则移除所有的事件监听器 如果只提供了事件则移除该事件所有的监听器 如果同时提供了事件与回调则只移除这个回调的监听器
vm.$off() // 移除所有的事件监听器
vm.$off(test) // 移除该事件所有的监听器
vm.$off(test, callback) // 只移除这个回调的监听器
16.5 ref 和vm.$refs
ref被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的$refs对象上如果在普通的DOM元素上使用引用指向的就是DOM元素;如果用在子组件上引用就指向组件的实例。
如下代码示例是用来设置输入框的焦点
input typetext refinp /
mounted(){//mounted之后才能访问到inpthis.$refs.inp.focus()
}下面在用户管理案例中看一下具体的实现效果。 //新增用户组件Vue.component(user-add, {// data() {// return {// userInfo: ,// };// },props: [value],template: divpinput typetext :valuevalue inputonInput v-on:keydown.enteraddUser refinp //pbutton clickaddUser新增用户/button/div,methods: {addUser() {//将输入的用户数据通知给父组件来完成新增用户操作.// this.$emit(add-user, this.userInfo);this.$emit(add-user);// this.userInfo ;},onInput(e) {this.$emit(input, e.target.value);},},mounted() {this.$refs.inp.focus();},});在上面的代码中我们首先给user-add组件模板中的文本框添加了ref属性。
然后在其所对应的mounted方法中通过$refs找到文本框然后为其添加焦点。
回到浏览器中刷新浏览器可以看到对应的文本框获取了焦点。
下面我们在将弹出窗口修改一下
下面修改一下message模板中的内容。 //创建弹出的组件Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。// props: [show],data() {return {show: false,};},template: div classmessage-box v-ifshow!--具名插槽--slot nametitle默认标题/slotslot/slotspan classmessage-box-close clicktoggle关闭/span/div,mounted() {//给总线绑定message-close事件//也就是监听是否有message-close事件被触发。this.$bus.$on(message-close, () {// this.$emit(close, false);this.toggle();});},methods: {toggle() {this.show !this.show;},},});
在上面的代码中取消了props而定义了data属性表明的含义就是整个窗口的状态的控制也就是提示窗口的显示与隐藏都是有自己控制而不是受外部传递的参数来进行控制了。
同时在该组件中添加了toggle方法修改对应的show的状态。
所以模板中按钮的单击事件触发以后调用的就是toggle方法也就是单击了窗口的右侧的关闭按钮是通过调用toggle方法来完成窗口的关闭。
同样事件message-close触发以后也是调用toggle方法来关闭窗口。
下面看一下关于message模板的使用。 !-- 弹窗组件 --message refmsgSuccess classsuccess!-- titile的插槽 --template v-slot:titleh2恭喜/h2/template!-- 默认插槽 --template添加用户成功/template/message在上面的代码中我们为message组件添加了ref属性。
同理表示警告的窗口也需要添加ref的属性。 !-- 警告 --message refmsgWaring classwarning!-- titile的插槽 --template v-slot:titleh2警告/h2/template!-- 默认插槽 --template请输入用户名/template/message关于data中定义的isShow与showWarn就可以取消了。
data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,// isShow: false,// showWarn: false, // 控制警告窗口的显示与隐藏},当用户点击“新增用户”按钮的时候执行addUser方法下面也需要对该方法进行如下修改 //添加用户的信息addUser() {if (this.userInfo) {if (this.users.length 0) {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;//完成用户添加后给出相应的提示信息// this.isShow true;this.$refs.msgSuccess.toggle();}} else {// 显示错误警告信息// this.showWarn true;this.$refs.msgWaring.toggle();}},在上面的代码中我们都是通过$ref 找到对应的窗口然后调用toggle方法来修改对应的状态。
因为我们前面讲过如果ref用在子组件上引用就指向组件的实例.所以可以调用组件内部的toggle方法。
完整代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}.message-box {padding: 10px 20px;}.success {background-color: #4fc;border: 1px solid #42b;}.warning {background-color: red;border: 1px solid #42b;}.message-box-close {float: right;}/style/headbodydiv idapp!-- 弹窗组件 --message refmsgSuccess classsuccess!-- titile的插槽 --template v-slot:titleh2恭喜/h2/template!-- 默认插槽 --template添加用户成功/template/message!-- 警告 --message refmsgWaring classwarning!-- titile的插槽 --template v-slot:titleh2警告/h2/template!-- 默认插槽 --template请输入用户名/template/message!-- 清空提示栏 --div classtoolbarbutton click$bus.$emit(message-close)清空提示栏/button/div!-- 批量更新身高 --pinput typetext v-model.numberheight /button clickbatchUpdate批量更新用户身高/button/p!-- 新增用户 --user-add add-useraddUser v-modeluserInfo/user-add!-- 用户列表组件 --user-list :usersusers/user-listp总人数{{totalCount}}/p/divscript srcvue.js/scriptscript//创建事件总线Vue.prototype.$bus new Vue();//创建弹出的组件Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。// props: [show],data() {return {show: false,};},template: div classmessage-box v-ifshow!--具名插槽--slot nametitle默认标题/slotslot/slotspan classmessage-box-close clicktoggle关闭/span/div,mounted() {//给总线绑定message-close事件//也就是监听是否有message-close事件被触发。this.$bus.$on(message-close, () {// this.$emit(close, false);//当警告窗口和提示信息的窗口展示出来了才关闭。if (this.show) {this.toggle();}});},methods: {toggle() {this.show !this.show;},},});//新增用户组件Vue.component(user-add, {// data() {// return {// userInfo: ,// };// },props: [value],template: divpinput typetext :valuevalue inputonInput v-on:keydown.enteraddUser refinp //pbutton clickaddUser新增用户/button/div,methods: {addUser() {//将输入的用户数据通知给父组件来完成新增用户操作.// this.$emit(add-user, this.userInfo);this.$emit(add-user);// this.userInfo ;},onInput(e) {this.$emit(input, e.target.value);},},mounted() {this.$refs.inp.focus();},});// 用户列表Vue.component(user-list, {data() {return {selectItem: ,};},props: {users: {type: Array,default: [],},},template: divp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ul/div,});new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,// isShow: false,// showWarn: false, // 控制警告窗口的显示与隐藏},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//关闭窗口closeWindow(data) {this.isShow data;this.showWarn data;},//添加用户的信息addUser() {if (this.userInfo) {if (this.users.length 0) {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;//完成用户添加后给出相应的提示信息// this.isShow true;this.$refs.msgSuccess.toggle();}} else {// 显示错误警告信息// this.showWarn true;this.$refs.msgWaring.toggle();}},//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});/script/body
/html
下面在对ref与vm.$refs的使用做一个总结
ref是作为渲染结果被创建的在初始渲染时不能访问它们。也就是必须在mounted构造函数中。$refs不是响应式的不要试图用它在模板中做数据绑定。
17、过滤器
17.1 过滤器基本使用
过滤器在日常生活中也是比较常见的例如自来水的过滤等。
在Vue中过滤器的作用就是格式化数据也就是对数据的过滤处理比如将字符串格式化为首字母大写
或者将日期格式化为指定的格式等。
下面先看一下自定义过滤器的语法
Vue.filter(过滤器名称,function(value){
//value参数表示要处理的数据//过滤器业务逻辑最终将处理后的数据进行返回
})定义好以后可以使用。使用的方式如下
div{{msg|upper}}/div
div{{msg|upper|lower}}/div具体的程序如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title过滤器基本使用/title/headbodydiv idappinput typetext v-modelmsg /div!--使用过滤器--{{msg|upper}}/div/divscript srcvue.js/scriptscript//定义过滤器让输入的单词首字母变成大写.Vue.filter(upper, function (value) {//获取首字母让其转换成大写然后拼接后面的内容。return value.charAt(0).toUpperCase() value.slice(0);});const vm new Vue({el: #app,data: {msg: ,},});/script/body
/html
过滤器在使用的时候可以采用如下的方式
div{{msg|upper|lower}}/div也就是先对msg中的数据使用upper过滤器得到的结果在交给lower过滤器进行处理。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title过滤器基本使用/title/headbodydiv idappinput typetext v-modelmsg /div{{msg|upper}}/divdiv{{msg|upper|lower}}/div/divscript srcvue.js/scriptscript//定义过滤器让输入的单词首字母变成大写.Vue.filter(upper, function (value) {//获取首字母让其转换成大写然后拼接后面的内容。return value.charAt(0).toUpperCase() value.slice(0);});Vue.filter(lower, function (value) {return value.charAt(0).toLowerCase() value.slice(0);});const vm new Vue({el: #app,data: {msg: ,},});/script/body
/html
上面定义的顾虑器是全局的过滤器当然也可以定义局部过滤器。
局部过滤器只能在其所定义的组件内使用。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title过滤器基本使用/title/headbodydiv idappinput typetext v-modelmsg /div{{msg|upper}}/divdiv{{msg|upper|lower}}/div/divscript srcvue.js/scriptscript//定义过滤器让输入的单词首字母变成大写.// Vue.filter(upper, function (value) {// //获取首字母让其转换成大写然后拼接后面的内容。// return value.charAt(0).toUpperCase() value.slice(0);// });Vue.filter(lower, function (value) {return value.charAt(0).toLowerCase() value.slice(0);});const vm new Vue({el: #app,data: {msg: ,},//局部过滤器filters: {upper: function (value) {return value.charAt(0).toUpperCase() value.slice(0);},},});/script/body
/html
在上面的代码中我们通过fileters定义了一个局部的过滤器upper.
在前面我们也说过Vue实例本身就是一个组件。
17.2 带参数的过滤器
带参数的过滤器定义如下
Vue.filter(format,function(value,arg1){//value表示要过滤的数据。//arg1,表示传递过来的参数})使用的方式如下
div{{data|format(yyyy-MM-dd)}}
/div要处理的数据data交给了过滤器中回调函数的value参数yyyy-MM-dd交给了arg1.
如下代码
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title过滤器参数/title/headbodydiv idappdiv{{date|format(abc,hello)}}/div/divscript srcvue.js/scriptscriptVue.filter(format, function (value, arg, arg1) {console.log(arg, arg1);return value;});const vm new Vue({el: #app,data: {date: new Date(),},});/script/body
/html
在上面的代码中我们定义了format过滤器然后在使用的时候我们是将date日期数据给了value
abc这个字符串给了arg,hello给了arg1.
下面我们把日期给具体的处理一下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title过滤器参数/title/headbodydiv idappdiv{{date|format(yyyy-MM-dd)}}/div/divscript srcvue.js/scriptscriptVue.filter(format, function (value, arg, arg1) {let result ;result value.getFullYear() - (value.getMonth() 1) - value.getDate();return result;});const vm new Vue({el: #app,data: {date: new Date(),},});/script/body
/html
18、自定义指令
18.1 自定义指令基本用法
为什么需要自定义指令呢
因为内置指令不满足需求。
下面看一下基本的创建自定义指令语法:
Vue.directive(focus,{inserted:function(el){//获取元素焦点el.focus();}})自定义指令用法
input typetext v-focus下面看一下具体的代码。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title自定义指令基本使用/title/headbodydiv idappinput typetext v-focus //divscript srcvue.js/scriptscriptVue.directive(focus, {inserted: function (el) {//el:表示指令所绑定的元素el.focus();},});const vm new Vue({el: #app,data: {},});/script/body
/html在上面的代码中我们通过directive方法创建了一个focus指令。
在使用该指令的时候一定要加上v-的形式。
inserted表示的是指令的钩子函数含义是被绑定元素插入父节点时调用。
18.2 自定义指令-带参数
带参数的自定义指令创建的语法改变元素背景色
Vue.directive(color,{inserted:function(el,binding){//binding表示传递过来的参数el.style.backgroundColorbinding.value.color;}
})指令的用法
input typetext v-color{color:orange} /下面看一下完整的代码案例
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title自定义指令带参数/title/headbodydiv idappinput typetext v-colormsg //divscript srcvue.js/scriptscript//自定义指令-带参数Vue.directive(color, {bind: function (el, binding) {el.style.backgroundColor binding.value.color;},});const vm new Vue({el: #app,data: {msg: {color: blue,},},});/script/body
/html通过上面的代码可以看到定义了一个color的指令在使用的时候传递了msg对象。
所以这个对象会给binding这个参数我们通过这个参数的value 属性获取msg对象中的color属性的值然后用来设置文本框的背景色。
这里使用了bind这个钩子函数只调用一次第一次绑定指令到元素时调用我们可以在此绑定只执行一次的初始化动作。
18.3 自定义局部指令
局部指令的基本语法
directives:{focus:{//指令的定义inserted:function(el){el.focus()}}
}在Vue实例中添加directives
具体实现的代码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title局部指令/title/headbodydiv idappinput typetext v-colormsg //divscript srcvue.js/scriptscriptconst vm new Vue({el: #app,data: {msg: {color: red,},},directives: {color: {bind: function (el, binding) {el.style.backgroundColor binding.value.color;},},},});/script/body
/html
局部指令只在所定义的组件中使用。
19、渲染函数
Vue推荐在绝大数情况下使用模板来创建你的HTML。然后在一些场景中你真的需要JavaScript的完全编程的能力也就是使用javaScript来创建HTML这时你可以用渲染函数它比模板更接近编译器。
这里我们先来做一个基本的了解为后期的深入学习打好一个基础。
下面先看一下render函数的基本结构。
render:function(createElement){//createElement函数返回的结果为VNode. VNode就是虚拟dom用js对象来模拟真实的DOM.retrun createElement(tag, //标签名称data,// 传递数据children //子节点数组 )}下面我们在用户管理这个案例中使用render函数来创建一个组件。
具体的代码如下 // heading组件//heading :level1{{title}}/heading //这时要创建的组件// h2 title/h2 //这时上面的组件最终渲染的结果Vue.component(heading, {props: {level: {type: String,required: true,},},render(h) { //h 就是createElement函数return h(h this.level, //参数1表示要创建的元素this.$slots.default //参数3子节点VNode数组。这里没有使用参数2{{tile}}就是一个子元素);},});接下来就可以使用heading组件了。 !-- 使用render函数创建的头部组件 --heading level1{{title}}/heading当然这里需要在data中定义title属性。
data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,title: 用户管理,// isShow: false,// showWarn: false, // 控制警告窗口的显示与隐藏},完整代码如下24、render函数.html
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /title列表渲染/titlestyle.actived {background-color: #dddddd;}.message-box {padding: 10px 20px;}.success {background-color: #4fc;border: 1px solid #42b;}.warning {background-color: red;border: 1px solid #42b;}.message-box-close {float: right;}/style/headbodydiv idapp!-- 弹窗组件 --message refmsgSuccess classsuccess!-- titile的插槽 --template v-slot:titleh2恭喜/h2/template!-- 默认插槽 --template添加用户成功/template/message!-- 警告 --message refmsgWaring classwarning!-- titile的插槽 --template v-slot:titleh2警告/h2/template!-- 默认插槽 --template请输入用户名/template/message!-- 使用render函数创建的头部组件 --heading level1{{title}}/heading!-- 清空提示栏 --div classtoolbarbutton click$bus.$emit(message-close)清空提示栏/button/div!-- 批量更新身高 --pinput typetext v-model.numberheight /button clickbatchUpdate批量更新用户身高/button/p!-- 新增用户 --user-add add-useraddUser v-modeluserInfo/user-add!-- 用户列表组件 --user-list :usersusers/user-listp总人数{{totalCount}}/p/divscript srcvue.js/scriptscript//创建事件总线Vue.prototype.$bus new Vue();// heading组件//heading :level1{{title}}/heading //这时要创建的组件// h2 title/h2 //这时上面的组件最终渲染的结果Vue.component(heading, {props: {level: {type: String,required: true,},},render(h) {return h(h this.level, //参数1表示要创建的元素this.$slots.default //参数3子节点VNode数组。这里没有使用参数2{{tile}}就是一个子元素);},});//创建弹出的组件Vue.component(message, {//show表示的含义控制弹出窗口的显示与隐藏。//slot:表示占坑。也就是窗口中的内容是通过外部组件传递过来的。// props: [show],data() {return {show: false,};},template: div classmessage-box v-ifshow!--具名插槽--slot nametitle默认标题/slotslot/slotspan classmessage-box-close clicktoggle关闭/span/div,mounted() {//给总线绑定message-close事件//也就是监听是否有message-close事件被触发。this.$bus.$on(message-close, () {// this.$emit(close, false);//当警告窗口和提示信息的窗口展示出来了才关闭。if (this.show) {this.toggle();}});},methods: {toggle() {this.show !this.show;},},});//新增用户组件Vue.component(user-add, {// data() {// return {// userInfo: ,// };// },props: [value],template: divpinput typetext :valuevalue inputonInput v-on:keydown.enteraddUser refinp //pbutton clickaddUser新增用户/button/div,methods: {addUser() {//将输入的用户数据通知给父组件来完成新增用户操作.// this.$emit(add-user, this.userInfo);this.$emit(add-user);// this.userInfo ;},onInput(e) {this.$emit(input, e.target.value);},},mounted() {this.$refs.inp.focus();},});// 用户列表Vue.component(user-list, {data() {return {selectItem: ,};},props: {users: {type: Array,default: [],},},template: divp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ul/div,});new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,title: 用户管理,// isShow: false,// showWarn: false, // 控制警告窗口的显示与隐藏},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//关闭窗口closeWindow(data) {this.isShow data;this.showWarn data;},//添加用户的信息addUser() {if (this.userInfo) {if (this.users.length 0) {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;//完成用户添加后给出相应的提示信息// this.isShow true;this.$refs.msgSuccess.toggle();}} else {// 显示错误警告信息// this.showWarn true;this.$refs.msgWaring.toggle();}},//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});/script/body
/html
虚拟DOM
Vue通过建立一个虚拟DOM来追踪自己要如何改变真实DOM.
createElement参数
前面说过createElement函数有三个参数。
createElement(//{string |Object|Function}//第一个参数可以是字符串也可以是对象或者是函数‘div’,// 第二个参数是对象表示的是一个与模板中属性对应的数据对象。该参数可选{},//第三个参数是一个数组,表示的是子节点数组[]
)
下面给heading组件添加第一个属性。 !-- 使用render函数创建的头部组件 --heading level1 :titletitle{{title}}/heading在上面的代码中我们给heading组件动态添加了一个title属性。而我们知道heading组件最终渲染成的是h1的元素最终效果为h1 titleaaa的形式。 // heading组件//heading :level1{{title}}/heading //这时要创建的组件// h2 title/h2 //这时上面的组件最终渲染的结果Vue.component(heading, {props: {level: {type: String,required: true,},title: {type: String,default: ,},},render(h) {return h(h this.level, //参数1表示要创建的元素{ attrs: { title: this.title } }, //参数2this.$slots.default //参数3子节点VNode数组。这里没有使用参数2{{tile}}就是一个子元素);},});在上面的代码中我们在render函数中给h函数添加了第二个参数给最终生成的元素添加了attrs属性。
20、函数式组件
组件没有管理任何状态也没有监听任何传递给它的状态也没有生命周期方法时可以将组件标记为functional.这意味它无状态没有响应式数据也没有实例没有this上下文
因为只是函数所以渲染的开销相对来说较小。
函数化的组件中的 Render 函数提供了第二个参数 context 作为上下文data、props、slots、children 以及 parent 都可以通过 context 来访问。
这块内容简单了解一下就可以。
21、混入
混入(mixin)提供了一种非常灵活的方式来分发Vue组件中的可复用功能一个混入对象可以包含任意组件选项。当组件使用混入对象时所有混入对象的选项被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin{created:function(){this.hello()},methods:{hello:function(){console.log(hello world)}}
}
Vue.component(comp,{mixins:[myMixin]
})“混入”可以提高组件的复用功能例如上面所写的hello这个方法不仅在一个组件中使用还会
在其它组件中使用.那么我们的处理方式就是可以将hello 这个方法单独定义在一个地方如果某个组件想要使用可以直接将该方法注入到组件中。
22、插件
前面我们讲解的混入组件封装等都可以提高组件的复用功能。
但是这种方式不适合分发也就是不适合将这些内容上传到github上npm上。而这种情况最适合通过插件来实现。
插件通常用来为Vue添加全局功能。插件的功能范围一般有下面几种
添加全局方法或者属性。例如‘element’添加全局资源通过全局混入来添加一些组件选项。例如vue-router添加vue实例方法通过把它们添加到Vue.prototype上实现一个库提供自己的API同时提供上面提到的一个或多个功能例如vue-router
插件声明
Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器第二个参数是一个可选的选项对象
MyPlugin.install function (Vue, options) {// 1. 添加全局方法或 propertyVue.myGlobalMethod function () {// 逻辑...}// 2. 添加全局资源Vue.directive(my-directive, {bind (el, binding, vnode, oldVnode) {// 逻辑...}...})// 3. 注入组件选项Vue.mixin({created: function () {// 逻辑...}...})// 4. 添加实例方法Vue.prototype.$myMethod function (methodOptions) {// 逻辑...}
}https://www.cnblogs.com/luozhihao/p/7414419.html
23、vue-cli使用
npm install -g vue/cliarget.value); }, }, mounted() { this.$refs.inp.focus(); }, }); // 用户列表Vue.component(user-list, {data() {return {selectItem: ,};},props: {users: {type: Array,default: [],},},template: divp v-ifusers.length0没有任何用户数据/pul v-elseliv-for(item,index) in users:keyitem.id:style{backgroundColor:selectItemitem?#dddddd:transparent}mousemoveselectItemitem编号{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}/li/ul/div,});new Vue({el: #app,data: {num: 100,totalCount: 0,users: [],height: 0,userInfo: abc,title: 用户管理,// isShow: false,// showWarn: false, // 控制警告窗口的显示与隐藏},//组件实例已创建时async created() {const users await this.getUserList();this.users users;//批量更新用户身高this.batchUpdate();},methods: {//关闭窗口closeWindow(data) {this.isShow data;this.showWarn data;},//添加用户的信息addUser() {if (this.userInfo) {if (this.users.length 0) {this.users.push({id: this.users[this.users.length - 1].id 1,name: this.userInfo,});this.userInfo ;//完成用户添加后给出相应的提示信息// this.isShow true;this.$refs.msgSuccess.toggle();}} else {// 显示错误警告信息// this.showWarn true;this.$refs.msgWaring.toggle();}},//批量更新身高动态的给users中添加身高属性batchUpdate() {this.users.forEach((c) {// c.height this.height;// Vue.set(c, height, this.height);this.$set(c, height, this.height);});},getTotal: function () {console.log(methods);return this.users.length 个;},getUserList: function () {return new Promise((resolve) {setTimeout(() {resolve([{id: 1,name: 张三,},{id: 2,name: 李四,},{id: 3,name: 老王,},]);}, 2000);});},},watch: {users: {immediate: true, //立即执行handler(newValue, oldValue) {this.totalCount newValue.length 个人;},},},});
/script**虚拟DOM**Vue通过建立一个虚拟DOM来追踪自己要如何改变真实DOM.**createElement参数**前面说过createElement函数有三个参数。js
createElement(//{string |Object|Function}//第一个参数可以是字符串也可以是对象或者是函数‘div’,// 第二个参数是对象表示的是一个与模板中属性对应的数据对象。该参数可选{},//第三个参数是一个数组,表示的是子节点数组[]
)
下面给heading组件添加第一个属性。 !-- 使用render函数创建的头部组件 --heading level1 :titletitle{{title}}/heading在上面的代码中我们给heading组件动态添加了一个title属性。而我们知道heading组件最终渲染成的是h1的元素最终效果为h1 titleaaa的形式。 // heading组件//heading :level1{{title}}/heading //这时要创建的组件// h2 title/h2 //这时上面的组件最终渲染的结果Vue.component(heading, {props: {level: {type: String,required: true,},title: {type: String,default: ,},},render(h) {return h(h this.level, //参数1表示要创建的元素{ attrs: { title: this.title } }, //参数2this.$slots.default //参数3子节点VNode数组。这里没有使用参数2{{tile}}就是一个子元素);},});在上面的代码中我们在render函数中给h函数添加了第二个参数给最终生成的元素添加了attrs属性。
20、函数式组件
组件没有管理任何状态也没有监听任何传递给它的状态也没有生命周期方法时可以将组件标记为functional.这意味它无状态没有响应式数据也没有实例没有this上下文
因为只是函数所以渲染的开销相对来说较小。
函数化的组件中的 Render 函数提供了第二个参数 context 作为上下文data、props、slots、children 以及 parent 都可以通过 context 来访问。
这块内容简单了解一下就可以。
21、混入
混入(mixin)提供了一种非常灵活的方式来分发Vue组件中的可复用功能一个混入对象可以包含任意组件选项。当组件使用混入对象时所有混入对象的选项被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin{created:function(){this.hello()},methods:{hello:function(){console.log(hello world)}}
}
Vue.component(comp,{mixins:[myMixin]
})“混入”可以提高组件的复用功能例如上面所写的hello这个方法不仅在一个组件中使用还会
在其它组件中使用.那么我们的处理方式就是可以将hello 这个方法单独定义在一个地方如果某个组件想要使用可以直接将该方法注入到组件中。
22、插件
前面我们讲解的混入组件封装等都可以提高组件的复用功能。
但是这种方式不适合分发也就是不适合将这些内容上传到github上npm上。而这种情况最适合通过插件来实现。
插件通常用来为Vue添加全局功能。插件的功能范围一般有下面几种
添加全局方法或者属性。例如‘element’添加全局资源通过全局混入来添加一些组件选项。例如vue-router添加vue实例方法通过把它们添加到Vue.prototype上实现一个库提供自己的API同时提供上面提到的一个或多个功能例如vue-router
插件声明
Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器第二个参数是一个可选的选项对象
MyPlugin.install function (Vue, options) {// 1. 添加全局方法或 propertyVue.myGlobalMethod function () {// 逻辑...}// 2. 添加全局资源Vue.directive(my-directive, {bind (el, binding, vnode, oldVnode) {// 逻辑...}...})// 3. 注入组件选项Vue.mixin({created: function () {// 逻辑...}...})// 4. 添加实例方法Vue.prototype.$myMethod function (methodOptions) {// 逻辑...}
}https://www.cnblogs.com/luozhihao/p/7414419.html
23、vue-cli使用
npm install -g vue/cli通过使用vue-clie创建项目。 文章转载自: http://www.morning.pmhln.cn.gov.cn.pmhln.cn http://www.morning.yxyyp.cn.gov.cn.yxyyp.cn http://www.morning.zfrs.cn.gov.cn.zfrs.cn http://www.morning.fpjxs.cn.gov.cn.fpjxs.cn http://www.morning.myzfz.com.gov.cn.myzfz.com http://www.morning.wftrs.cn.gov.cn.wftrs.cn http://www.morning.dgpxp.cn.gov.cn.dgpxp.cn http://www.morning.youprogrammer.cn.gov.cn.youprogrammer.cn http://www.morning.ykshx.cn.gov.cn.ykshx.cn http://www.morning.rjbb.cn.gov.cn.rjbb.cn http://www.morning.mzhgf.cn.gov.cn.mzhgf.cn http://www.morning.jwwfk.cn.gov.cn.jwwfk.cn http://www.morning.nkrmh.cn.gov.cn.nkrmh.cn http://www.morning.nzqmw.cn.gov.cn.nzqmw.cn http://www.morning.btqqh.cn.gov.cn.btqqh.cn http://www.morning.rnwt.cn.gov.cn.rnwt.cn http://www.morning.ttshf.cn.gov.cn.ttshf.cn http://www.morning.hflrz.cn.gov.cn.hflrz.cn http://www.morning.cgthq.cn.gov.cn.cgthq.cn http://www.morning.dxrbp.cn.gov.cn.dxrbp.cn http://www.morning.pmmrb.cn.gov.cn.pmmrb.cn http://www.morning.xjkfb.cn.gov.cn.xjkfb.cn http://www.morning.flfxb.cn.gov.cn.flfxb.cn http://www.morning.fsbns.cn.gov.cn.fsbns.cn http://www.morning.wzwpz.cn.gov.cn.wzwpz.cn http://www.morning.gkpgj.cn.gov.cn.gkpgj.cn http://www.morning.xlbtz.cn.gov.cn.xlbtz.cn http://www.morning.jzykq.cn.gov.cn.jzykq.cn http://www.morning.dnwlb.cn.gov.cn.dnwlb.cn http://www.morning.ghxzd.cn.gov.cn.ghxzd.cn http://www.morning.hphfy.cn.gov.cn.hphfy.cn http://www.morning.aswev.com.gov.cn.aswev.com http://www.morning.bpmnz.cn.gov.cn.bpmnz.cn http://www.morning.glnfn.cn.gov.cn.glnfn.cn http://www.morning.gzgwn.cn.gov.cn.gzgwn.cn http://www.morning.qgzmz.cn.gov.cn.qgzmz.cn http://www.morning.lffrh.cn.gov.cn.lffrh.cn http://www.morning.qdxwf.cn.gov.cn.qdxwf.cn http://www.morning.sgpnz.cn.gov.cn.sgpnz.cn http://www.morning.zlhzd.cn.gov.cn.zlhzd.cn http://www.morning.rpdmj.cn.gov.cn.rpdmj.cn http://www.morning.fzlk.cn.gov.cn.fzlk.cn http://www.morning.rhqn.cn.gov.cn.rhqn.cn http://www.morning.rnsjp.cn.gov.cn.rnsjp.cn http://www.morning.fkwgk.cn.gov.cn.fkwgk.cn http://www.morning.tthmg.cn.gov.cn.tthmg.cn http://www.morning.jpjxb.cn.gov.cn.jpjxb.cn http://www.morning.lfcfn.cn.gov.cn.lfcfn.cn http://www.morning.jghty.cn.gov.cn.jghty.cn http://www.morning.qkpzq.cn.gov.cn.qkpzq.cn http://www.morning.wknjy.cn.gov.cn.wknjy.cn http://www.morning.yqtry.cn.gov.cn.yqtry.cn http://www.morning.kdldx.cn.gov.cn.kdldx.cn http://www.morning.krhkn.cn.gov.cn.krhkn.cn http://www.morning.kzrg.cn.gov.cn.kzrg.cn http://www.morning.yktwr.cn.gov.cn.yktwr.cn http://www.morning.kfyqd.cn.gov.cn.kfyqd.cn http://www.morning.nqlx.cn.gov.cn.nqlx.cn http://www.morning.3jiax.cn.gov.cn.3jiax.cn http://www.morning.jczjf.cn.gov.cn.jczjf.cn http://www.morning.kwnbd.cn.gov.cn.kwnbd.cn http://www.morning.rgfx.cn.gov.cn.rgfx.cn http://www.morning.kjrp.cn.gov.cn.kjrp.cn http://www.morning.klltg.cn.gov.cn.klltg.cn http://www.morning.bwnd.cn.gov.cn.bwnd.cn http://www.morning.mymz.cn.gov.cn.mymz.cn http://www.morning.lzqnj.cn.gov.cn.lzqnj.cn http://www.morning.fcwb.cn.gov.cn.fcwb.cn http://www.morning.ktmpw.cn.gov.cn.ktmpw.cn http://www.morning.mdnnz.cn.gov.cn.mdnnz.cn http://www.morning.mnqz.cn.gov.cn.mnqz.cn http://www.morning.qsy37.cn.gov.cn.qsy37.cn http://www.morning.cthrb.cn.gov.cn.cthrb.cn http://www.morning.dmzzt.cn.gov.cn.dmzzt.cn http://www.morning.lwrks.cn.gov.cn.lwrks.cn http://www.morning.bmyrl.cn.gov.cn.bmyrl.cn http://www.morning.qyxwy.cn.gov.cn.qyxwy.cn http://www.morning.yunease.com.gov.cn.yunease.com http://www.morning.lznfl.cn.gov.cn.lznfl.cn http://www.morning.rbyz.cn.gov.cn.rbyz.cn