Vue.js启航——英雄编辑器(二)
简介
上一篇关于英雄编辑器的文章写到了简单的双向数据绑定和简单的组件式开发。这一篇文章将会更加深入的使用组件和指令。
主从结构的英雄编辑器
主从结构在当前应用领域是非常流行的,常常用于数据索引和数据查询,让我们先看看主从结构的英雄编辑器的实现效果
从实现效果可以看出整个应用使用了列表渲染,条件渲染,双向绑定,样式绑定等,双向绑定都很熟悉,那什么是列表渲染,条件渲染以及样式绑定呢
- 列表渲染:用 v-for
指令根据一组数组的选项列表进行渲染。
- 条件渲染:用 v-if
指令组根据条件来选择渲染,v-if
指令组包括 v-if
, v-else
, v-else-if
(2.1.0新增)
- 样式绑定:用 v-bind
将class和样式进行绑定,使其能够根据数据显示不同的样式
模版
先来讲解一下模版
<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>
<template v-if="selectedHero">
<h2>{{selectedHero.name}} details</h2>
<div><label>id: </label>{{selectedHero.id}}</div>
<div>
<label>name: </label>
<input v-model="selectedHero.name" placeholder="name"/>
</div>
</template>
</div>
这个模版分了两个部分,一个是英雄列表的显示,一个是上一篇文章写的简单的编辑。在英雄列表显示这一部分上使用了列表渲染,具体的代码是
<li v-for="hero in heroes" ...>....</li>
主要的意思是遍历 heroes
集合里的所有元素,并显示出来。具体的 heroes
就通过指定实例时使用 v-bind:heroes
来绑定。之后就可以在遍历标签内使用 hero
具体代表 heroes
集合内的元素。如:
<li v-for='hero in heroes'>
<span>{{hero.id}}</span>{{hero.name}}
</li>
然后就是简单的编辑,这次加入了条件渲染,也就是 v-if
指令,下面代码的意思是,在 selectedHero
这个对象值不为 false
以及解释为 false
的取值时,就会渲染出来,否则就不渲染。实现出来的效果是,当一开始打开页面的时候,这一部分并没有出现,只有等到你点击了某个英雄 selectedHero
被赋值时,这一部分的代码才会被渲染出来。
<template v-if="selectedHero">
...
</template>
有一点要注意的是 <template></template>
标签只是一个渲染容器,在渲染后会去掉,也就是在你看到的网页源码中不会出现 <template></template>
标签
在模版里还是用了样式绑定这一技巧,那么什么是样式绑定呢,我们在编写css文件的时候会使用选择器来选择元素进行样式编写,在这个模版的样式中我们使用了 class
来编辑样式,在编写传统的html/css/js网页时我们也会改变标签的 className
属性来改变样式,而使用Vue.js的实现就是通过 v-bind:class
指令来实现,看一下模版里的有关样式绑定的代码
v-bind:class="{selected:hero===selectedHero}"
这行代码的含义是当 hero===selectedHero
表达式为 true
时,绑定 .selected
样式,在传统的js改变样式是改变标签属性的值,而到了Vue.js就是改变表达式的真值,这样我们就可以很方便的为这个标签指定不同的样式样式,例如,我们在 active1
条件下激活 class1
样式,在 active2
条件下激活 class2
样式,写法为
<div v-bind:class=[{class1:activ1},{class2:active2}]></div>
传统的js还有直接改变标签的 style
属性的值来改变样式,而Vue.js当然也是可以的,通过 v-bind:style
指令就可以实现,如
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
当然也是可以引用实例的 data
选项的值来改变,如下所示就是将 style
的值设置为 color:'red', fontSize:'13px'
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
接着是DOM事件处理部分,这个模版中有一处DOM事件处理,在Vue.js中事件处理一般是使用 v-on
指令,下面处理得是 click
时间所以使用 v-on:click
, onSelect(hero)
是事件处理的方法,具体如何定义将会在数据传递以及数据处理那一部分讲解。
<li ... v-on:click="onSelect(hero)">...</li>
到这里我们的模版也就解释完了,下一部分就解释组件中的 props
, data
以及 methods
,这三个选项主要进行组件的数据传递以及数据处理的
数据传递以及数据处理
在这里我们先理清,组件的内部数据以及组件的外部数据,也称为父组件的数据以及子组件的数据。我的理解就是父组件的数据就是要通过 props
传递进去组件的数据,而子组件的数据就是 data
引入的数据。那么父组件的数据和子组件的数据的处理有什么不同吗,暂时我遇到的第一个对于数据传递的坑就是Vue.js对于父组件数据的直接修改会警告。Vue.js的数据流使用的是单向数据流,具体就是,父组件的数据变化会导致子组件相应数据的变化,而子组件改变了传入的父组件的数据,父组件的数据不会发生改变。那么究竟如何改变父数据的值才不会警告呢,官方文档给了两种方法
- 定义一个局部变量,并用 prop 的值初始化它:
- 定义一个计算属性,处理 prop 的值并返回
说了一堆理论的,看一下我的英雄编辑器是怎么处理数据传递以及处理的,首先外部数据我使用了两个,分别是 title
和 heroes
,title
就是网页的标题, heroes
就是英雄列表,这两个数据至少在这篇文章的主从结构的英雄编辑器中是不会有改动的,很好处理,因为根本不用处理,内部数据只有 selectedHero
,这是标识选中的英雄,所以是经常会发生改变的,使用内部数据会使得改变数据更加灵活,使用data
选项时要注意,data
选项必须是函数,所以我在定义 selectedHero
时的定义方法是:
data:function(){
return {selectedHero:""}
}
关于数据处理,我们可以使用 methods
选项,也可以使用 computed
选项,关于这两者的区别以后会讲到。这一次我使用的是最好理解的 methods
选项,在上面我们说到事件处理方法 onSelect(hero)
的定义就是在 methods
选项中定义的,具体的定义方式为:
methods:{
onSelect:function(hero){
this.selectedHero=hero
}
}
上面代码与 v-on:click="onSelect(hero)
结合起来就是当点击一个 hero
时,就会把 selectedHero
改为对应的 hero
,接下来就会发生,样式改变,英雄编辑器数据的改变等连锁反应。
到这里我们这个主从结构的英雄编辑器就差不多做好了,接下来就是创建一个Vue实例,使用 v-bind
指令将外部数据传入就可以了。
总结
在五一假期的最后一天写完了这篇文章,感觉很不错。在实现这个主从结构效果的时候确实遇到不少坑,首先,一开始没有理解内部数据以及外部数据的区别,selectedHero
使用 props
选项传入,然后直接使用 this.selectedHero=hero
来进行修改。其实运行还是可以的,但是Vue会发出警告,而且这确实是个不好的习惯,要从源头开始改正。
然后就是摸着石头过河,根本不知道哪些数据需要外部传入,哪些数据只要内部处理。一开始不仅 selectedHero
是通过 props
传入,甚至连 onSelect(hero)
, selected
等,反正是所有要使用到的数据都通过 props
传入。经过好几次的尝试和不断的查找资料修改才得出最终的结果,可能这也不是最好的结果,毕竟只是尝试了一个下午。况且当他是一个下午出来的最好的结果吧。
&nbps;还有就是 onSelect(hero)
应该是定义在实例中呢,还是定义在组件中呢,一开始也是没有个概念,也是通过查找资料才确定的。onSelect(hero)
主要是对组件的内部数据进行处理的,所以在没弄清楚内部数据和外部数据的时候根本是不可能确定这个方法在哪里定义,当明确了内部数据和外部数据的时候,方法的定义就迎刃而解了。
总结一下知识点:
- 使用
v-bind:class
和v-bind:style
进行样式的绑定 - 使用
v-if
指令族进行条件渲染 - 使用
v-for
指令进行列表渲染 - 组件中
props
和data
的区别与使用方法 - 组件中
methods
选项的定义和使用 <template></template>
作为渲染容器的作用
可运行代码
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>
</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' }
];
//主从结构组件
Vue.component("host-guest",{
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>\
<template v-if="selectedHero">\
<h2>{{selectedHero.name}} details</h2>\
<div><label>id: </label>{{selectedHero.id}}</div>\
<div>\
<label>name: </label>\
<input v-model="selectedHero.name" placeholder="name"/>\
</div>\
</template>\
</div>',
data:function(){
return {selectedHero:""}
},
methods:{
onSelect:function(hero){
this.selectedHero=hero
}
},
})
var app_host_guest=new Vue({
el:"#host-guest-app",
data:{
title:"Tour of Heroes",
heroes:HEROES,
},
})
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;
}