一、创建和使用组件
组件名的命名规范有两种:
1.1、使用 kebab-case:(短横线分隔命名的形式)
Vue.component('my-component-name', { /* ... */ })
此时引用该组件,必须使用kebab-case:
<my-component-name></my-component-name>
1.2、使用PascalCase:(首字母大写、或驼峰命名的形式)
Vue.component('MyComponentName', { /* ... */ })
注意,此时引用该组件,可以使用 <my-component-name>,但是使用 <MyComponentName>会报错。
基本实例如下:
<body>
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script>
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({ el: '#app' })
</script>
</body>
注意:data必须是一个函数!template模板只能包含一个根元素!
1、全局注册:
注册一个全局组件,基本语法格式如下:
Vue.component('my-component-name', { /* ... */ })// 第一个参数‘my-component-name’为组件名
方式1:使用 Vue.extend 配合 Vue.component 方法:
var login = Vue.extend({
template: '<h1>登录</h1>'
});
Vue.component('login', login);
方式2:直接使用 Vue.component 方法:
Vue.component('register', {
template: '<h1>注册</h1>'
});
方法3:将模板字符串,定义到template标签中:(放在被控制的#app外面)
<template id="tmpl">
<div><a href="#">登录</a> | <a href="#">注册</a></div>
</template>
同时,需要使用 Vue.component 来定义组件:
Vue.component('account', {
template: '#tmpl'
});
3、局部注册
<body>
<div id="app">
<component-a></component-a>
<component-b></component-b>
</div>
<script>
var ComponentA = { template: `<h1>ComponentA</h1>` }
var ComponentB = { template: '<h1>ComponentB</h1>' }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
</script>
</body>
注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
在模块系统中局部注册
举例:在一个假设的 ComponentB.js 或 ComponentB.vue 文件中,使用ComponentA和ComponentC:
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
二、给子组件传值:
1、定义子组件时,声明props: props: ['title'] ,实际上任何类型的值都可以传给一个 prop,自定义的方法也可以传入。
2、父组件调用子组件时,定义一个属性 title=‘xxx’
<body>
<div id="app">
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
</div>
<script>
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
new Vue({ el: '#app' })
</script>
</body>
在页面显示结果如下:
如果数据是以对象数组的方式获取,那么上面代码可以修改如下:
<body>
<div id="app">
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:title="post.title"></blog-post>
</div>
<script>
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
new Vue({
el: '#app',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
})
</script>
</body>
说明:从上面我们可以看到,我们可以使用 v-bind
来动态传递 prop,例如 v-bind:title="post.title"。
三、子组件向父组件传值:监听子组件事件
1、父组件调用子组件时,添加属性 v-on:enlarge-text="..." 监听子组件实例(这种是自定义事件)。
2、定义子组件时,可以通过调用 v-on:click="$emit('enlarge-text')" 方法 并触发一个事件。
例如,子组件有一个按钮,点击后用于放大字号,即改变父组件中的postFontSize属性:
<body>
<div id="app">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post"
v-on:enlarge-text="postFontSize += 0.1">
</blog-post>
</div>
</div>
<script>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
</div>
`
})
new Vue({
el: '#app',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
],
postFontSize: 1,
}
})
</script>
</body>
说明:template模板中的元素如果想要换行显示,多行的模板更易读,需要用到模板字符串(` ... `)
上面代码中的'enlarge-text'为事件名,事件名不存在任何自动化的大小写转换。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到:
this.$emit('myEvent')
<!-- 没有效果 -->
<my-component v-on:my-event="doSomething"></my-component>
因此,我们推荐你始终使用 kebab-case 的事件名。
如果一个事件需要抛出一个特定的值,例如要上面文本要放大多少,这时使用 $emit
的第二个参数来提供这个值,在父组件通过 $event
访问到这个值。
<body>
<div id="app">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post ... v-on:enlarge-text="postFontSize += $event">
</blog-post>
</div>
</div>
<script>
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<button v-on:click="$emit('enlarge-text',0.1)">
Enlarge text
</button>
</div>
`
})
new Vue({
el: '#app',
data: {
...
}
})
</script>
</body>
如果处理函数是一个方法,那么上面抛出的值将会作为第一个参数传入这个方法,可以这么写:
<body>
<div id="app">
<div :style="{ fontSize: postFontSize + 'em' }">
<blog-post ... v-on:enlarge-text="onEnlargeText">
</blog-post>
</div>
</div>
<script>
Vue.component('blog-post', {
...
})
new Vue({
el: '#app',
data: {
...
},
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
})
</script>
</body>
还有剩下两种子传父的方法,可查看文章:https://www.csdn.net/tags/OtDaggxsNTEzNjctYmxvZwO0O0OO0O0O.html
四、在组件上使用 v-model:自定义输入框
<input v-model="searchText">
//等价于:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
当用在组件上时,v-model 则会这样:
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
为了让它正常工作,这个组件内的 <input> 必须:
A、将其 value 特性绑定到一个名叫 value 的 prop 上;
B、在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出。
下面是自定义一个输入框组件:
<body>
<div id="app">
<custom-input v-model="searchText"></custom-input>
</div>
<script>
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
var vm = new Vue({
el: '#app',
data: {
searchText: 'hello'
}
})
</script>
</body>
五、prop详解和验证
1、prop的大小写:
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
注:如果你使用字符串模板,那么这个限制就不存在了。
2、prop类型:
目前我们只看到了字符串数组形式的prop,例如:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
但是,如果你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop:
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
3、单向数据流
父级 prop 的更新会向下流动到子组件中,但是反过来则不行。但是如果子组件想要修改prop数据,下面有两种情形:
A、情形一:prop 传递过来一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值。
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}
B、情形二:prop 传递过来一个初始值且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
4、prop验证
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
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
}
}
}
})
type
可以是下列原生构造函数中的一个:
String
Number
Boolean
Array
Object
Date
Function
Symbol
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。