[Vue.js启航]——多组件应用构建
2017-05-06 13:45
555 查看
Vue.js启航——英雄编辑器(三 )
简介
上一篇文章写到了主从结构的英雄编辑器,其中大概用到了两个组件,英雄列表组件和英雄详情组件,但我们并没有把他们分开来,这一篇文章我们就来将他们解构,把一整个组件分成英雄列表组件和英雄详情组件。多组件的英雄编辑器
组件复用是软件工程第一种重要的思想,通过组件设计使得组件具有良好的复用性,可以减少重复性代码,降低测试难度。多组件的英雄编辑器和主从结构的英雄编辑器的实现效果是一样的,具体实现效果如下英雄列表组件
首先我们来看一下英雄列表组件,跟主从结构中的英雄列表组件大同小异//英雄列表组件 Vue.component("hero-list",{ props:["title","heroes"], template:'\ <div>\ <h1>{{title}}</h1>\ <h2>My Heroes</h2>\ <ul class="heroes">\ <li v-for="hero in heroes"\ v-on:click="onSelect(hero)"\ v-bind:class="{selected:hero===selectedHero}">\ <span class="badge">{{hero.id}}</span>{{hero.name}}\ </li>\ </ul>\ </div> \ ', methods:{ onSelect:function(hero){ this.selectedHero=hero bus.$emit("selectHero",this.selectedHero); } }, data:function(){ return {selectedHero:""}; } })
不同点就在于
onSelect方法中多了
bus.$emit(),
bus是我们用于非父子组件中通信所定义的实例,而
$emit是Vue中用于触发事件的函数,关于非父子组件的通信会在下面讲到。
英雄列表组件与英雄详情组件的通信
非父子组件的通信在官方指南里有提到有时候两个组件也需要通信(非父子关系)。在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线。在复杂的情况下,我们应该考虑使用专门的 状态管理模式.
所谓使用空的Vue实例作为中央事件总线就是我们现在使用的
bus实例,在
bus实例中定义监听事件
selectHero,然后在英雄列表组件中触发该事件。将需要传递的数据存放在
bus实例中,然后再在英雄详情组件中引用该数据。在这里我的实现方法是在具体实例中使用
computed选项来即时改变数据,然后使用
v-bind:hero="hero"将数据绑定到英雄详情组件中。具体流程如下
这里我们使用到
computed选项,
computed选项能够在相关一来发生改变时重新求值。这是什么意思呢,例如在这个例子中,具体实例中的
computed选项如下
computed:{ hero:function(){ return bus.hero } }
在这个时候,当
bus.hero这个依赖值发生改变时,会促使具体实例中
hero数据值的变化。
接下来再简单介绍一下Vue中自定义事件的应用。官方指南中对于Vue事件的解释
每个 Vue 实例都实现了事件接口(Events interface),即:
使用
$on(eventName)监听事件
使用
$emit(eventName)触发事件
在这个例子中我们在
bus定义
selectHero事件的代码如下
created:function(){ this.$on("selectHero",this.selectHero) }
created选项是Vue定义的一个生命周期钩子,这段代码具体的意思是在实例被创建之后就监听
selectedHero事件,事件具体的实现方法是
this.selectHero,
this.selectHero,是
bus实例中定义的方法,具体代码如下
methods:{ selectHero:function(hero){ this.hero=hero; } }
在触发了该事件之后会将
bus实例中的
hero数据改变。
再来看看触发事件的代码,触发事件实在英雄列表实例中,
onSelect:function(hero){ ... bus.$emit("selectHero",this.selectedHero); }
bus.$emit("selectHero",this.selectedHero),这段代码是指触发
bus实例中的
selectHero事件,并传入参数
this.selectedHero。
经过事件监听和事件触发就能成功把
hero实例中的
hero数据改变为英雄列表中的
selectedHero数据。然后再经过具体实例中的
computed选项将数据传入到具体实例中,再接着使用
v-bind指令就能够将数据传入到英雄详情组件中了。是不是感觉很复杂。以后我们会使用更加简洁,更加统一的方法实现非父子组件中的数据通信,那就是状态管理.
英雄详情组件
如果说英雄列表组件尚且有不同,那么英雄详情组件可以说是完全相同了。//英雄详情组件 Vue.component("hero-detail",{ props:["hero"], template:'\ <div v-if="hero">\ <h2>{{hero.name}}</h2>\ <div><label>id:</label>{{hero.id}}</div>\ <div>\ <label>name:</label>\ <input v-model="hero.name" placeholder="name"/>\ </div>\ </div>\ ', })
由于非父子组件通信的原因,
hero从外部传入,然后流程跟主从结构中的一模一样。因而没什么好说的。
总结
这篇文章写的是多组件的应用,而使用多组件的核心就是组件间的通信问题。这篇文章中我是使用了一种很笨拙的方法实现组件间的通信。也来聊一聊我在实现这个例子时遇到的坑,对于非父子组件的通信官方指南只是一笔带过,并没有具体的例子。最后的实现只是靠我自己的不断摸索。一开始英雄详情中的hero数据我是这样写的
data:function(){ return {hero:bus.hero} }
然后发现我点击英雄列表时英雄详情并没有显示出来,但
bus.hero已经是变了,而具体实例中的
hero并没有跟着更新
其实是因为
data选项的数据并不会根据依赖的改变而改变数据,要实现数据根据依赖来改变,可以通过
methods,
computed,
watched选项来设定,具体可以参考计算属性。这样就能实现数据根据依赖的改变而变动。但
computed选项是实例的选项并不是组件的选项,所以只能在具体实例中定义
computed选项。
这篇文章所用到的知识点:
-
$on,
$emit实现自定义事件的监听和触发
- 非父子组件间通信的简单方法
- 实例中
computed选项的使用
- 多组件构建思想
可运行代码
<!--hero.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" href="hero.css"> </head> <body> 、 <!-- <div id="host-guest-app"> <host-guest v-bind:title="title" v-bind:heroes="heroes" ></host-guest> </div> --> <div id="hero"> <hero-list v-bind:heroes="heroes" v-bind:title="title"></hero-list> <hero-detail v-bind:hero="hero"></hero-detail> </div> </body> <script src="https://unpkg.com/vue/dist/vue.js"></script> <script src="hero.js"></script> </html>
//hero.js
//十位英雄数据
const HEROES=[
{ id: 11, name: 'Mr. Nice' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Celeritas' },
{ id: 15, name: 'Magneta' },
{ id: 16, name: 'RubberMan' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dr IQ' },
{ id: 19, name: 'Magma' },
{ id: 20, name: 'Tornado' }
];
//多个组件
//事件总线
var bus=new Vue({
data:{
hero:""
},
created:function(){
this.$on("selectHero",this.selectHero)
},
beforeDestroy:function(){
this.$off("selectHero",this.selectHero)
},
methods:{ selectHero:function(hero){ this.hero=hero; } }});
//英雄详情组件 Vue.component("hero-detail",{ props:["hero"], template:'\ <div v-if="hero">\ <h2>{{hero.name}}</h2>\ <div><label>id:</label>{{hero.id}}</div>\ <div>\ <label>name:</label>\ <input v-model="hero.name" placeholder="name"/>\ </div>\ </div>\ ', })
//英雄列表组件
Vue.component("hero-list",{
props:["title","heroes"],
template:'\
<div>\
<h1>{{title}}</h1>\
<h2>My Heroes</h2>\
<ul class="heroes">\
<li v-for="hero in heroes"\
v-on:click="onSelect(hero)"\
v-bind:class="{selected:hero===selectedHero}">\
<span class="badge">{{hero.id}}</span>{{hero.name}}\
</li>\
</ul>\
</div> \
',
methods:{
onSelect:function(hero){
this.selectedHero=hero
bus.$emit("selectHero",this.selectedHero);
}
},
data:function(){
return {selectedHero:""};
}
})
var hero=new Vue({
el:"#hero",
data:{
heroes:HEROES,
title:"Tour of Heroes",
},
computed:{
hero:function(){
return bus.hero
}
},
})
//hero.css h1 { color: #369; font-family: Arial, Helvetica, sans-serif; font-size: 250%; } h2, h3 { color: #444; font-family: Arial, Helvetica, sans-serif; font-weight: lighter; } .selected { background-color: #CFD8DC !important; color: white; } body { margin: 2em; } body, input[text] { color: #888; font-family: Cambria, Georgia; } .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes li.selected:hover { background-color: #BBD8DC !important; color: white; } .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } .heroes .text { position: relative; top: -3px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; }
相关文章推荐
- [Vue.js启航]——主从结构应用构建
- 利用纯Vue.js构建Bootstrap组件
- vue用组件构建应用
- Asp.net+Vue2构建简单记账WebApp之三(使用Vue-cli构建vue.js应用)
- 用Vue.js递归组件构建一个可折叠的树形菜单
- 2018 年你需要知道的 Vue.js 组件库,完善你的应用开发
- 如何使用Vuex+Vue.js构建单页应用
- Vue.js使用vue-router构建单页应用
- 七周七种前端框架四:Vue.js 构建大型应用
- Vue.js教程: 构建一个预渲染SEO友好的应用示例
- Vue.js递归组件构建树形菜单
- 使用 Vuex + Vue.js 构建单页应用
- JS组件系列之MVVM组件构建自己的Vue组件
- JS组件系列——又一款MVVM组件:Vue(二:构建自己的Vue组件)
- Vue.js—组件快速入门及Vue路由实例应用
- 使用Vue.js 2.0搭建单页应用:从构建到部署
- vue用组件构建应用
- vue.js+vue-router+sea.js构建SPA应用
- Vue.js使用vue-router构建单页应用
- 用Vue.js递归组件构建一个可折叠的树形菜单