本篇开始会涉及到 Vue (以及其他前端框架)中最为滥用和难以拿捏的内容 —— 组件(Component)。
Vue 官方相关的说明资料如下,不过我们还是会基于 “功利的实用主义” 跳着来讲。
此外,也推荐几个延伸阅读的资料,有时间的时候慢慢看。
1、组件是什么?
抛开各种枯燥的官方解释,我们先从几个例子入手来直观地理解组件是什么。
1)不使用 Vue 脚手架时的编码方式:
我们在 《没时间学 Vue (9) —— 小结(一):绑定 + 渲染 + 事件处理,你已经很厉害了》这一篇里,实现了下面这样的复杂功能。
我们当时把 HTML 元素和控制用的 JavaScript 代码都写在一个 HTML 文件中,大概是这样的。
2)使用 Vue 脚手架之后的编码方式:
而我们在 《没时间学 Vue (10) —— 实践(一):第一个 Vue 项目》中使用的 @vue/cli 脚手架创建的项目中,则是这个样子的:
简单跟不使用 Vue 脚手架的编码方式做个比较,我们很容易就会发现,App.vue 其实就是个 “页面级” 的组件,整个页面中的有效内容其实都在 App.vue 里面。
要是我们用脚手架来改造的话,大概会是下面这个样子的:
3)组件嵌套其他组件
我们再来细看组件 App.vue,你会发现它还嵌套使用了另一个组件 HelloWorld.vue。
至此,我们可以脑补出整个程序运行时,页面大概是这样的构成(把各个组件中的 template 展开):
跟 HTML 元素的树状结构是不是很像?
套用 Vue 官网的描述,就是 “通常一个应用会以一棵嵌套的组件树的形式来组织”:
4)多页面复用的组件
我们在《没时间学 Vue (5) —— 绑定(四):面向单选、复选和选择框的 v-model》等章节里,介绍了各种 HTML 原生的选择控件,这些控件可以在各个页面中重复使用。
除了这些原生的控件,还有很多各式各样、基础的、在各个页面之间复用的组件,比如说:(选自 Element 组件库)
借助统一的组件技术,我们可以很方便的在代码中使用它们。
<el-switch v-model="switchValue" active-text="按月付费" inactive-text="按年付费">
</el-switch>
<el-date-picker v-model="dateValue" type="date" placeholder="选择日期">
</el-date-picker>
5)组件是什么
Vue 官网也没有给出明确的定义,所以这个问题某种程度来说只能意会、不可言传。
简单来看可以这么理解,组件就是为了实现特定功能的一组 HTML 元素和其他组件的封装。
既可能是可以被广泛复用的 “简单而基础的” 组件(比如:日期选择组件),也可能是为了实现特定功能的某个页面(比如:我们在之前做的模拟商城页面)。
跨度如此之大,导致组件的切分变成了一个非常“艺术”的事情 —— 就像微服务的切分粒度一样,没有最好、只有合适。
但是合适是什么?没有显而易见的标准,制约条件倒是多如牛毛,比如说:当前的产品定位和业务内容、公司 and/or 项目方针、项目人员能力、架构师个人偏好等等。
还是以 1)中的模拟购物车功能为例,你觉得要切分成几个组件呢?需不需要再细分呢、或者合并呢?
对于新手来说,最简单的做法是:
- 如果项目中有指导原则的话,遵循项目的指导原则
- 如果项目中没有指导原则的话,尽量按照高内聚、低耦合的原则来切分组件,保证每个组件功能和责任明确,而且组件尽量少、组件之间的嵌套关系简单易懂
《深入理解Vue.js实战》4.4 组件的自我修养 等资料中也有各种经验介绍,可以参考。
2、最基本的组件怎么写
接下来,我们以偏重单文件组件(而跳过部分基础的组件内容)的方式,来看看如何写一些最基本的组件。
以 App.vue 为例 —— 这个组件就是一个 “单文件组件”,一个 vue 文件代表了一个完整的组件 —— 很容易发现单文件组件的 Vue 文件是由这么三大部分组成的:
由于 Vue 脚手架生成的两个组件都不是最简单的形式,所以我们还得换另一个例子。
我们在 《没时间学 Vue (2) —— 绑定(一):简介》中,做了一个非常简单的画面,就是显示这么一行字:
<div id="app">
<div>{{message}}</div>
</div>
<script>
var app = new Vue({
el: "#app",
data: {
message: "没时间学 Vue",
},
});
</script>
如果要应用到 Vue 脚手架生成的项目中的话,我们会把组件 App.vue 改成大概这个样子的。
<template>
<div id="app">
<div>{{ message }}</div>
</div>
</template>
<script>
export default {
name: "App",
data: function () {
return {
message: "没时间学 Vue"
}
}
};
</script>
<!-- 没有特殊的样式 -->
<style>
</style>
映射关系是这样的:(再具体一点儿的可以参照 https://vue-loader.vuejs.org/zh/spec.html)
注意,data 属性在组件中必须写成函数,否则运行的时候会报以下错误、强制你把 data 改成函数。
(准确说是运行前使用 ESLint 进行静态检查的时候,报出了这个错误)
至于为什么要把 data 属性做成函数呢?主要是为了复用组件时,各个组件的数据能安全地相互隔离。
比如说,画面上有 2 个日期选择组件分别用来指定开始日期和结束日期。如果不把 data 做成函数的话,那么这 2 个日期选择组件的 data 就是完全一样的,任何一个修改了会影响到另一个。
Vue 官网的解释《data 必须是一个函数》读起来有些绕口,不过里面的例子却是非常生动。
<script> 中除了 data 属性之外,我们之前用过的 methods 属性也可以照搬过来。
(其他的更多属性可以参照 https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E6%95%B0%E6%8D%AE,暂时先跳过)
另外,我们有意绕开了 <style> 样式定义这部分,但是相关的基础知识还是要学习学习的,各个项目中都会涉及到画面布局和显示效果的问题。
有时间的时候,推荐至少翻一翻 CSS 速查手册、 MDN CSS 教程 中关于选择器和布局的部分,最好还能在 freeCodeCamp 中动手试一试。
对了,非常推荐为每一个组件的根元素(也就是 <template> 中的第一个元素)设置一个全局唯一的 id 或者 class 。
这样比较容易实现 “有作用域的 CSS”,避免多个组件之间的样式冲突。
3、思考题
本篇的思考题,就把之前做的这个画面,改成单文件组件吧 —— 先热热身,为后面更深入的组件运用方式做准备。
有时间有余力的话,也可以考虑和尝试以下拆分成多个组件。