前一篇我们简介了组件,并且以 “页面级” 的组件为中心演示了 “单文件组件” 的写法,算是开了个头。
本篇我们继续说组件,尤其是被其他组件使用的组件要怎么创建、使用,以及组件和使用者之间怎么传递数据。
我们仍然基于项目中实际会用到的 “单文件组件” 、而不是 Vue.component() 这种方式来创建组件,虽然本质上单文件组件会被转换成 Vue.component() 。
如果有兴趣了解以下 “真正的” 组件定义方式的话,可以抽时间看一下:
- 组件基础 - 基本示例
- Vue.component() API
- Vue 组件中的 选项/数据
1、创建一个简单的组件
实际项目中,我们很少会自己造轮子,开发诸如 “日期选择组件”、“开关组件” 这些非常基础的组件。
像 Element 、Vant、Ant Design of Vue 等组件库已经够用了,而且很多人已经帮你踩过坑、代码的健壮性都比从从头自己写更好。
我们平常遇到的最多的是 “页面级的” 组件,然后就是一些项目内 “共通业务逻辑的” 组件。
比如说:顶部的导航区、右侧的菜单栏、用户展示(头像、名称)组件、单个商品展示组件、项目特有的数据字典选择组件等等。
接下来,让我们做一个简单的 “显示年龄” 的控件,并且在 App.vue 中使用它。
1)组件构成:
年龄组件 Age.vue 的功能很简单:
- 接收 “生日” 数据 (String 类型)
- 显示年龄字符串:如果大于 1 岁,显示 "XX 岁";否则显示 "不足一岁"
(计算年龄的时候,需要用到 JavaScript 原生的 Date 对象:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date)
2)年龄组件 Age.vue
我们可以在 src\components 目录中,新建一个 Age.vue 文件。
输入 < 字符后,Vetur 插件会很耐斯地提示使用何种模板。
最后,我们完成的 Age.vue 大概是这样的:
<template>
<span class="age">
{{age}}
</span>
</template>
<script>
export default {
name: 'Age',
props: {
birthday: String
},
data: function() {
return {
age: this.computeAge()
}
},
methods: {
computeAge: function() {
// 通过 this.birthday 计算年龄,省略计算逻辑 10000 字
this.age = "正在计算中";
}
}
}
</script>
<style>
</style>
拆解一下,跟之前的组件(App.vue)不太一样的地方是:
其中比较重要的是用来接收数据的 props 属性,如果不写的话、组件的使用者就没法传递数据给组件了。
此外,Vue 官网也极力推荐,prop 的定义应该尽量详细,至少需要指定其类型,还给出了带参数校验的更好的例子。
3)深入了解 props
props 属性中每个 prop 比较完整的定义规范的介绍在 https://cn.vuejs.org/v2/api/#props,简单归纳如下:
字段 | 描述 | 附加说明 |
---|---|---|
type | 属性的数据类型 | 可以是下列原生构造函数中的一种:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数、或上述内容组成的数组。 如果使用者传递过来的值的类型不符合,Vue 会抛出一个警告。 我们上面的例子中 birthday: String 这种写法,其实就是: birthday: { type: String } 的简写。 |
default | 默认值 | 可以不设置,也可以设置为任意值。 如果使用者在传递的数据中没有指定本 prop,那么使用 default 中定义的默认值。 |
required | 本 prop 是否必须指定 | |
validator | 本 prop 的有效性校验函数 |
如:
props: {
// 简写方式,只指定类型
height: Number,
// 更加复杂和丰富的定义方式
age: {
type: Number, // 类型
default: 0, // 默认值
required: true, // 是否必须
validator: function (value) { // 校验函数
return value >= 0
}
}
}
另外,props 是用来单向接收来自使用者指定的数据的(参见:单向数据流),有点儿像是 C/C++ 中的 “形参”。
因此,在组件里不要修改 props 中定义的数据,不然运行时会抛出一个警告异常。
当然,也不要在 data 属性中定义同名的数据,不然编译时就会报错。
2、使用组件
现在我们已经写好 年龄组件 Age.vue 了,那么在 App.vue 中要怎么用起来呢?
最为精简的用法大概是这样的:
<template>
<div id="app">
<div>年龄:<Age birthday="2000-01-01"></Age></div>
</div>
</template>
<script>
import Age from './components/Age';
export default {
name: "App",
components: {
Age
}
};
</script>
<!-- 没有特殊的样式 -->
<style>
</style>
主要的修改点是:(注意按照下图中的序号来看,而不是从上往下地看)
上图中的步骤 1 和 2 其实是一体的,先导入后注册,都属于组件的 “局部注册”。
与之相对的还有 “全局注册”,全局注册的组件在所有的 vue 文件中都可以直接使用,就不用再挨个重复上面的步骤 1 和 2 了。
全局注册一般在 main.js 中进行,有些还相当不直观,在 import 的时候就直接调用全局注册函数、完成注册了。
另外,步骤 1 中的 import 语句,在项目中也会常伴我们左右。
除了导入 Vue 组件,能导入 CSS 文件,还能导入第三方的 JavaScript 库,比如说:Element 基础组件库、axios 网络请求库、lodash 实用工具库等。
import 导入和 export 导出本身也是比较复杂的 “模块管理” 的一部分,先记着怎么用就行,更多细节可以参照:https://zh.javascript.info/import-export。
3、数据传递
有了上面的经验,好像组件的创建、使用和数据传递都 “尽在掌握之中” 了,一切也都非常简单。
不过,其实一切才刚刚开始,接下来才是有那么点儿绕、所以会分成多篇慢慢来说的 “数据传递”。
上面的例子中,我们在 App.vue 中直接使用了 <Age birthday="2000-01-01"></Age> 这样硬编码的方式,把数据传递给了 Age.vue —— 实际项目中基本不可能有这种使用方式的。
现在,让我们稍微修改一下 App.vue,加入一个选择生日的 <input type="date"> 控件,把控件的日期传递给 Age.vue;当生日发生变化时,年龄显示也跟着变化。
根据之前《没时间学 Vue (4) —— 绑定(三):面向各种文本框的 v-model》的知识,我们很容易做出生日选择那部分。大概是这样的:
<template>
<!-- 省略其他部分 -->
<div>生日:<input type="date" v-model="birthDate"></div>
</template>
<script>
export default {
// 也省略其他部分
data: function() {
return {
birthDate: '2000-01-01'
}
}
}
</script>
那要怎么把生日选择控件的数据传递给 Age.vue 呢?之前的代码是这样的: <Age birthday="2020-01-01"></Age>
如果你还记得《没时间学 Vue (3) —— 绑定(二):v-bind 和 v-model》这篇里提到过 “所有的 attribute 绑定,都得用 v-bind 的方式来做” 的这个原则,
你就很容易推测出来,数据得这么传递:
<div>生日:<input type="date" v-model="birthDate"></div>
<div>年龄:<Age v-bind:birthday="birthDate"></Age></div>
也可以把 v-bind:birthday 简写成 :birthday,但是你一定要能区分得出来,:birthday 和 birthday 这两种用法是完全不同的意思。
运行 npm run serve 命令,然后在浏览器中确认画面,貌似一切正常!
不过当我们试着修改生日选择组件的内容的时候 —— 比如说改成 1999 年 1 月 1 日 —— 你会发现,年龄控件的显示根本没有变化,自带冻龄效果。
为什么呢?为什么呢?等下篇我们说完 “计算属性和监听器”,你就知道了。
今天也不早了,就到此打住吧 —— 你要是实在很好奇的话,可以先看一下 Vue 的官网资料:计算属性和监听器,也可以折腾一下上面那个 “冻龄” 的问题。
4、思考题
今天的思考题,是实现下面这样的一个组件、并且在 App.vue 中使用(和测试)它。
友情提示:
- 显示结果组件中,可以根据 “显示风格” 的值,使用 v-if / v-else-if 来控制显示内容
- 显示内容可以使用函数来计算出来