组件名
1、使用 kebab-case
1
| Vue.component('my-component-name', { })
|
2、使用 PascalCase
1
| Vue.component('MyComponentName', { })
|
当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 和 都是可接受的。
直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的
组件注册
全局注册
1 2 3
| Vue.component('my-component-name', { })
|
注册之后可以用在任何新创建的 Vue 跟实例(new Vue)的模版中。
基础组件的自动化全局注册
我们往往有很多功能单一的基础组件,而这些组件有经常会被各个功能组件频繁的用到。这就会导致每个功能组件都会有一长串基础组件的长列表。
如果使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),则可以通过require.context
来全局注册这些常用的基础组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import Vue from 'vue' import upperFirst from 'lodash/upperFirst' import camelCase from 'lodash/camelCase'
const requireComponent = require.context( './components', false, /Base[A-Z]\w+\.(vue|js)$/ )
requireComponent.keys().forEach(fileName => { const componentConfig = requireComponent(fileName)
const componentName = upperFirst( camelCase( fileName.replace(/^\.\/(.*)\.\w+$/, '$1') ) )
Vue.component( componentName, componentConfig.default || componentConfig ) })
|
全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
局部注册
先通过一个普通的对象来定义组件,然后在需要使用的地方通过属性-值的方式来进行定义引入。
1 2 3 4 5 6 7 8 9 10 11
| var ComponentA = { } var ComponentB = { }
new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
|
由于是局部注册,所以只在引入的模块内有效,如果希望在子组件中使用,同样需要定义引入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| var ComponentA = { }
var ComponentB = { components: { 'component-a': ComponentA }, }
或者通过 Babel 和 webpack 使用 ES2015 模块
import ComponentA from './ComponentA.vue'
export default { components: { ComponentA }, }
|
在模块系统中局部注册
在上面例子中通过 ES2015 的模块方式进行了组件的局部注册。我们可以将所有的组件放到同一个目录下,每一个都在一个单独的文件中,然后在需要使用的地方通过模块引入的方式进行局部注册即可。
Prop
1、HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。所以在使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
1 2 3 4 5 6 7 8
| Vue.component('blog-post', { // 在 JavaScript 中是 camelCase 的 props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' })
<blog-post post-title="hello!"></blog-post>
|
使用字符串模版时则不存在上述限制
2、可以通过直接赋值的方式来给 prop 传递一个静态的值,也可以通过 v-bind 的方式来动态传入 prop 的值。
3、几种数据类型的传入方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| 1.数字类型
<blog-post v-bind:likes="42"></blog-post>
<blog-post v-bind:likes="post.likes"></blog-post>
2.布尔值类型
<blog-post is-published></blog-post>
<blog-post v-bind:is-published="false"></blog-post>
<blog-post v-bind:is-published="post.isPublished"></blog-post>
3.数组类型
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>
4.对象类型
<blog-post v-bind:author="{ name: 'Veronica', company: 'Veridian Dynamics' }" ></blog-post>
<blog-post v-bind:author="post.author"></blog-post>
5.对象的全部属性 post: { id: 1, title: 'My Journey with Vue' } <blog-post v-bind="post"></blog-post>
等价于
<blog-post v-bind:id="post.id" v-bind:title="post.title" ></blog-post>
|
单向数据流
通过 prop 传递数据是的父子组件之间形成了单向下行绑定,父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。
两种在子组件中试图改变一个 prop 的情况:
1、这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
1 2 3 4 5 6
| props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
|
2、这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
1 2 3 4 5 6
| props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
|
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
Prop 验证
我们可以为 Prop 提供一个验证来保证传入的数据类型的有效性。为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| Vue.component('my-component', { props: { propA: Number, propB: [String, Number], propC: { type: String, required: true }, propD: { type: Number, default: 100 }, propE: { type: Object, default: function () { return { message: 'hello' } } }, propF: { validator: function (value) { return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } })
|
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
类型检查
type
可以是下列原生构造函数中的一个:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
type
还可以是一个自定义的构造函数,并且通过 instanceof
来进行检查确认。
1 2 3 4 5 6 7 8 9 10
| function Person (firstName, lastName) { this.firstName = firstName this.lastName = lastName }
Vue.component('blog-post', { props: { author: Person } })
|
非 Prop 特性
替换/合并已有的特性
对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。class
和 style
特性会将两边的值合并起来。
禁用特性继承
如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 inheritAttrs: false
。
注意 inheritAttrs: false
选项不会影响 style
和 class
的绑定。
自定义事件
事件名
不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的)。所以,始终使用 kebab-case 的事件名
自定义组件的 v-model
可以通过 model 选项将 v-model 的 value 特性用于不同的目的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)" > ` })
<base-checkbox v-model="lovingVue"></base-checkbox>
|
这里的 lovingVue
的值将会传入这个名为 checked
的 prop。同时当 <base-checkbox>
触发一个 change
事件并附带一个新的值的时候,这个 lovingVue
的属性将会被更新。
将原生事件绑定到组件
可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符来进行绑定。但是如果是一个类似原生元素的组件时,可能组件内部已经被重构了,此时会.native 失效。
为了解决这个问题,Vue 提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。有了这个 $listeners 属性,你就可以配合 v-on=”$listeners” 将所有的事件监听器指向这个组件的某个特定的子元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], computed: { inputListeners: function () { var vm = this return Object.assign({}, this.$listeners, { input: function (event) { vm.$emit('input', event.target.value) } } ) } }, template: ` <label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on="inputListeners" > </label> ` })
|
.sync 修饰符
在前面我们知道,prop 是单行向下的数据流方式,子组件不能直接修改 prop 的值,我们推荐通过update:myPropName
的模式触发事件取而代之。
1 2 3 4 5 6 7 8
| <text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event" ></text-document>
this.$emit('update:title', newTitle)
|
我们为这种模式提供一个缩写,即 .sync
修饰符:
1
| <text-document v-bind:title.sync="doc.title"></text-document>
|
注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的属性名,类似 v-model。
当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:
1
| <text-document v-bind.sync="doc"></text-document>
|
这样会把 doc 对象中的每一个属性 (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。
将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。
小结
本次主要围绕组件注册和组件的 Prop,以及组件的自定义事件相关内容进行了梳理。我们知道,在 Vue.js 的开发过程中,我们会面临各种各样的组件相关的处理,组件也是我们开发过程中十分重要的部分,所以关于 Vue.js 组件相关的内容我们分两次来进行整理。