前情回顾
上一节课,我们使用了组件化思维重构blog-post组件,这节课,我们通过一个小功能,继续加强blog-post组件,并通过这个过程,讲一讲子组件是如何与父组件传值的。
单个根元素
当构建博客组件<blog-post>
时,肯定不止一个标题属性。至少,要包含正文:
1<h3>{{ title }}</h3>
2<div v-html="content"></div>
但这样写,Vue会报错,并解释道 every component must have a single root element (每个组件必须只有一个根元素)。你可以将模板的内容包裹在一个父元素内,来修复这个问题,例如:
1<div class="blog-post">
2 <h3>{{ title }}</h3>
3 <div v-html="content"></div>
4</div>
这就是所谓的单个根元素。在Vue中每个组件必须只有一个根元素。
子组件与父组件如何传值?(难点)
在我们开发 <blog-post>
组件时,它的一些功能可能要求我们和父级组件进行沟通。例如我们可能会引入一个辅助功能来放大博文的字号,同时让页面的其它部分保持默认的字号。如何来实现这个功能呢?我们大概来想象下,首先肯定是有个按钮,点击放大字号,博文字号就会放大一点,点击一次放大一次。
第一步 定义postFontSize属性来支持放大博文字号功能
1var app = new Vue({
2 el: '#app',
3 data: {
4 posts: [
5 { id: 1, title: 'My journey with Vue', content: "...content..." },
6 { id: 2, title: 'Blogging with Vue', content: "...content..." },
7 { id: 3, title: 'Why Vue is so fun', content: "...content..." }
8 ],
9 postFontSize: 1
10 },
posts数组是博文数据,包括了标题和内容。postFontSize可以在HTML模板中,控制所有博文字号:
1<div id="app">
2 <div :style="{ fontSize: postFontSize + 'em'}">
3 <blog-post v-for="post of posts" :key="post.id"
4 :post="post"
5 @enlarge-text="handleEnlarge"
6 >
7 </blog-post>
8 </div>
9</div>
代码解析:
- 这里使用了v-bind绑定标签style属性,使得style属性变为动态的。
- fontSize的值等于postFontSize+em,只要按钮点击,我们控制postFontSize的值就相当于控制了整个博文字号了。
- blog-post里的指令后面再来分析。
第二步 创建blog-post组件
1Vue.component('blog-post',{
2 props: ['post'],
3 methods: {
4 handleBtnClick() {
5 this.$emit('enlarge-text')
6 }
7 },
8 template:`
9 <div class="blog-post">
10 <h3>{{post.title}}</h3>
11 <div v-html="post.content"></div>
12 <button @click="handleBtnClick">
13 放大博文标题
14 </button>
15 </div>
16 `
17
18 })
代码解析:
- 先看template,我们使用了单引号来阔起html代码,这样html代码可以多行编写
- 模板里的内容就是包含了标题和博文内容,以及一个button按钮
- 这个button按钮触发了一个点击事件handleBtnClick
- handleBtnClick事件里,通过调用内建的$emit方法并传入事件名称来触发一个enlarge-text事件。为什么要这样做?为什么不直接在handleBtnClick方法里写一段代码直接控制字体放大的功能呢?那是因为,子组件里没有办法直接控制父组件的数据,那怎么办呢?
子组件blog-post通过$emit触发事件的方式将值传给父组件
$emit方法是Vue内建的方法,除了传递事件名还可以将值作为第二个参数传递到父组件,现在问题来了?父组件如何接收?回头看看之前的HTML模板里的代码。
1<div id="app">
2 <div :style="{ fontSize: postFontSize + 'em'}">
3 <blog-post v-for="post of posts" :key="post.id"
4 :post="post"
5 @enlarge-text="handleEnlarge"
6 >
7 </blog-post>
8 </div>
9</div>
在blog-post中,自定义了一个enlarg-text事件,该事件的方法handleEnlarge就写在父组件中
1 var app = new Vue({
2 el: '#app',
3 data: {
4 posts: [
5 { id: 1, title: 'My journey with Vue', content: "...content..." },
6 { id: 2, title: 'Blogging with Vue', content: "...content..." },
7 { id: 3, title: 'Why Vue is so fun', content: "...content..." }
8 ],
9 postFontSize: 1
10 },
11 methods: {
12 handleEnlarge() {
13 this.postFontSize += 0.1
14 }
15 }
16 })
hanleEnlarge的作用就是一个,改变postFontSize的值,每次点击button一次,该值就加0.1,从而控制了博文字号。
第三步 完整代码如下:
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>blog-post组件</title>
6 <script src="vue.js"></script>
7 </head>
8 <body>
9 <div id="app">
10 <div :style="{ fontSize: postFontSize + 'em'}">
11 <blog-post v-for="post of posts" :key="post.id"
12 :post="post"
13 @enlarge-text="handleEnlarge"
14 >
15 </blog-post>
16 </div>
17 </div>
18 <script>
19 Vue.component('blog-post',{
20 props: ['post'],
21 methods: {
22 handleBtnClick() {
23 this.$emit('enlarge-text')
24 }
25 },q
26 template:`
27 <div class="blog-post">
28 <h3>{{post.title}}</h3>
29 <div v-html="post.content"></div>
30 <button @click="handleBtnClick">
31 放大博文标题
32 </button>
33 </div>
34 `
35
36 })
37 var app = new Vue({
38 el: '#app',
39 data: {
40 posts: [
41 { id: 1, title: 'My journey with Vue', content: "...content..." },
42 { id: 2, title: 'Blogging with Vue', content: "...content..." },
43 { id: 3, title: 'Why Vue is so fun', content: "...content..." }
44 ],
45 postFontSize: 1
46 },
47 methods: {
48 handleEnlarge() {
49 this.postFontSize += 0.1
50 }
51 }
52 })
53 </script>
54 </body>
55</html>
代码解析:
现在还剩下一个问题,props选项里接收了一个post是什么意思?我们可以看到,博文是有很多属性组成的,比如标题、作者、时间、内容等等,这里为了方便说明问题,只取了两个属性:标题和内容,就是posts数组里的title和content。
那么多属性,如果一个一个在html标签里通过v-bind绑定是不是很繁杂?如下代码:
1<blog-post
2 v-for="post in posts"
3 v-bind:key="post.id"
4 v-bind:title="post.title"
5 v-bind:content="post.content"
6 v-bind:publishedAt="post.publishedAt"
7 v-bind:comments="post.comments"
8></blog-post>
所以是时候重构一下这个组件了,让它变成接受一个单独的 post
prop:
1<blog-post
2 v-for="post in posts"
3 v-bind:key="post.id"
4 v-bind:post="post"
5></blog-post>
所以,这就是为什么props选项里只接收了一个post的原因了。
总结
- 这节课内容比较多,也比较难懂,需要同学们好好将代码敲出来实现功能,然后慢慢琢磨。
- 我们从这节课的代码里可以看到Vue的优点,只要定义好数据,想想你需要实现的功能,定义好方法,做好组件之间的传值,功能自然就实现了,而不需要去操作DOM,去思考这些无谓的问题。实际上,一个页面,就是组件组成,而功能无非就是数据加上组件之间通信,再加上恰当的方法,就可以实现了。
- 严格来说,不是叫子组件与父组件传值,应该叫子组件如何与父组件通信。子组件无法直接与父组件通信,必须通过Vue内建的$emit方法将事件传递出去,在HTML标签里,触发该事件来与父组件通信,也就是获取父组件的数据。
欢迎关注公众号从零单排vue,获取最新学习笔记及前端学习资源