重要说明:本文会在我有空闲时间时持续更新,相当于是将官网的示例给完全呈现,是为了帮助初学者,也是为了巩固我自己的技术,我决定将官网给过滤一道消化,敬请期待。
一.介绍
vue是一种渐进式框架,被设计为自底向上逐层应用。所谓渐进式框架,我的理解就是vue是循序渐进的,一步一步的用。
举个简单的例子,就算我们不会webpack,不会node,但也能很快的入门。更多详情参阅。二.起步
1.hello,world
在学习vue之前,需要有扎实的HTML,CSS,JavaScript基础。任何一个入门语言都离不开hello,world!例子,我们来写这样一个例子:
新建一个html文件,helloworld.html
,如下: hello,world { { message }}
js代码如下:
var app = new Vue({ el:"#app", data:{ message:"hello,world!" }})
现在我们已经成功创建了第一个vue应用,数据和DOM已经被关联,所有的东西都是响应式
的,我们要如何确定呢,打开浏览器控制台,修改app.message
的值。
在这其中data对象的写法,我们还可以写成函数形式,如下:
var app = new Vue({ el:"#app", //这里是重点 data(){ return{ message:"hello,world!" } }})
2.文本插值
当然除了文本插值,我们还可以绑定元素属性,如下:
v-bind 鼠标悬浮上去可以看到
js代码如下:
var app = new Vue({ el:"#app", data:{ message:"页面加载于:" + new Date().toLocaleString() }})
查看效果,前往查看效果:
同样的我们也可以修改message的值,这样的话,鼠标悬浮上去,悬浮的内容就会改变了。在这个例子中v-bind
(或者也可以写成':'
)其实就是一个指令
,指令通常前缀都带有v-
,用于表示vue指定的特殊特性,在渲染DOM的时候,它会应用特殊的响应式行为。这个指令所表达的意思就是:将这个title属性的值与vue实例的message值保持一致。 3.元素的显隐
当然,我们也可以控制一个元素的显隐,那也是非常的简单,只需要使用v-show
指令即可:
v-if 默认你是看不到我的哦
js代码如下:
var app = new Vue({ el:"#app", data:{ seen:false } })
尝试在控制台中修改seen
的值,也就是app.seen = true
,然后你就可以看到页面中的span元素了,
4.列表渲染
还有v-for
指令,用于渲染一个列表,如下:
v-for { { item.name }}{
{ item.content }}
js代码如下:
var app = new Vue({ el:"#app", data:{ list:[ { name:"项目一",content:"HTML项目"}, { name:"项目二",content:"CSS项目"}, { name:"项目三",content:"JavaScript项目"}, ] } })
当然你也可以自己在控制台改变list
的值,
5.事件
vue通过v-on
+ 事件属性名(也可以写成'@'
+ 事件属性名)指令添加事件,例如v-on:click
或@click
如下一个示例:
v-on { { message }}
js代码如下:
var app = new Vue({ el:"#app", data:{ message:"hello,vue.js!" }, methods:{ reverseMessage:function(){ //在这里this指向构造函数构造的vue实例 this.message = this.message.split('').reverse().join(''); } } })
反转信息的思路就是使用split()
方法将字符串转成数组,,然后使用数组的reverse()
方法将数组倒序,然后再使用join()
方法将倒序后的数组转成字符串。
你也可以尝试在这里
6.组件
组件是vue中的一个核心功能,它是一个抽象的概念,它把所有应用抽象成一个组件树,一个组件树就是一个预定义的vue实例,在vue中使用Vue.component()
注册一个组件,它有两个参数,第一个参数为组件名(尤其要注意组件名的命名)
,第二个参数为组件属性配置对象
,如:
//定义一个简单的组件Vue.component('todo-item',{ template:`
现在我们来看一个完整的例子:
component
js代码如下:
Vue.component('todo-item',{ props:['todo'], template:`
这样,一个简单的组件就完成了,在这里,我们知道了,父组件app
可以通过props
属性将数据传递给子组件todo-item
,这是vue父子组件之间的一种通信方式。
三.核心
1.vue实例
每个vue应用都是通过Vue
构造函数创建的一个新的实例开始的:
var vm = new Vue({ //选项对象})
在这其中vm(viewModel的简称)
通常都表示vue实例的变量名。当创建一个vue实例,你都可以传入一个选项对象作为参数,完整的选项对象,你可能需要查看。
一个vue应用应该由一个通过new Vue
构造的根实例和许多可嵌套可复用的组件构成,这也就是说所有的组件都是vue实例。
2.数据与方法
当一个vue实例被创建完成之后,就会向它的vue响应系统中加入了data
对象中能找到的所有属性,当这些属性的值发生改变之后,视图就会发生响应
,也就是更新相应的值。我们来看一个例子:
//源数据对象var obj = { name:"eveningwater" };//构建实例var vm = new Vue({ data:obj})//这两者是等价的vm.name === obj.name;//这也就意味着//修改data对象里的属性也会影响到源数据对象的属性vm.name = "waterXi";obj.name;//"waterXi"//同样的,修改源数据对象的属性也会影响到data对象里的属性obj.name = 'stranger';vm.name;//"stranger"
可能需要注意的就是,只有data对象中存在的属性才是响应式的,换句话说,你为源数据对象添加一个属性,根本不会影响到data对象。如:
obj.sex = "男";vm.sex;//undefinedobj.sex;//'男'obj.sex = "哈哈哈";vm.sex;//undefined
这也就意味着你对sex
的修改并不会让视图更新,如此一来,你可能需要在data对象中初始化一些值,如下:
data:{ str:'', bool:false, arr:[], obj:{}, err:null, num:0}
查看此处。
只是还有一个例外Object.freeze()
,这个方法就相当于锁定(冻结)一个对象,使得我们无法修改现有属性的特性和值,并且也无法添加新属性。因此这会让vue响应系统无法追踪变化:
freeze { { message }}
js代码如下:
var obj = { message: "hello,vue.js!" } //阻止对象 Object.freeze(obj); var app = new Vue({ el: "#app", data:obj, methods: { reverseMessage: function() { this.message = this.message.split("").reverse().join(""); } } });
如此一来,无论我们怎么点击按钮,都不会将信息反转,甚至页面还会报错。
可前往此处自行查看效果。当然除了数据属性以外,vue还暴露了一些有用的实例属性和方法,它们通常都带有$
前缀,这样做的方式是以便与用户区分开来。来看一个示例:
property
js代码:
var obj = { name:'eveningwater' } var vm = new Vue({ data:obj, }); //这行代码表示将vue实例挂载到id为app的DOM根节点上,相当于在实例的选项对象中的el选项,即 //el:'#app' vm.$mount(document.querySelector('#app')) //数据是相等的 vm.$data === obj;//true //挂载的根节点 vm.$el === document.querySelector('#app');//true //以上两个属性都是实例上的属性,接下来还有一个watch即监听方法是实例上的方法 vm.$watch('name',function(oldValue,newValue){ //数据原来的值 console.log(oldValue); //数据最新的值 console.log(newValue); })
接下来,可以尝试在浏览器控制台修改name
的值,你就会发现watch()
方法的作用了。
3.实例生命周期
每个vue实例在被创建的时候都会经历一些初始化的过程,这其中提供了一些生命周期钩子函数,这些钩子函数代表不同的生命周期阶段,这些钩子函数的this
就代表调用它的那个实例。对于生命周期,有一张图:
你不需要立即这张图所代表的含义,我们来看一个示例:
vue life cycle vue生命周期
js代码:
var obj = { name:'eveningwater' } var app = new Vue({ data:obj, beforeCreate:function(){ //此时this指向app这个vue实例,但并不能得到data属性,因此this.name的值是undefined console.log('实例被创建之前,此时并不能访问实例内的任何属性' + this.name) } });
关于生命周期的全部理解,我们需要理解后续的组件知识,再来补充,此处跳过。这个示例可前往
4.模板语法
vue使用基于HTML的模板语法,在vue的底层是将绑定数据的模板渲染成虚拟DOM,并结合vue的响应式系统,从而减少操作DOM的次数,vue会计算出至少需要渲染多少个组件。
最简单的模板语法莫过于插值
了,vue使用的是Mustache语法(也就是双大括号"{ {}}")
。这个只能对文本进行插值,也就是说无论是字符串还是标签都会被当作字符串渲染。如:
Mustache { { greeting }}World!
js代码:
var obj = { greeting:"Hello,"}; var vm = new Vue({ data:obj }); vm.$mount(document.getElementById('app'));
如此以来Mustache标签
就会被data对象
上的数据greeting
给替代,而且我们无论怎么修改greeting
的值,视图都会响应,。
我们还可以使用v-once
指令对文本进行一次性插值,换句话说,就是这个指令让插值无法被更新:
Mustache { { greeting }}World!
js代码:
var obj = { greeting:"Hello,"}; var vm = new Vue({ data:obj }); vm.$mount(document.getElementById('app'));
在浏览器控制台中我们输入vm.greeting="stranger!"
可以看到视图并没有被更新,这就是这个指令的作用,我们需要注意这个指令对数据造成的影响。
既然双大括号只能让我插入文本,那要是我们要插入HTML代码,我们应该怎么办呢?v-html
这个指令就可以让我们插入真正的HTML代码。
v-html {
{ message }}
js代码:
var obj = { message:"hello,world!"}; var vm = new Vue({ data:obj }); vm.$mount(document.getElementById('app'));
页面效果如图所示;
可前往。
关于HTML特性,也就是属性,我们需要用到v-bind
指令,例如:
v-bind 使用v-bind指令给该元素添加id属性
js代码:
var obj = { propId:"myDiv"}; var vm = new Vue({ data:obj }); vm.$mount(document.getElementById('app'));
打开浏览器控制台,定位到该元素,我们就能看到div
元素的id
属性为"myDiv"
,如下图所示:
。
在绑定与元素实际作用相关的属性,比如disabled
,这个指令就被暗示为true
,在默认值是false,null,undefined,''
等转换成false的数据类型时,这个指令甚至不会表现出来。如下例:
v-bind
js代码:
var obj = { isDisabled:123}; var vm = new Vue({ data:obj }); vm.$mount(document.getElementById('app'));
这样一来,无论我们怎么点击按钮都没用,因为123
被转换成了布尔值true
,也就表示按钮已经被禁用了,我们可以打开控制台看到:
你可以尝试这个示例。
在使用模板插值的时候,我们可以使用一些JavaScript表达式。如下例:
expression {
{ number + 1 }}{
{ ok ? "确认" : "取消" }}{
{message.split("").reverse().join("")}}元素的id为 myDiv
js代码:
var obj = { number: 123, ok: true, message: "hello,vue.js!", elementId: "Div", color: "red" }; var vm = new Vue({ data: obj }); vm.$mount(document.getElementById("app"));
这些JavaScript表达式都会被vue实例作为JavaScript代码解析,。
值得注意的就是有个限制,只能绑定单个表达式,像语句是无法生效的。如下例:
sentence {
{ var number = 1 }}{
{ if(ok){ return '确认'} }}
js代码:
var obj = { number: 123, ok: true }; var vm = new Vue({ data: obj }); vm.$mount(document.getElementById("app"));
像这样直接使用语句是不行的,浏览器控制台报错,如下图:
不信可以自己试试。
指令(Directives)
是带有v-
前缀的特殊特性,通常指令的预期值就是单个JavaScript表达式(v-for除外)
,例如v-if
与v-show
指令,前者表示DOM节点的插入和删除,后者则是元素的显隐。所以,指令的职责就是根据表达式值的改变,响应式的作用于DOM。现在我们来看两个示例:
v-if {
{ value }}{
{ value }}{
{ value }}
js代码:
var obj = { value: 1 }; var vm = new Vue({ el: "#app", data() { return obj; } });
运行在浏览器效果如图:
现在你可以尝试在浏览器控制台更改vm.value = 2
和vm.value = 3
我们就可以看到页面的变化。你也可以狠狠点击此处查看和编辑。
我们再看v-show
的示例:
v-show {
{ value }}{
{ value }}{
{ value }}
js代码:
var obj = { value:1} var vm = new Vue({ data:obj});vm.$mount(document.querySelector('#app'))
然后查看效果如图:
尝试在控制台修改
vm.value = 2
和vm.value = 3
我们就可以看到页面的变化。你也可以狠狠点击查看。 从上面两个示例的对比,我们就可以看出来v-show
与v-if
指令的区别了,从切换效果来看v-if
显然不如v-show
,这说明v-if
有很大的切换开销,因为每一次切换都要不停的执行删除和插入DOM元素操作,而从渲染效果来看v-if
又比v-show
要好,v-show
只是单纯的改变元素的display
属性,而如果我们只想页面存在一个元素之间的切换,那么v-if
就比v-show
要好,这也说明v-show
有很大的渲染开销。
而且v-if
还可以结合v-else-if
与v-else
指令使用,而v-show
不能,需要注意的就是v-else
必须紧跟v-if
或者v-else-if
之后。当需要切换多个元素时,我们还可以使用template
元素来包含,比如:
template {
{ value }}{ { value }}
{ { value }}{ { value }}
js代码:
var obj = { value: 1 }; var vm = new Vue({ el: "#app", data() { return obj; } });
此时template
相当于一个不可见元素,如下图所示:
尝试在控制台修改
vm.value = 2
就可以看到效果了,你也可以狠狠的点击此处。 对于可复用的元素,我们还可以添加一个key
属性,比如:
key
js代码:
var obj = { loginType: "username", count:1 }; var vm = new Vue({ el: "#app", data() { return obj; }, methods: { changeType() { this.count++; if (this.count % 3 === 0) { this.loginType = "username"; } else if (this.count % 3 === 1) { this.loginType = "email"; } else { this.loginType = "mobile"; } } } });
效果如图:
你可以狠狠的点击查看。
从这几个示例我们也可以看出v-if
就是惰性
,只有当条件为真时,v-if
才会开始渲染。值得注意的就是v-if
与v-for
不建议合在一起使用。来看一个示例:
v-if与v-for
- { { item.value }}
js代码:
var obj = { list:[ { value:'html', active:false }, { value:'css', active:false }, { value:"javascript", active:true } ] }; var vm = new Vue({ el: "#app", data() { return obj; } });
虽然以上代码不会报错,但这会造成很大的渲染开销,因为v-for
优先级高于v-if
,这就造成每次执行v-if
指令时总要先执行v-for
遍历一遍数据。你可以点击此处查看。
遇到这种情况,我们可以使用计算属性。如:
v-if和v-for
- { { item.value }}
js代码:
var obj = { list: [ { value: "html", active: false }, { value: "css", active: false }, { value: "javascript", active: true } ] }; var vm = new Vue({ el: "#app", //先过滤一次数组 computed: { newList: function() { return this.list.filter(function(item) { return item.active; }); } }, data() { return obj; } });
如此一来,就减少了渲染开销,你可以狠狠点击这里查看。
指令的用法还远不止如此,一些指令是可以带参数的,比如v-bind:title
,在这里title
其实就是被作为参数。基本上HTML5属性都可以被用作参数。比如图片路径的src
属性,再比如超链接的href
属性,甚至事件的添加也属于参数,如v-on:click
中的click其实就是参数。来看一个示例:
param
js代码:
var obj = { url: "https://segmentfault.com/", src:"http://eveningwater.com/project/imggallary/img/15.jpg" }; var vm = new Vue({ el: "#app", data() { return obj; } });
效果如图所示:
你可以点击此处查看。
v-on
指令还可以添加修饰符,v-bind
与v-on
指令还可以缩写成:
和@
。缩写对于我们在繁琐的使用指令的项目当中是一个很不错的帮助。
5.计算属性
模板表达式提供给我们处理简单的逻辑,对于更复杂的逻辑,我们应该使用计算属性。来看两个示例的对比:
mustache { { message.split('').reverse().join('') }}
js代码:
var obj = { message:"hello,vue.js!" } var vm = new Vue({ data:obj }) vm.$mount(document.querySelector('#app'))
第二个示例:
mustache { { reverseMessage }}
js代码:
var obj = { message:"hello,vue.js!" } var vm = new Vue({ data:obj, computed:{ reverseMessage:function(){ return this.message.split('').reverse().join(''); } } }) vm.$mount(document.querySelector('#app'))
与第一个示例有所不同的就是在这个示例当中,我们申明了一个计算属性reverseMessage
,并且提供了一个getter
函数将这个计算属性同数据属性message
绑定在一起,也许有人会有疑问getter
函数到底在哪里呢?
var obj = { message:"hello,vue.js!" } var vm = new Vue({ data:obj, computed:{ reverseMessage:{ get:function(){ return this.message.split('').reverse().join(''); } } } }) vm.$mount(document.querySelector('#app'))
相信如此一来,就能明白了。你可以狠狠点击此处。你可以通过控制台修改message
的值,只要message
的值发生改变,那么绑定的计算属性就会发生改变。事实上,在使用reverseMessage
绑定的时候,我们还可以写成调用方法一样的方式,如:
mustache { { reverseMessage() }}
js代码:
var obj = { message:"hello,vue.js!" } var vm = new Vue({ data:obj, computed:{ reverseMessage:function(){ return this.message.split('').reverse().join(''); } } }) vm.$mount(document.querySelector('#app'))
那么这两者有何区别呢?虽然两者的结果都一样,但计算属性是根据依赖进行缓存的,只有相关依赖发生改变时它们才会重新求值。比如这里计算属性绑定的依赖就是message
属性,一旦message
属性发生改变时,那么计算属性就会重新求值,如果没有改变,那么计算属性将会缓存上一次的求值。这也意味着,如果计算属性绑定的是方法,那么计算属性不是响应式的。如下:
mustache { { date }}
js代码:
var vm = new Vue({ data:obj, computed:{ reverseMessage:function(){ return Date.now(); } } }) vm.$mount(document.querySelector('#app'))
与调用方法相比,调用方法总会在页面重新渲染之后再次调用方法。我们为什么需要缓存,假设你要计算一个性能开销比较大的数组,而且如果其它页面也会依赖于这个计算属性,如果没有缓存,那么无论是读取还是修改都会去多次修改它的getter
函数,这并不是我们想要的。
计算属性默认只有getter
函数,让我们来尝试使用一下setter
函数,如下:
computed
js代码:
var vm = new Vue({ el: "#app", data: { first_name: "li", last_name: "qiang" }, computed: { name: { get: function() { return this.first_name + ' ' + this.last_name; }, set: function(newValue) { var names = newValue.split(' '); this.first_name = names[0]; this.last_name = names[names.length - 1]; } } } });
现在,我们只需要修改vm.name
的值就可以看到first_name
和last_name
的值相应的也改变了。你可以狠狠点击此处。
6.侦听器
虽然计算属性在大多数情况下更合适,但有时候也可以使用侦听器。vue通过watch
选项提供一个方法来响应数据的变化。如:
watch 可以给我提出一个问题,然后我来回答?
{
{ answer }}
js代码:
var vm = new Vue({ el:"#app", data(){ return{ answer:"我不能回答你除非你提出一个问题!", question:"", answerImg:"" } }, created:function(){ // `_.debounce` 是一个通过 Lodash 限制操作频率的函数。 // 在这个例子中,我们希望限制访问 yesno.wtf/api 的频率 // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于 // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识, // 请参考:https://lodash.com/docs#debounce this.debounceGetAnswer = _.debounce(this.getAnswer,500); }, //如果question值发生改变 watch:{ question:function(oldValue,newValue){ this.answer="正在等待你停止输入!"; this.debounceGetAnswer(); } }, methods:{ getAnswer:function(){ //如果问题没有以问号结束,则返回 if(this.question.indexOf('?') === -1){ this.answer = "提出的问题需要用问号结束!"; return; } this.answer = "请稍等"; var self = this; fetch('https://yesno.wtf/api').then(function(response){ //fetch发送请求,json()就是返回数据 response.json().then(function(data) { self.answer = _.capitalize(data.answer); self.answerImg = _.capitalize(data.image); }); }).catch(function(error){ self.answer = "回答失败,请重新提问!"; console.log(error); }) } } })
现在咱们来看一下效果:
你可以狠狠点击此处查看。
7.计算属性vs侦听器
当在页面中有一些数据需要根据其它数据的变动而改变时,就很容易滥用侦听器watch
。这时候命令式的侦听还不如计算属性,请看:
watch {
{ fullName }}
js代码:
var vm = new Vue({ el:"#app", data:{ firstName:"li", lastName:"qiang", fullName:"li qiang" }, watch:{ firstName:function(val){ this.fullName = val + ' ' + this.lastName; }, lastName:function(val){ this.fullName = this.firstName + ' ' + val; } } })
再看通过计算属性实现的:
computed {
{ fullName }}
js代码:
var vm = new Vue({ el:"#app", data:{ firstName:"li", lastName:"qiang" }, computed:{ fullName:function(){ return this.firstNmae + ' ' + this.lastName; } } })
通过计算属性实现的功能看起来更好,不是吗?你可以自行尝试与进行对比。
8.class与style绑定
操作元素的class
和style
是构建一个页面所常见的需求,因为它们都是属性,所以利用v-bind
指令就可以操作元素的class
和style
样式。如;
class 添加一个class类,改变字体颜色为红色。
js代码如下;
var vm = new Vue({ el:"#app", data:{ className:"font-red" }})
你可以狠狠点击此处 查看。
再来看一个简单绑定style
的示例。
style 改变元素的字体颜色为红色。
js代码:
var vm = new Vue({ el:"#app", data:{ styleProp:"color:#f00;" }})
你可以狠狠点击此处查看。
这只是class
与style
的简单用法,vue.js
专门在这方面做了增强,使得绑定class
和style
的值可以是对象,也可以是数组,如:
class 改变字体的颜色
js代码:
var vm = new Vue({ el:"#app", data:{ isRed:true, isBlue:false } })
我们可以看到页面效果如图:
你还可以通过控制台修改vm.isBlue
或vm.isRed
的值,这充分说明这两个值是响应式的。如:
你可以狠狠的点击此处查看。
同样的,style
一样也可以使用对象语法,如:
style 字体大小为18px,字体颜色为红色,并且加粗的字体。
js代码:
var vm = new Vue({ el: "#app", data: { fontColor: "#f00", font18: "18px", fontBold:"bold" } });
效果如图:
我们一样可以修改其中的值,这些值也是响应式的,比如修改
vm.fontColor="#0f0"
就表示将字体颜色改变为蓝色。从上例我们也可以看出,我们可以使用驼峰式 (camelCase)
或短横线分隔 (kebab-case,需要用单引号括起来)
来定义css属性名。你可以狠狠点击此处查看。 当然在更多时候,我们直接绑定一个对象更有利于让模板变得清晰,也方便我们理解。
style 字体大小为18px,字体颜色为红色,并且加粗的字体。
js代码:
var vm = new Vue({ el: "#app", data: { styleObject:{ fontSize:"18px", color:"#f00", 'font-weight':"bold" } } });
这也是一样的效果,你可以点击此处查看。
除了对象语法,数组语法也同样适用于class
和style
,如:
class 颜色为红色大小为18px的字体
js代码:
var vm = new Vue({ el: "#app", data: { fontColor: "font-red", fontSize: "font-18" } });
运行效果如图:
你同样可以修改class
的名字,诸如vm.fontColor="font-blue"
,这样页面就会将font-red
更改为font-blue
,这毕竟是响应式的。你可以狠狠点击此处查看。 同样的,style
也能如此做,如:
style 颜色为红色大小为18px的字体
js代码:
var vm = new Vue({ el: "#app", data: { colorF: { color:"#f00" }, sizeF: { fontSize:"18px" } } });
这里尤其注意如下的写法是错误的,vue.js
并不能渲染出样式:
//这说明style绑定的数组项只能是一个对象,而不能是字符串 var vm = new Vue({ el: "#app", data: { colorF: "color:#f00;", sizeF: "font-size:18px;" } });
同样,我们注意修改值的时候也应该修改成一个对象,如:
vm.sizeF = { 'font-size':"20px"}
这点是需要注意的,另外在遇到带有前缀的css属性,如transition
时,我们不必写前缀,因为vue
会自动帮我们添加前缀。你可以狠狠点击此处查看。
style
的用法还不止如此,我们还可以绑定多重值。如下:
style 颜色为红色大小为18px的字体
js代码:
var vm = new Vue({ el: "#app", data: { webkitD:"-webkit-flex", nomarD:"flex" } });
这样一来,浏览器会根据支持-webkit-flex
或flex
而采用支持的写法,这个是在vue2.3.0+
版本中增加的功能。你可以点击此处查看。
9.条件渲染
v-if
指令用于条件性的渲染一块内容,这个指令只在它绑定的值为truthy
的时候才会渲染内容。例如:
v-if
你可以狠狠点击此处查看效果。
v-if
指令也可以与v-else
指令结合使用,注意v-else
必须紧跟v-if
或者v-else-if
之后。比如:
v-if
你可以狠狠点击此处查看效果。
v-if
也可以直接在<template></template>
标签上使用,这种情况下,我们通常是为了切换多个元素,因为v-if
必须添加到一个元素上,而且会把template
当作不可见元素来渲染,也就是说最终渲染不会包含template
元素。比如:
v-if 呵呵呵
哈哈哈
嘻嘻嘻嘿嘿嘿
你可以狠狠点击此处查看效果。
在vue2.1.0
新增了v-else-if
,顾名思义,也就是紧跟v-if
之后,v-else
之前的指令,可以使用多个v-else-if
指令。比如:
v-if 哈哈哈
嘿嘿嘿嘻嘻嘻呵呵呵
你可以狠狠点击此处查看效果。在这些示例中,只要绑定的是在vue实例data选项中的数据
,那么值就是响应式的,我们可以直接在控制台中修改,比如以上的vm.type = 1
,我们就可以看到页面的的元素以及内容被改变,并重新渲染。
由于vue
是简单的复用元素,而不是重新渲染元素,因此,这会让vue
非常的高效,但这不可避免出现了一个问题,如下:
v-if
你可以狠狠点击此处查看效果。在输入框中输入值,然后再点击切换按钮,你会发现input
的内容并没有被清空,这也说明vue
并不是重新渲染元素,而是高效的复用元素而已。再实际开发中,这样肯定是不符合需求的,那么我们应该如何解决这个问题呢?
还好,vue
提供了一个key
属性,我们只需要给每个复用的元素绑定一个key
属性,用于区分它们是不同的元素。如下:
v-if
现在你再尝试在输入框中输入值,然后点击切换按钮,就会发现值会被清空了。请点击查看效果。
需要注意的是label
元素其实也是被复用了,因为它们没有添加key
属性。
v-show
的指令用法跟v-if
差不多,唯一需要注意的区别就是v-show
仅仅只是改变了元素的display
属性而已,其DOM元素仍然存在于文档中,并且v-show
之后没有v-else-if
与v-else
指令。看一个简单的示例:
v-show
具体效果如下图所示:
你可以狠狠点击此处查看效果。
所以我们也可以看得出来v-if
与v-show
的区别:
还要注意的一点就是不推荐v-if
与v-for
一起使用,因为它们同时使用的时候,v-for
的优先级高于v-if
。
10.列表渲染
vue.js
使用v-for
指令来渲染一个列表,形式类似item in items
或item of items
,在这之中,item
是数组中每一项的迭代名称,而items
则是源数据,在渲染列表的时候,通常都要添加一个key
属性,用以给vue.js
一个提示,以便vue.js
更好的跟踪每个节点的变化。key
属性工作方式就像一个属性,因此使用v-bind
指令来绑定,并且key
属性也是唯一的。理想的key
属性就是每一个数组项的唯一id
。来看一个示例:
v-for
- { { item }}
js代码:
var vm = new Vue({ el:"#app", data:{ items:['html','css','javascript'] } })
数组中的数组项还可以是对象,如:
v-for
- { { item.id }}.{ { item.value }}
js代码:
var vm = new Vue({ el:"#app", data:{ items:[ { id:1, value:"html" }, { id:2, value:"css" }, { id:3, value:"javascript" } ] } })
你可以狠狠点击和查看。
这也就是说,v-for
包含块中的父作用域,我们是有完全访问的权限的,而且v-for
还提供第二个可选参数,表示对当前项的索引。如:
v-for
- 索引:{ { index }}-{ { item }}
js代码如下:
var vm = new Vue({ el:"#app", data:{ items:['html','css','javascript'] } })
你可以点击此处查看。
v-for
同样可以渲染一个对象,可选有三个参数,第一个参数为对象值,第二个参数为对象属性键名,第三个参数则是索引。如下:
v-for
- 索引:{ { index }} + 属性名:{ { key }} + 属性值:{ { value }}
js代码:
var vm = new Vue({ el:"#app", data:{ info:{ name:"李白", value:"李太白" } } })
你可以狠狠点击此处查看。
为了vue.js
能够高效的更新虚拟DOM,我们有必要给vue
的v-for
提供一个key
属性,理想的key
属性就是每一项的id
。这是一个属性,所以使用v-bind
来绑定,添加key
属性是方便vue
跟踪每个节点,从而根据数据的变化来重排序节点,要理解key
属性的意义,你可能需要理解的知识。虽然vue.js
默认就采用就地复用
的策略,但这只针对不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出
,所以最好的方法就是添加key
属性。
v-for {
{ item.value }}
js代码:
var vm = new Vue({ el: "#app", data() { return{ items:[ { id:1, value:"html" }, { id:2, value:"css" }, { id:3, value:"javascript" } ] } } });
为了方便理解key
属性带来的高效性,可以尝试在控制台更改数据,比如增删改查操作。你可以点击此处查看。
vue
包含一组数组的变异方法,他们也可以让视图随着数据的改变而更新。方法如下:
push(),pop(),unshift(),shift(),splice(),reverse(),sort()
。我们可以来看一个示例: mutation methods
- { { item.name }}
js代码如下:
var vm = new Vue({ el:"#app", data:{ items:[ { id:1, name:"html" }, { id:2, name:"css" }, { id:3, name:"javascript" } ] } }); //vm.items.push({ id:4,name:"java"}) //vm.items.pop() //vm.items.shift() //vm.items.unshift({ id:4,name:"java"}) //vm.items.splice(0,1,{ id:1,name:"java"}) //vm.items.reverse()
尝试在控制台,使用这些变异方法修改数组items
的值,我们就可以看到这些方法的作用了。你可以狠狠点击此处查看。
变异的方法,顾名思义,就是会改变原数组,既然有变异方法,那当然也有非变异的方法,比如:filter()
,slice()
和concat()
,虽然这些方法不会改变一个数组,但总是会返回要给新数组,我们可以用新数组替换原数组。如:
mutation methods
- { { item.name }}
js代码如下:
var vm = new Vue({ el: "#app", data: { items: [ { id: 1, name: "html" }, { id: 2, name: "css" }, { id: 3, name: "javascript" } ] } }); // vm.items = vm.items.filter(function(item) { // return item.name.match(/javascript/); // });// vm.items = vm.items.concat([{ id: 4, name: "java" }]);// vm.items = vm.items.slice(1);
尝试在控制台将以上注释的js
代码给测试一下,你可以狠狠点击此处尝试一下。
通过以上示例,你可能会认为vue
抛弃了原数组,重新渲染了DOM,但实际上并不是这样,这也说明这样替换数组是非常高效的一个操作。
虽然我们可以使用变异方法修改数组,从而达到视图的更新,但我们也要注意两点,那就是以下两点并不会触发视图的更新:
1.根据数组的索引来修改数组项的值。
示例如下:
limit
- { { item.name }}
js代码如下:
var vm = new Vue({ el: "#app", data: { items: [ { id: 1, name: "html" }, { id: 2, name: "css" }, { id: 3, name: "javascript" } ] } });//vm.items[0].value = "java";并不会触发视图的更新。
你可以点击此处亲自查看。
2.修改数组的长度。
如以下示例:
limit
- { { item.name }}
js代码如下:
var vm = new Vue({ el: "#app", data: { items: [ { id: 1, name: "html" }, { id: 2, name: "css" }, { id: 3, name: "javascript" } ] } });//vm.items.length= 2;并不会触发视图的更新。
你同样可以点击此处进行查看。
针对第一个问题,我们可以通过Vue.set()
方法和变异方法splice()
方法来解决,如下:
//第一种方案Vue.set(vm.items[0],'name','java')//或this.$set(vm.items[0],'name','java');//第二种方案vm.items.splice(0,1,{ id:1,name:"java"});
你可以点击此处查看。
针对第二个问题,你可以使用splice()
方法来改变。如下:
vm.items.splice(2);
你可以点击此处进行查看。
从以上的示例,我们可以看出Vue.set()
或this.$set()[只是一个别名而已]
的参数可以传3
个,即要改变的数组项,数组项的索引或者属性名,新数组项的属性值
。而splice()
则可以传1到3
个参数,即数组的起始项索引,删除几项,从第三个参数开始就是需要替换的项或者说是需要添加的项
。
对于对象,其实也仍然有限制,对象的新增和删除,vue
是无法检测的。来看如下例子:
limit
- { { item.value }}
- { { item.name }}
js代码:
var vm = new Vue({ el: "#app", data: { item: { value:"123" } } }); //vm.item.name = "javascript";//无效 //delete vm.item.value;//无效
你可以狠狠点击此处查看。
针对以上属性的添加的问题,我们还是使用Vue.set()
方法来解决。而属性的删除,我们可以使用Vue.delete()
来解决,理论上在项目开发中应该很少用到这个方法。
//对象属性的添加Vue.set(vm.item,'name','javascript');//或this.$set(vm.item,'name','javascript'); //对象属性的删除Vue.delete(vm.item,'value');
当我们需要添加多个对象时,可以使用Object.assign()或_.extend()[jquery方法]
,如下:
vm.item = Object.assign({},vm.item,{ age:26,skill:['html','css','javascript']}) //或者vm.item = $.extend({},vm.item,{ age:26,skill:['html','css','javascript']})
你可以狠狠点击此处查看。
有时候,在某种情况下,我们会需要过滤或者排序一个数组,并且不能改变原数组,从而渲染出满足条件的数组项,这时候,我们可以通过计算属性结合数组的迭代方法filter()
这个方法不会改变原数组的值。我们来看一个示例:
filter array
- { { item }}
js代码如下:
var vm = new Vue({ el:"#app", data:{ items:[1,2,3,4,5,6,7,8,9] }, computed:{ filterNumbers(){ return this.items.filter((num) => { return num < 5; }) } } });
你可以狠狠点击此处查看效果。
当然,你也可以在计算属性不适用的情况下,使用方法来替代。如:filter array
- { { item }}
js代码如下:
var vm = new Vue({ el:"#app", data:{ items:[1,2,3,4,5,6,7,8,9] }, methods:{ filterNumbers(numbers){ return numbers.filter((num) => { return num < 5; }) } } });
你可以狠狠点击此处查看效果。
也许有时候,我们是没必要渲染每一个父元素的,只需要渲染子元素的,这时候我们就可以使用template
元素来使用v-for
指令渲染元素,渲染的数据也不一定是一个数组,也可以是一个整数。需要注意的就是当添加key
属性时,需要将这个属性添加到真实的DOM元素上,这也就是说template
元素是不推荐使用key
属性的。如下:
template { { n }}
js代码如下:
var vm = new Vue({ el:"#app", data:{ number:100 }, });
你可以狠狠点击此处查看效果。
当v-if
和v-for
指令结合一起使用时,由于v-if
指令的优先级要高于v-for
,因此每次进判断都要重复遍历一道数组,不过这在只需要渲染某些项的时候会非常有用。如:
v-for with v-if
- { { item.value }}
js代码如下:
var vm = new Vue({ el:"#app", data:{ items:[ { show:true, value:'html' }, { show:false, value:"css" }, { show:false, value:"javascript" } ] }, });
你可以狠狠点击此处进行查看。
v-for
还可以用在组件当中,但在组件中使用v-for
是必须要加上key
属性的。如下是一个简易版的todolist
组件示例:
todolist demo
js代码如下:
Vue.component("todoItem", { template: ``, props: ["item"] }); var vm = new Vue({ el: "#app", data: { newToDo: "", items: [ { show: true, value: "html" }, { show: true, value: "css" }, { show: true, value: "javascript" } ] }, methods: { addNewToDo() { if (this.newToDo) { this.items.push({ value: this.newToDo, show: true }); }else{ alert('please input your need to do thing!') } } } });{ { item.value }}
你可以狠狠点击此处进行查看。
11.事件处理
Vue
中使用v-on(简写@)
指令来为DOM添加点击事件。如:
v-on The button above has been clicked {
{ count }} times!
js代码:
var vm = new Vue({ data:{ count:0 } }); vm.$mount(document.getElementById("app"));
你可以狠狠点击此处查看效果。
很多时候,事件的复杂程度远不止如此,因此,事件可以用方法来表示。如:
v-on The button above has been clicked {
{ count }} times!
js代码如下:
var vm = new Vue({ data:{ count:0 }, methods:{ clickMethod:function(event){ //this指向vm这个vue实例 this.count += 1; alert('你点击了按钮' + this.count + '次!'); alert(event.target.tagName); } } }); //还可以直接调用方法// vm.clickMethod(); vm.$mount(document.getElementById("app"));
你可以狠狠点击此处查看效果。
除了直接绑定方法,当然还可以在内联JavaScript语句中调用方法。如:
v-on
js代码如下:
var vm = new Vue({ data: { msg1:'hello,', msg2:"world!" }, methods: { clickMethod: function(message) { alert(message) } } }); vm.$mount(document.getElementById("app"));
你可以狠狠点击此处查看效果。
当然有时候我们需要访问原生DOM事件,这时候可以使用一个特殊的变量$event
传入方法。如:
v-on
js代码如下:
var vm = new Vue({ data: { msg: "hello,world!" }, methods: { clickMethod: function(message, event) { //现在你可以访问原生的事件对象 if (event) event.preventDefault(); alert(message); } } }); vm.$mount(document.getElementById("app"));
你可以狠狠点击此处查看效果。
在事件中阻止事件的默认行为或者阻止事件冒泡是非常常见的需求,通常阻止事件的默认行为,都可以在事件方法内调用事件对象的preventDefault()
方法。例如:
var myDiv = document.getElementById('myDiv'); myDiv.onclick = function(event){ //event即事件对象 event.preventDefault(); }
再比如阻止事件冒泡,即调用stopPropagation()
。例如以下一个示例:
Document 父元素子元素
如果不对子元素调用stopPropagation()
,那么父元素的事件就会传播到子元素上,也就是说会弹出两个弹出框,父元素的事件冒泡到了子元素上,而如果调用该方法,则表明父元素的事件被阻止冒泡了,结果点击子元素也就只弹出一个弹出框提示"点击了子元素"
,这是在原生当中的用法,而vue
则提供了事件修饰符。
.stop,.prevent,.capture,.self,.once,.passive
等事件修饰符。 v-on 父元素子元素{
{ count }}
对比加了.stop
和不加.stop
的效果如下图所示:
//加了
//不加
你可以狠狠点击此处查看效果。
.prevent
事件修饰符其实也就是阻止事件的默认行为,比如:
v-on
尝试将.prevent
事件修饰符去掉,你会发现当你点击了提交按钮提交的时候,页面会重载。你可以狠狠点击此处进行查看。
.capture
修饰符的作用就是让元素最先执行事件,如果有多个元素拥有该事件修饰符,那么则由外到内依次触发该事件。如下例:
v-on 点击我{ { count }}请点击我
点击我啊
当点击最外层的div
元素时,触发最外层的事件,当点击到p
元素(不是span
元素),则先执行div
的事件,然后再执行p
元素,因此count
的值就会相加两次。同理,点击span
元素,则相加三次。你可以狠狠点击此处查看效果。
.self
事件修饰符在只有event.target
的值为自身元素的时候才会触发,类似.stop
事件修饰符,该事件修饰符阻止了事件的冒泡,因为event.target
的值始终只会有一个值。如下例:
v-on 点击我{ { count }}请点击我
点击我啊
如果点击div
元素,那么event.target
的值就是div
元素,因此也就触发该元素的事件,count
的值就加1
,同理,点击p
和span
元素就是一样的道理,你可以狠狠点击此处查看效果。
.once
事件修饰符只允许事件执行一次。如:
v-on {
{ count }}
运行点击按钮,我们会发现count
的值只会加1,不管点击按钮多少次,都没有用,这也就是说事件只会执行一次。你可以狠狠的点击此处查看效果。
.passive
事件修饰符主要是为了提升页面滑动的性能,主要是对passive
事件监听器的一个封装,要理解这个事件修饰符,我们需要知道原生的Passive Event Listeners
。这是chrome提出的一个新的浏览器特性:Web开发者通过一个新的属性passive
来告诉浏览器,当前页面内注册的事件监听器内部是否会调用preventDefault
函数来阻止事件的默认行为,以便浏览器根据这个信息更好地做出决策来优化页面性能。当属性passive
的值为true
的时候,代表该监听器内部不会调用preventDefault
函数来阻止默认滑动行为,Chrome浏览器称这类型的监听器为被动(passive)监听器
。目前Chrome
主要利用该特性来优化页面的滑动性能,所以Passive Event Listeners
特性当前仅支持mousewheel/touch
相关事件。更多。我们来看一个示例:
v-on { { item.content }}
如以上注释所示,如果给section
加了passive
事件修饰符,并且将注释去掉,我们会发现页面还是能流畅的滑动的。你可以狠狠点击此处查看效果。
事件修饰符也可以组合使用,但组合的顺序非常重要,比如@click.self.prevent
与@click.prevent.self
完全是两个不同的概念,前者会阻止event.target
的值指向的那个元素的所有点击效果,因为是先执行.prevent
事件修饰符,即阻止了该元素的所有点击事件。而后者则只是阻止了event.target
的值指向的那个元素的点击事件,而不会阻止默认事件的执行。如下例:
v-on
你可以狠狠点击此处查看效果。
此外,值得注意的就是,不能将.passive
和.prevent
事件修饰符组合在一起使用。否则浏览器会给出一个警告,如下图所示:
在监听键盘事件时,往往需要监听键盘的键值,vue
则提供了按键修饰符,允许为键盘事件添加键值。如:
v-on 你按下的键值为:{
{ count }}
你可以狠狠点击此处查看效果。
13
是键盘上enter
键的键值,通常我们不容易记住键盘的键值,这时候vue
提供了别名。如以上的键盘事件可以改写为:
v-on:keyup.enter //@keyup.enter
目前已有的全部按键别名有.enter,.tab,.delete,.esc,.space,.up,.down,.left,.right
,当然我们还可以通过全局去自定义别名。
Vue.config.keyCodes.num0 = 48
。如示例: v-on 你按下的键值为:{
{ count }}
你可以狠狠点击查看效果。
也可以通过直接将暴露的任意有效键名转换成kebab-case(短横线命名)
来作为修饰符,比如键盘上的PageDown
键可以写成page-down
,PageUp
可以写成page-up
,End
可以写成end
等。如下例:
v-on 你按下的键值为:{
{ count }}
将鼠标定位到按钮上,按住键盘上的end
键,查看效果。你可以狠狠点击此处查看效果。
值得注意的就是,有一些按键(.esc以及所有的方向键)在IE9中有不同的key值,如果想支持IE9,它们的内置别名应该是首选。
除了常用键以外,还有四个系统修饰键,即.ctrl,.alt,.shift,.meta(在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”)
,这四个键通常是和其他常规按键一起组合使用,也就是说只有当按下这四个系统修饰键,再按下相应的常规键,才能触发键盘事件,而且这四个键单独使用是没有效果的。要想让四个键单独有效果,那么你就只能使用keyCode
。
@keyup.ctrl
你应该写成@keyup.17
这样才会单独按下ctrl
键,发生键盘事件,产生效果。所以,我们只能像下面那样使用: v-on 你按下的键值为:{
{ count }}
鼠标光标定位到按钮上,按下shift
键,再同时按下数字键0(左上字母键对应的数字键0)
,就能看到效果呢。你可以狠狠点击此处查看效果。
当然我们也可以将系统修饰符组合使用到非键盘事件,比如click
事件中,如下例:
v-on 事件名为:{
{ eventname }}
鼠标定位到按钮上,首先单独点击按钮是没有效果的,需要按住键盘上的ctrl键
,然后再点击按钮,才能产生效果。你可以狠狠点击此处查看效果。
由此看来,单独使用.exact
修饰符,就表示只要按下了系统修饰键,就不会执行点击事件。如下例:
v-on 你按下的键值为:{
{ count }}
当按住键盘上的ctrl,shift,meta,alt
键,然后再点击按钮就不会有效果。你可以狠狠点击此处查看效果。
除了常用键盘修饰符以及系统修饰符之外,vue
还提供了鼠标按钮修饰符,用于指定鼠标哪个按键按下才会执行事件。鼠标按钮修饰符有.left,.middle,.right
。如下:
v-on 鼠标按钮按住中间的滚轮键触发:{
{ event }}
用鼠标滚轮键点击按钮,就可以查看效果,你可以狠狠点击此处查看效果。
12.表单输入绑定
vue
可以通过v-model
指令在<input>,<select>,<textarea>
这三种表单元素上创建数据双向绑定,这个指令会根据表单控件类型选择正确的方法来更新元素。v-model
只不过是一个语法糖,通过监听用户输入事件从而达到更新数据的目的,这在一些前端极端场景中尤为重要。
在使用v-model
指令时,需要注意它会忽略所有表单元素中的valued,selected,checked
等特性的初始值,而将vue实例的data对象
作为数据初始值,所以在使用过程中需要在data选项中
声明初始值。
一个最简单的示例如下:
v-model 绑定的数据:{
{ message }}
当用户在输入框中输入数据的时候,对应绑定在data选项
中的数据就会发生改变。你可以狠狠点击此处查看效果。
还可以绑定在textarea
标签上,如:
v-model 绑定的数据:{
{ message }}
你可以狠狠点击此处查看效果。
绑定到单个复选框示例如下:
v-model 是否选中:{
{ isChecked }}
你可以狠狠点击此处查看效果。
除此之外,还可以将多个复选框绑定到一个数组,如下:
v-model 选中的值:{
{ checkArr }}
你可以狠狠点击此处查看效果。
还可以绑定单选按钮,如下:
v-model 选中的值:{
{ checkedValue }}
你可以狠狠点击此处查看效果。
绑定到select
的示例如下:
v-model 选中的值:{
{ message }}
你可以狠狠点击此处查看效果。
在这里需要注意的就是,如果v-model
绑定的初始值未匹配任何选项,那么select
元素就会渲染为'未选中状态',在IOS中,这会导致用户无法选中第一项,因为并没有触发change
事件,因此更推荐像以上那样提供一个默认禁用的选项。
select
多选时:
v-model 选中的值:{
{ message }}
你可以狠狠点击此处查看效果。
还可以用v-for
指令渲染动态选项:
v-model 选中的值:{
{ selected }}
你可以狠狠点击此处查看效果。
对于单选按钮,复选框,选择框来说,v-model
指令通常绑定的是静态字符串,对于复选框,还可以是布尔值。它们的value
值,我们也是可以绑定到vue实例的data选项
上的,而且绑定的值不一定是静态字符串。一个完整的示例如下:
v-model 你选择的是:{
{ sex }}将value值动态绑定到vue实例上:{
{ bindSex }}布尔值:{
{ isChecked }}是否选中:{
{ toggle }}你喜欢:{
{ selected }}
你可以狠狠点击此处进行查看。
在表单绑定的时候,vue
也提供了3个修饰符.lazy,.number,.trim
,.lazy
的作用就是改变表单元素触发的input
事件,一般说来,v-model
通常是在每次input
事件(即用户只要输入内容时),将输入框的值与绑定的数据进行同步更新,如果不想要与input
事件同步,而是与change
事件同步,那么就可以使用.lazy
修饰符,.number
修饰符的作用就是将输入的值转换成number
类型,因为默认输入的值时string(字符串)
类型。而.trim
修饰符的作用就是自动过滤掉首位的空白。示例如下:
v-model 未使用.lazy修饰符:
{
{ notMsg }}使用.lazy修饰符:
{
{ msg }}未使用.number修饰符:
{
{ typeof notCount === 'string' ? '字符串值:"'+notCount+'"' : notCount }}使用.number修饰符:
{
{ typeof count === 'number' ? '数值:' + count : count }}未使用.trim修饰符:
{
{ notTrim }}使用.trim修饰符:
{
{ trim }}
你可以狠狠点击此处进行查看。
13.组件基础
组件是可复用的vue实例
,除了el
选项不一致以外,其余的data,prop,computed,methods,watch
等选项都是一样的,并且组件都需要一个名字。一个基本的示例如下:
component
以上定义了一个简单的示例,通过这个示例我们可以知道组件是可复用的vue实例
因此可以接收与new Vue()
一样的选项参数,组件的名字就是button-num
,通过Vue.component()
来全局注册一个组件,这个方法接受2个参数,第一个参数就是组件名,第二个参数则是组件配置对象,配置对象的选项与vue实例
是一致的。
组件还可以复用,当基础组件要使用的多的时候,可以使用v-for
来渲染。如下:
component
你可以点击这里查看该示例。在这里,我们需要注意一点,组件中的data选项必须是一个函数
,如果不是一个函数,控制台则会弹出一个警告,如下图所示:
一个大型的网站项目,比如一个博客,我们可以将博客分割成多个小组件,比如导航组件,比如内容组件等等,这由许多个小组件组织起来,就成了一个SPA单页应用
。如下图所示:
为了能够在模板中使用,需要先注册这些组件以便vue根实例来识别。注册组件有两种方式:全局注册
和局部注册
,在这里,我们都是通过Vue.component()
来全局注册的。
component
在上例中,我们可以像访问data选项
一样访问props
传递的值,props
可以接收任意类型传递的值,你可以狠狠点击此处进行查看。
一个prop
被注册之后,我们其实可以像下面这样把数据作为一个自定义的特性传递:
component
你可以狠狠点击此处查看该示例。
然而,在一个典型的应用当中,我们需要传递的数据不可能是如此简单的一个字符串,有可能是一个对象,甚至是一个数组。这样,以上的示例,我们需要如此修改:
component
如上所示,你应该会发现我们使用v-bind
来动态传递prop
,这在你不知道要渲染的具体内容,尤其是当渲染一个API数据接口
时是非常有用的。到目前为止,prop
需要了解的就只有这些呢。你可以点击此处查看该示例。
但在一篇博文当中,我们不仅仅是需要添加标题,最起码我们需要渲染博文的内容,因此你的模板内容就需要做修改:
template:`{ { title }}
`;
然而如果这样写的话,vue
会显示一个错误,every component must have a single root element (每个组件必须只有一个根元素),因此我们需要用一个根元素来包裹,如下:
template:``;{ { title }}
当组件变得越来越复杂时,如果单独定义一个prop
,这会看起来越来越复杂。一篇博文可能会包含发布日期,评论等,我们如此做法,代码看起来也不够简洁:
重构一下这个组件,让它接收一个单独的prop
,如下:
component
你可以狠狠点击此处查看。在开发blog
组件时候,有时候,我们不仅需要建立父组件向子组件的通信,我们也许还需要建立子组件向父组件传递数据之间的通信,这时候,我们可以采用自定义事件
来向父组件传递数据。例如,我们想要添加一个按钮,用于改变字体的颜色,我们可以自定义一个颜色框,然后让用户选择字体颜色来改变字体的颜色。如下:
component
通过以上示例我们可以看出,父组件通过像处理native DOM
事件一样通过v-on
指令监听子组件的任意事件,然后子组件通过$emit()
方法来自定义一个事件,这个方法接收两个参数,第一个参数就是自定义的事件名(在这里是change-color)
,第二个参数则是要传递的参数,在这里,传递了颜色选择器选择的颜色值colorValue
,然后父组件使用这个事件,有了这个自定义的v-on:change-color
监听器,父组件就可以为数组的每一项对象新增一个color
属性,通过将设置style
,将color
属性绑定到style
标签内,然后父组件就可以更新数据,并渲染结果了。你可以狠狠点击此处来查看。
通过自定义事件我们还可以实现在组件上使用v-model
指令,如下:
component {
{ msg }}
你可以狠狠点击此处查看。
和HTML元素一样,通常我们也需要向组件传递内容,这时候,我们可以使用插槽。如:
component {
{ msg }}
你可以狠狠点击此处查看。
有时候,我们需要动态切换组件,也就是在做一个选项卡组件的时候,我们把每一个导航对应一个导航组件,这时候就需要动态切换组件。如果要动态切换组件,我们可以使用一个特性is
来实现。例如:
component
你可以狠狠点击此处查看。通过这个示例,我们可以知道is绑定的值currentTabComponent
可以是一个组件名,但事实上,我们还可以绑定一个组件的选项对象,如下例:
component
你可以狠狠点击此处查看。
在解析DOM模板时,我们还需要注意一点,那就是有些HTML元素,诸如<ul>,<ol>,<table>,<select>
,对于哪些元素出现在其内部是有严格限制的,而有些元素,诸如<li>,<tr>,<option>
,只能出现在特定的元素内部。这会导致我们在使用这些有约束的元素时出现问题,如:
component
我们可以打开浏览器控制台,看到以上自定义的ew-div
组件中的元素,被渲染到最外部去了,如下图所示:
你可以狠狠点击此处查看这个问题。
幸运的是,你可以使用is
解决这个问题。如:
component
此时再看浏览器控制台,你会发现自定义的组件已经被渲染在table
内部,如下图所示:
你可以狠狠点击此处查看。
当然,如果我们从如下来源使用模板的话,这条限制是不存在的。
1.字符串(如:template:'')
2.单文件组件(.vue)
3.<script type="text/x-template"></script>
到目前为止,需要了解的组件基础知识大概就只有这些呢,接下来是深入组件的了解。
四.组件深入
1.组件注册
在注册一个组件的时候,我们需要为组件起一个名字,比如全局注册的时候,我们已经知道了
Vue.component('my-component-name',{});
也就是Vue.component()
的第一个参数。在给组件起名的时候,如果不是字符串模板或者单文件组件
,直接在DOM中使用组件,组件名是有规范的(字母全小写,并且单词之间用连字符连接)
。你可以查阅API。
这也就是说定义组件名有两种方式,即(kebab-case)短横线分隔命名
与(PascalCase)首字母大写命名
。当使用短横线命名时,我们在使用组件的时候,也必须是采用短横线命名,而使用首字母大写命名则既可以采用首字母大写也可以采用短横线命名,当然在DOM模板而非字符串模板或者单文件组件中,也还是只能采用短横线命名。
到目前为止,我们只是用Vue.component
来全局注册一个组件,如:
Vue.component('com-a',{})Vue.component('com-b',{})Vue.component('com-c',{})
然后,你就可以在new Vue()
根实例内的任何组件中使用这三个组件,比如:
//jsnew Vue(el:"#app")//html
这往往是不够理想的,比如在一个通过webpack
构建的系统应用中,全局注册所有的组件,则意味着即便你不再使用一个组件了,但组件仍然被包含在最终构建的结果中,这也就增加了一些无谓的JavaScript代码。
不过,我们可以局部注册组件。局部注册也就是将一个组件当作普通的JavaScript对象来定义。如下例:
component
通过JavaScript定义一个组件对象,然后在vue实例
中添加components
选项,components
选项的属性名就是组件名,属性值就是这个组件的选项对象。你可以狠狠点击此处查看效果。
但是局部注册组件是无法在其子组件中使用另一局部注册的组件的,如果想要这样的效果,那么代码需要写成下面那样:
component
也就是往局部注册组件对象中添加一个components
选项,注册需要添加的局部注册的组件而已。你可以狠狠点击此处查看效果。
如果是使用es6与babel
以及webpack
,那么代码,则更像是如下:
import componentA from './componentA.vue'export default{ components:{ componentA }}
在对象中放一个componentA
其实也就是componentA:componentA
的缩写,也就是说这个变量名同时是:
用在模板中的自定义元素的名称。
包含了这个组件的选项的变量名。
如果你还不熟悉模块化构建,那最好还是跳过这里。
2.prop
由于HTML特性
是不区分大小写的,也就是说浏览器会将所有的大写字母转换成小写字母,这也就意味着当你使用camelCase(驼峰命名法)
来为prop
定义的时候,你在子组件使用prop
的时候需要使用等价的kebab-case(短横线命名)
来命名,比如:
prop { { content }}
在这里,我们是使用的局部注册一个组件,也就是用一个对象表示一个组件,然后在Vue实例
中添加components
选项来注册这个组件,组件名虽然使用的camelCase(驼峰命名法)
,也就是ewButton
,但我们也看到了,在使用这个组件的时候,需要使用kebab-case(短横线命名)
,即ew-button
来表示,同样的,定义的prop名
也是如此,在子组件中定义成ewType
,但在使用的时候则是ew-prop
,当然在单文件组件
以及字符串模板
中是不存在这个限制的,这里是使用DOM模板
来表示的。
你可以狠狠点击此处查看效果。
到目前为止,我们只看到了prop
的形式只是一个字符串数组:
props:['str','num','bool','arr','obj']
但是,通常我们想要每个prop
都有指定的值和类型,这时候,我们可以以对象的形式来列出prop
,这些属性的名称和值分别代表prop名
和类型
。如下:
props:{ str:String, num:Number, bool:Boolean, arr:Array, obj:Object}
如果指定的prop类型
不对,那么浏览器就会在控制台报一个invalid prop
的错误,这是vue
内部实现的。
在此之前,我们可以看到prop
有两种用法,第一种是静态的prop
,如上例的:
{ { content }}
这里的ew-type
就是一个静态的prop
,但实际上,我们还可以用v-bind
来表示一个动态的prop
,以上的静态prop
,我们就可以表示成如下:
{ { content }} //或者{ { content }}
有了v-bind
,我们就可以为prop
绑定任意类型的值。例如:
以下是传递一个数值:
//即便100是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串{ { content }} //绑定一个变量名{ { content }}
在js代码中,我们就可以如此写:
new Vue({ ... data(){ return{ number:100 } } ...})
以下是传递一个布尔值:
//如果不传入值,默认就是一个布尔值true{ { content }} //即便false是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串{ { content }} /绑定一个变量名{ { content }}
在js代码中,我们就可以如此写:
new Vue({ ... data(){ return{ bool:false } } ...})
以下是传递一个数组:
//即便数组是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串{ { content }} /绑定一个变量名{ { content }}
在js代码中,我们就可以如此写:
new Vue({ ... data(){ return{ arr:[1,2,3] } } ...})
以下是传递一个对象:
//即便对象是静态的,我们也需要使用v-bind来告诉vue,这是一个JavaScript表达式,而不是字符串{ { content }} /绑定一个变量名{ { content }}
在js代码中,我们就可以如此写:
new Vue({ ... data(){ return{ obj:{ name:"eveningwater", sex:"male" } } } ...})
如果你想将一个对象的所有属性都作为prop
传入,那么你可以使用不带参数的v-bind
来取代v-bind:propName
。例如:
prop //等价于
以上定义的v-bind="item"
实际上就等价于v-bind:name="item.name"
与v-bind:sex="item.sex"
,所以,我们才可以在子组件的props
里获取到name
和sex
,并且将props
的值绑定到元素中去。你可以狠狠点击此处查看效果。
所有的prop
都使得父子之间的prop
形成了一个单向下行绑定
:父组件的prop
如果发生了更新,则会向下流动到子组件中,但是反过来修改子组件的prop
就不行。这样也就防止了子组件意外改变父组件的状态,从而导致应用的数据流难以理解。
这也就是说一旦父组件更新了,那么子组件也就会立刻刷新为最新的值,这也意味着你不应该修改一个子组件的prop
,如果你如此做了,浏览器会发出一个警告,这是vue
内部实现的。来看一个示例:
prop
如上例,当我们点击了按钮,去修改prop
的值,尽管最终值发生了改变,但是浏览器控制台还是给出了一个警告信息,如下图所示:
你可以狠狠点击此处查看效果。实际上,这个警告信息也就是提示,不能直接在子组件中修改prop
,但是我们有两种办法解决这个问题。
1.我们将父组件传给子组件的prop
作为一个初始值,然后在子组件的data选项
中定义一个新的数据,将prop
的值用作其初始值,然后在绑定数据的时候就不用绑定prop
,而是绑定定义在data选项中
的值。如下所示:
prop
现在,我们再点击按钮,值发生了改变,而且浏览器也没有给出警告。你可以狠狠点击此处查看效果。
2.如果是这个prop
作为初始值,并且要进行转换,那么,最好还是使用一个计算属性来表示。例如:
prop
你可以狠狠点击此处查看效果。又或者,综合以上两个方法,修改如下:
prop
你可以狠狠点击此处查看效果。
注意在JavaScript数组和对象中,由于数组和对象是通过引用传入的,因此修改子组件的数据可能会影响到父组件的状态。
在使用prop
的时候,我们还可以为prop
指定验证要求,如果不满足验证要求,则vue
会在浏览器控制台中警告你。这在对于开发一个被别人用到的组件时尤为重要。
为了指定prop
的验证需求,你应该为props
的值指定一个带有验证需求的对象,而不是字符串数组。如下一个示例:
validate-prop
根据以上示例,我们可以得知一个prop
可以验证js当中的基本数据类型,也可以验证对象或数组,可以指定必要值以及默认值,还可以自定义验证函数。你可以狠狠点击此处查看效果。
这里需要注意一点,就是prop
会在一个组件实例创建之前进行验证,因此实例中的data
,computed
等属性在default
和validator
函数中是不可用的。
前面提到prop
可以通过一个type
属性来进行验证,在这里,type
的值可以是如下几个原生构造函数:
Number,String,Boolean,Object,Array,Function,Date,Symbol
。
额外的,type
其实也可以是一个自定义的构造函数,并且通过instanceof
来检查确认。比如以下一个示例:
prop type
在这里,只要name
是自定义构造函数FullName
的实例,那么数据就验证成功,如果不是,那么vue
就会在浏览器控制台给出警告。你可以狠狠点击此处查看效果。
在前面,我们都是显式的定义prop
,然而,当我们不显式的定义prop
的时候,实际上,它也作为了一个特性,传给了组件,只不过它是一个非prop
特性,因为该组件并没有显式的定义该prop
特性,显式的定义prop
固然适用于传向一个组件,但是组件库的作者并不能总是预见到prop
特性会应用于什么样的场景。所以如果不显式的定义prop
特性,那么该特性就会被添加到组件的根元素上。如下例:
not prop
在浏览器控制台,我们可以看到,name
特性被添加到class为name
的根元素上,如下图所示:
你可以狠狠点击此处查看效果。
在这里,对于绝大多数的特性来说,外部提供的特性都会替换组件内部的特性,例如想象一下,如果组件内部是这样一个模板:
如果我们在组件外部提供一个type="number"
的特性,一旦替换掉组件内部的特性,那么就改变了原有的DOM元素,这并不是我们所想要的。来看如下一个例子:
replace prop
显然,type="number"
已经替换掉了原有的type="text"
属性,这并不是我们所想要的。
庆幸的是,class
与style
则会替换或合并原有的特性,只要原有特性与提供的特性不同,那么就会合并原有的特性。如下例所示:
replace prop
现在,你在控制台中查看该组件的根元素,你会发现class
和style
都已经被合并了。你可以狠狠点击此处查看效果。
虽然是如此,但我们有时候并不想要这样的效果,也就是说我们想要组件的根元素不能继承特性
,那么我们可以在组件的选项中设置inheritAttrs:false
。格式如下:
Vue.component('my-component',{ inheritAttrs:false, ......})
当然,这个属性的设置并不会影响style
与class
的绑定。我们可以将这个属性与实例的$attrs
属性一起结合使用,在构造基础组件的时候非常常用,$attrs
属性也是一个对象,包含传递给一个组件的特性名和特性值。例如:
{ placeholder:"请输入你的用户名", id:"#myInput"}
这个模式更允许你像写原始的HTML元素一样写一个组件,而不需要担心组件的根元素到底是哪个元素。如下例:
inherit prop
这样,我们就已经完成了一个基本的表单输入框组件,再加点样式扩展,我们可以完美的构造一个媲美常用的UI框架的表单输入框组件。你可以狠狠的点击此处查看效果。
3.自定义事件
事件名不同于prop
和组件名的注册,是不存在大小写的转换的。也就是说事件名注册的是什么,使用的时候就应该是什么。比如如果用camelCase
来定义事件名的话,那么用kebab-case
的事件名是无效的。如下例:
emit {
{ count }}
在这个示例中,无论我们怎么点击按钮,都不会触发自定义事件,因为我们事件定义的名称是camelCase
,而在使用的时候则是kebab-case
,这也说明了事件名是不存在大小写的转换的。你可以狠狠点击此处查看效果。
所以,推荐用kebab-case
来定义事件名。
v-model
指令默认是利用一个props
和一个名为input
的事件来完成的,但在像单选框,复选框,下拉框等类型的控件中可能会将value
特性用于其它目的。所以vue
提供了model
选项来避免这样的冲突,来看一个自定义v-model
的示例:
自定义v-model 游戏 是否选中:{ { gameChecked }}
这里的gameChecked
的值会被传入名为checked
的prop
,同时当base-checkbox
被更新的时候,这个作为属性的值也会发生相应的改变。你可以狠狠点击此处查看效果。
在这里你需要注意的就是,需要显式的声明checked
这个prop
。
在某些时候,我们是想要直接在组件上监听一个元素的原生事件的,这个时候,我们可以直接使用.native
事件修饰符。比如:
native {
{ count }}
你可以点击此处查看效果。
在有的时候,这样确实不错,但事实上,在有些情况下,这样做却并不是一个好主意。