该篇为翻译文章:原文为:Intro to Vue.js: Components, Props, and Slots
该系列文章目录:
Vue.js #1-渲染、指令、事件(原文)Vue.js #2-组件、属性、Slots (当前)
组件当中的数据传输
如果你熟悉React或者Angular2的话,那么对于组件之间传递数据想关的操作你一定不会陌生。不熟悉也没关系,我们接下来就会说到。
一个网站应用不管是大还是小,它都是由一个个小模块组成的,这样非常易于维护,例如:
<header></header>
<aside>
<sidebar-item v-for="item in items"></sidebar-item>
</aside>
<main>
<blogpost v-for="post in posts"></blogpost>
</main>
<footer></footer>
这是一个非常简单的例子,你可以发现这种方式非常的易于管理。
Vue可以通过几种不同的方式来创建组件,让我们由浅入深。但是记住,再复杂的应用都是由一个一个很普通的组件组成的。
app.$mount('#app');
var app = new Vue({
el: 'hello',
template: '<h1>Hello World!</h1>'
});
上面的代码完全可以跑起来,但是它并不是一个好例子,因为它只并不能被重复使用。我们要的是一个可复用的组件,从而方便我们给不同的组件实例传递不同的参数。从父组件传递到子组件的一种方法是使用props
。
下面我来举一个简单的例子,下面的代码中,Vue.component
是一个组件,new Vue
叫做Vue实例。在一个应用当中,可以有多个Vue实例,但一般来说,我们在一个应用当中有一个实例和一些组件,这个实例就是我们整个APP的实例。
Vue.component('child', {
props: ['text'],
template: `<div>{{ text }}<div>`
});
new Vue({
el: "#app",
data() {
return {
message: 'hello mr. magoo'
}
}
});
<div id="app">
<child :text="message"></child>
</div>
<div id="app">
<child :text="message"></child>
</div>
结果:http://codepen.io/sdras/pen/788a6a21e95589098af070c321214b78
现在,我们可以在整个应用当中重复使用这个组件:
<div id="app">
<child :text="message"></child>
<child :text="message"></child>
</div>
结果:http://codepen.io/sdras/pen/9c04bdcf1a2d0c770d6a1fd4af3c66f3
我们也可以为属性添加验证,就像React中的PropTypes
。这个功能非常好,因为这样可以实现「自我文档化」,如果参数不是我们想要的,那么将会返回错误信息,但要注意的是,这只在开发模式下才起作用:
Vue.component('child', {
props: {
text: {
type: String,
required: true
}
},
template: `<div>{{ text }}<div>`
});
下面的例子当中,我加载的是Vue开发版,而我偏偏传入了一个错误类型的值。这个时候你可以在console面板当中看到一条错误信息。
Vue.component('child', {
props: {
text: {
type: Boolean,
required: true
}
},
template: `<div>{{ text }}<div>`
});
结果:http://codepen.io/sdras/pen/d6fcaeee50a530d9a5e5832f0aec0773
对象和数组应该由一个工厂函数来进行返回。你甚至可以创建一个自定义的验证函数。你可以根据你的业务逻辑来检测你的值是否准确。更多关于验证函数的内容,官方的「Prop Validation」写的非常详细。
当然 ,除了传入一个变量数据之外 ,你还可以传入一个静态值:
Vue.component('child', {
props: {
count: {
type: Number,
required: true
}
},
template: `<div class="num">{{ count }}</div>`
})
new Vue({
el: '#app',
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
}
})
<div id="app">
<h3>
<button @click="increment">+</button>
Adjust the state
<button @click="decrement">-</button>
</h3>
<h2>This is the app state: <span class="num">{{ count }}</span></h2>
<hr />
<h4><child count="1"></child></h4>
<p>This is a child counter that is using a static integer as props</p>
<hr />
<h4><child :count="count"></child></h4>
<p>This is the same child counter and it is using the state as props</p>
</div>
结果:http://codepen.io/sdras/pen/5a34f6ed12cf954202c6d38f1ceba633
它们的区别在于你是否绑定一个属性:
不绑定属性:<child count="1"></child>
VS
绑定属性:<child :count="count"></child>
在例子当中,你注意到我们使用了ES6当中的「模板字符串」(强烈推荐大家使用Babel来对ES6进行编译转换):
Vue.component('individual-comment', {
template:
`<li> {{ commentpost }} </li>`,
props: ['commentpost']
});
new Vue({
el: '#app',
data: {
newComment: '',
comments: [
'Looks great Julianne!',
'I love the sea',
'Where are you at?'
]
},
methods: {
addComment: function () {
this.comments.push(this.newComment)
this.newComment = ''
}
}
});
<ul>
<li is="individual-comment"
v-for="comment in comments"
v-bind:commentpost="comment"
></li>
</ul>
<input v-model="newComment"
v-on:keyup.enter="addComment"
placeholder="Add a comment"
/>
结果:http://codepen.io/sdras/pen/cd81de1463229a9612dca7559dd666e0
译者:上面的代码当中在调用组件的时候,使用了
is
关键字来代替之前的直接将组件名称写成一个标签,这样做是为了避免HTML报错,详情见:
DOM Template Parsing Caveats
「模板字符串」对我们来说非常方便有用。但是如果需要写很多HTML在里面的话,仍然不是一个好办法,因为看起来会很乱。所以,我们使用一个单独的「模板」来解决这个问题,即将模板写在一个特殊的script
标签当中供我们调用:
<!-- This is the Individual Comment Component -->
<script type="text/x-template" id="comment-template">
<li>
<img class="post-img" :src="commentpost.authorImg" />
<small>{{ commentpost.author }}</small>
<p class="post-comment">"{{ commentpost.text }}"</p>
</li>
</script>
Vue.component('individual-comment', {
template: '#comment-template',
props: ['commentpost']
});
结果:http://codepen.io/sdras/pen/egEgXb
Slots (空位、占位符)
假如我们现在有两个组件,这两个组件之间有一些细小的不同(可能是内容的不同,也可能是样式的不同)。那么我们可以传入一些属性参数(props),针对每个组件传入不同的参数来达到目的,或者我们也可以多写几个类似的组件。但是这些解决方案只适用于用到类似的数据或者功能的组件。如果组件之间的变化不同之处特别大的话,那么这时候就需要Slots(占位符)介入了。
假如,我们现在在一个应用实例里用到了两次<app -child>
组件。而这两个组件当中,有相同的内容,有不同的内容。对于相同的内容,我们把这放到一个p
标签当中,而针对不同的内容,我们就需要使用<slot></slot>
标签了。
<script type="text/x-template" id="childarea">
<div class="child">
<slot></slot>
<p>It's a veritable slot machine!<br>
Ha ha aw</p>
</div>
</script>
然后,在应用实例里面,我们可以在</app><app -child>
组件标签中写任何我们想要的内容,这些内容到时候会替换<slot>
标签:
<div id="app">
<h2>We can use slots to populate content</h2>
<app-child>
<h3>This is slot number one</h3>
</app-child>
<app-child>
<h3>This is slot number two</h3>
<small>I can put more info in, too!</small>
</app-child>
</div>
结果:http://codepen.io/sdras/pen/e06f9393e73054e7185ff48dfa36e161
当然,你也可以写一个默认的内容在</slot><slot>
当中,例如</slot><slot>I am some default text</slot>
。这时,当你在调用组件的时候,如果你不写其它的内容的话,就会显示这块默认的内容。
你也可以给<slot>
取名字。如果你在一个组件当中有两个</slot><slot>
,这时候就需要给它们取不同的名字来进行区分了。比如,</slot><slot name="headerinfo"></slot>
,那么在调用组件的时候则需要指定slot:<h1 slot="headerinfo">I will populate the headerinfo slot!</h1>
,下面来看一个例子:
<div id="post">
<main>
<slot name="header"></slot>
<slot></slot>
</main>
</div>
<app-post>
<h1 slot="header">This is the main title</h1>
<p>I will go in the unnamed slot!</p>
</app-post>
<main>
<h1>This is the main title</h1>
<p>I will go in the unnamed slot!</p>
</main>
就我个人而言,如果用到了多个<slot>
的话,我会一一给它们命名,这样就不会乱。但不管怎么说,Vue提供了非常灵活的API。
Slots实例
我们可以为不同的组件设计不同的样式,但这些组件的内容是一样的。下面我们来做一个「酒瓶标签生成器」的例子,其中一个按钮是用来切换组件的颜色用的,当改变了瓶子的颜色后,瓶子的标签和文字的颜色将同时发生改变,而里面的内容是不变的。
const app = new Vue({
...
components: {
'appBlack': {
template: '#black'
}
}
});
APP实例的HTML:
<main>
<component :is="selected">
...
<path class="label" d="M12,295.9s56.5,5,137.6,0V409S78.1,423.6,12,409Z" transform="translate(-12 -13.8)" :style="{ fill: labelColor }"/>
...
</component>
<h4>Color</h4>
<button @click="selected ='appBlack', labelColor = '#000000'">Black Label</button>
<button @click="selected ='appWhite', labelColor = '#ffffff'">White Label</button>
<input type="color" v-model="labelColor" defaultValue="#ff0000">
变色组件HTML:
<script type="text/x-template" id="white">
<div class="white">
<slot></slot>
</div>
</script>
(这个例子相对于前面的来说比较的大,所以你最好一边看代码一边看结果)
这个例子当中,我们在里面插入了很多SVG,这些SVG都是放到组件的</slot><slot>
当中去的。我们通过一些按钮来改变内容或者样式。
我们把所有的东西都放到了一个</slot><slot>
当中,现在我们把它改成多个</slot><slot>
:
<!-- main vue app instance -->
<app-comment>
<p slot="comment">{{ comment.text }}</p>
</app-comment>
<!-- individual component -->
<script type="text/x-template" id="comment-template">
<div>
<slot name="comment"></slot>
</div>
</script>
我们可以在不同的组件之间进行切换,但使用的是同一个</slot><slot>
。但当我们在来回切换的时候,如果想保持每个组件自身的值不受到影响该怎么办呢?现在的例子里面,当我们在「黑」「白」之间进行切换的时候,模板进行了切换,但它们展现的内容(数据)是一样的。那么如果我们需要「黑」「白」都有自己的label数据的话,那么我们就需要在组件外面套上一个<keep -alive></keep>
来进行实现。
<keep-alive>
<component :is="selected">
...
</component>
</keep-alive>
结果:http://codepen.io/sdras/pen/db71c231f760ee3a53e9d4e65f8745b8
为了方便给大家展示,我们举的示例是把所有的东西都写在了一个或两个文件当中。其实当我们在真正做一个项目的时候,最好是把不同的组件放到不同的文件当中,然后在需要的时候进行导入即可。具体的细节,我们下回再说,下次让我们来聊聊Vue项目管理相关的东西,比如Vue-cli,Vuex等等。