[

这次我将带领大家看一下组件。下面让我们开始吧!

下面是我们的初始代码结构:

<html> <head> <title>Vue 101</title> </head> <body> <div id="app"> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { } }); </script> </body> </html>

基础组件

我们会从组件的创建入手,这将会是个有趣的开始。本章节只涉及一些组件基础操作,这并不代表着本章节不重要,本章节内容会对之后我们学习组件的 computed 属性 和 watchers 有巨大帮助,之后的章节的学习会在本章节基础之上,所以学好基础至关重要。

组件是 Vue.js 框架的核心部分,它们就像是用来构建响应式的,丰富应用程序的乐高积木。幸运的是,组件超级简单并且易学易用。

我们可以把组件想象为你在 网页 / App 中可以用一次或者多次的元素。它可以小如一个按钮或者输入框,大如整个菜单栏,组件甚至还可以作为整个页面视图。

组件化的优势在于你可以只写一次页面展示逻辑(HTML/CSS)与用户交互逻辑(JS) - 然后你就可以在 App 的任何地方调用这个组件。

我们一般都会从尽可能简单的例子开始,比如一个按钮。
首先规划一下这个组件的 蓝图 或者说 模板 ,暂且叫他 awesome-button 吧!

让我们定义一个新的 awesome-button 组件。将如下代码复制在你的 new Vue 声明之上。

Vue.component('awesome-button', { template: `<button @click="clickHandler">点我获得快乐</button>`, methods: { clickHandler() { alert('奥里给 ?'); } } });

我们在第一天引入的 Vue.js sciprt 标签提供了 Vue 对象,里边包含了这里需要调用的 component 方法。它允许我们创建一个我们需要的新组件。我们需要设置的第一个参数是组件的名字,它是一个 字符串

第二个参数是一个 JavaScript 对象,神奇的是这个对象与我们在主 Vue 实例 上的配置是相同的!这意味着什么?意味着你已经知道如何去为这实例赋值属性和方法了。

在上面这个示例中,你可能会注意到一个小细节,那就是 - template 属性。我们需要把这个属性定义为 字符串 (注意,我们还用了 反引号 去包裹这个字符串,这样就可以很方便的声明多行字符串而不是使用字符串拼接的方式去做),它将会保存这个 这个组件 的实际的 HTML 代码。在这个练习示例中,有一个简单的 button 标签就足够了。

如果现在刷新了页面,那么我们啥都看不到。还记得之前说组件只是一个 蓝图或模板么?是时候让它在我们的页面里边实际渲染出来了。

转到那个放置了所有标签的 <div id="app"> 处,放一个新的 <awesome-button> 元素进去。

现在的 HTML 看上去就像下面这样儿:

<div id="app"> <awesome-button></awesome-button> </div>

刷新下页面,就可以在渲染好的页面上看到那个按钮。然后按照自己的喜好在页面上放两个或者多个 awesome-button 标签。虽然这只是管中窥豹,但已经可以感受到组件的强大了吧。

注: 如果你是好奇宝宝,可以使用浏览器开发工具里边的 检查元素功能原始页面 做一下对比。 Vue.js<awesome-button> 作一个占位符,用来标记解析后的模板应该放到哪里。

第二节 - 一些更有用的东西#

让我们重新打开看一下上周的示例,用我们的 游戏 数据玩些新的花样。

首先,我们在主 Vue 实例的 data() 中重新添加一些游戏数据。

const app = new Vue({ el: '#app', data: { games: [ { name: '超级马里奥 64', console: '任天堂 64', rating: 4 }, { name: '塞尔达传说', console: '任天堂 64', rating: 5 }, { name: '圣剑传说', console: '任天堂 Super', rating: 4 }, { name: '辐射 76', console: '多机', rating: 1 }, { name: '超级银河战士', console: 'Super Nintendo', rating: 6 } ] } });

跟之前一样,你可以把这些标题改成你所喜欢的游戏名称。

这次,我们将创建一个 game-card 组件,这个组件将更有意义的显示我们的数据。

准备好了就接着往下看吧。

Vue.component('game-card', { props: ['gameData'], template: ` <div style="border-radius: .25rem; border: 1px solid #ECECEC; width: 400px; margin: 1rem; padding: 1rem;"> <h2>{{ gameData.name }} - <small>{{ gameData.console }}</small></h2> <span v-for="heart in gameData.rating">❤️</span> <button @click="increaseRating">赞</button> </div> `, methods: { increaseRating() { // this.game.rating++ ? } } });

不要被这代码吓到了,我们之前已经了解过里边的绝大部分内容了?!

我们首先创建了一个新的  Vue.component 实例,并且命名为 game-card。我们先跳过 props 看下 template 属性

并没有什么新东西,不过你可能注意到了,我们访问了 gameData 属性,这个属性并不是在 data 中定义的,但是却在 props 属性中。

然后,我们申明了 method 对象,并且在里边放入了 increaseRating 方法。这里我故意将 this.game.rating++ 注释掉了,这个逻辑也许可以实现你想要的功能,但它是不会生效的!这个问题一会儿再看,现在得先聊聊 props 属性。

组件属性

自定义组件的保留属性之一即为 props。它最简单的形式是定义一个 字符串 数组。在之前的示例中,我们想让组件的蓝图或者说是模板知道有一个名为 game 的属性。

属性 允许我们从组件外向组件内 传递 信息!多说无益,放码过来,下面看下实际操作。

首先,让我们在 app 里边添加一堆 <game-card> 标签。我们将会用到之前使用过的 v-for 循环,跟之前不同的是,这次要循环自定义组件。

<div id="app"> <awesome-button></awesome-button> <hr> <game-card v-for="game in games" :game-data="game" :key="game.name"></game-card> </div>

然后就有相当多的 游戏 被渲染出来了,接下来让我们看下具体细节。

第一步,就像我们之前写的那样,调用了 <game-card> 组件。

随后跟上周讲到的那样,添加了 v-for="game in games" 循环。这个语句创建了一个循环内部的 game 变量,我们可以在循环内部直接使用它。

最后,我们给模板的 gameData 属性 一个值,在这个示例里边是循环中的 game 变量。需要注意下,由于 HTML 是不区分大小写的,故传递属性的时候不是用的驼峰命名法而是使用了连字符 game-data。如果很难理解这一点,可以理解为 Vue 帮我们把连字符转换为了驼峰命名的属性名称,它的作用相当于 game-card.props.gameData = game

千万别忘了加 :key

不知道你是否注意到了,这里还有个大大的陷阱,我们把 game 传递到 game-data 属性,但是前面需要加一个 :

当我们赋予组件实例一个属性的时候,有两种方式可供选择。在示例代码中,我们可以在属性前面加 :(这个是 v-bind: 的缩写!)。这可以明确的表示我们传递到 ="<值>" 的数据是一个 JavaScript 变量或者是一串可执行的 代码

如果你输入的是 gameData="game",那么 Vue 将会认为我们传入了一个 字符串 类型的 "game"。就像是 game-card.props.gameData = "game"

让我们从理论中出来一小会儿,在浏览器中跑一下这段代码。你将如预期所示,看到我们所有的 <game-card> 组件模板渲染出来了每一个 游戏

最秀的一点就是如果我们改了组件的 HTML,app 中使用到这个组件的地方都会被更新。

最重要的是组件 允许我们在里边包含特殊逻辑。接下来继续看下 game-card 组件的 increaseRating() 方法。

组件数据 vs. 组件属性

组件属性(props)其实是一个比较大的话题,但是有一个非常重要的规则一定要注意。重要的事情说三遍,组件属性 一定一定一定 不能被组件内部所修改。

实际上,如果你尝试在组件内部赋值组件属性的话, Vue 将会抛出各种警告并且在调试控制台跳个不停 - 因为这个行为将会产生一些不可预料的结果。如果你想了解关于这方面更多的知识,可以看下文档: v-bind - 的 - once - 和 - sync - 修饰符

那么,我们怎样在组件内部修改 rating 呢?这个问题的关键就在问题本身!我们需要在从 prop 复制一份到 data 中,这样就可以修改 data 里边的数据了。

让我们在 game-card 组件里先添加 data 属性,然后赋值给一个新的不重复的名称(组件属性和组件数据名称重复的话会有冲突),然后再使用组件属性为其填充一个默认值。

data() { return { game: {...this.gameData} } },

这里有几个点需要介绍下,在此之前如果你不知道 {...gameData} 这个扩展操作符是干啥用的,我随后将很快对针对这个语法发表一篇文章做详细的介绍,简单的说,这就是用来 拷贝 gameData 所有属性的,因为 JS 对象都是引用传递的,我们不希望在 data 里边直接改掉原始数据。

The data property’s return:
data 属性的 返回值

我们学习过了 data 属性之后,我曾讲过这个对象将会保存我们所需要的全部属性,这也是我们为主 Vue 实例 做过的事情。但是对于一个 组件 却需要通过 () 创建一个函数,然后 返回 实际对象。

但是为啥要这么麻烦呢? ?

Simply put, there can be one or many instances of your component, right?
简单的说,这是为了让每个组件拥有不同的对象实例,回想一下 JS 对象的引用赋值,是这个理儿吧?

每个实例需要有一个 独立的 数据对象!不能让所有的组件共享这一个 data 对象,否则在这个示例中他们将会共享的使用同样的 title,这样的话 app 组件复用的意义就消失了。

所以需要用 函数 创建并 返回 一个对象的根本原因是 Vue 可以在每次 创建 game-cards 时通过调用这个函数获得唯一的新对象用于组件的运行。

接下来看下组件属性:

当我们想要创建 游戏data 属性时,我们把这个赋值给了 this.gameData,这里还有一点儿东西需要掌握。props 也可以 在组件脚本通过 this 访问,跟你访问本地属性 data 一样。所以在这里我们让 gamegameData 属性相同(译者注:不然就命名冲突了)。

这意味着我们需要更新一下 HTML,在组件内部把 gameData 换为 game,如下所示:

<div style="border-radius: .25rem; border: 1px solid #ECECEC;"> <h2>{{ game.name }} - <small>{{ game.console }}</small></h2> <span v-for="heart in game.rating">❤️</span> <button @click="increaseRating"></button> </div>

在浏览器重新运行一下,你就可以获得与之前相同的效果

最后,我们可以让 increaseRating 方法起作用了!找到那个方法并且用如下的代码替换:

methods: { increaseRating() { this.game.rating++ } }

每次点击,都会增加组件 内部 datagame 对象的 rating 字段,而不是组件属性。


关于组件还有很多的理论需要学习,这里仅仅是一点皮毛,但是希望你能窥一斑而知全豹,理解为何像 Vue 这样的框架会如此流行且易用。

接下来我们将开始研究我所认为的靠中间的主题,就像 computed 计算属性,watchers 监听器,events 事件等。希望你能很快的掌握 Vue 多汁多彩的部分。

今天完整的代码如下所示以备不时之需,非常感谢阅读至此! ??

<html> <head> <title>Vue 101</title> </head> <body> <div id="app"> <awesome-button></awesome-button> <game-card v-for="game in games" :game-data="game" :key="game.name"></game-card> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('awesome-button', { template: `<button @click="clickHandler">点我获得快乐</button>`, methods: { clickHandler() { alert('奥利给 ?'); } } }); Vue.component('game-card', { props: ['gameData'], data() { return { game: {...this.gameData} } }, template: `<div style="border-radius: .25rem; border: 1px solid #ECECEC; width: 400px; margin: 1rem; padding: 1rem;"> <h2>{{ game.name }} - <small>{{ game.console }}</small></h2> <span v-for="heart in game.rating">❤️</span> <button @click="increaseRating">赞</button> </div>`, methods: { increaseRating() { this.game.rating++ } } }); const app = new Vue({ el: '#app', data: { games: [ { name: '超级马里奥 64', console: '任天堂 64', rating: 4 }, { name: '塞尔达传说', console: '任天堂 64', rating: 5 }, { name: '圣剑传说', console: '任天堂 Super', rating: 4 }, { name: '辐射 76', console: '多机', rating: 1 }, { name: '超级银河战士', console: 'Super Nintendo', rating: 6 } ] } }); </script> </body> </html>

像往常一样,我们将从最简单的示例 (一个按钮) 开始。
让我们首先为该组件命名,awesome-button 就再合适不过了!