组件基础
1.定义
- 组件是可复用的Vue实例,与new Vue接收相同的选项,包括data、computed、watch、methods以及生命周期钩子。
- el是根实例特有的选项.
- 【注意】:data属性必须是一个函数,以让每个实例都可以独立维护一份被返回对象的独立的拷贝。否则,多次使用同一个组件,若某个实例改变了data中的属性值,会导致其余实例也被修改。
- 举例
// 定义一个名为 button-counter 的新组件
Vue.component('button-counter', {
// 1.data为一个函数,各个按钮的count是互相独立的
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
Vue.component('button-counter', {
// 2.data为一个对象,点击第一个按钮,其余按钮的count也会随之变化
data: {
count: 0
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
<!-- 3.多次使用同一个组件 -->
<div id="components-demo">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
2.组件的全局注册
- 通过Vue.component进行全局注册;
- 全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建 Vue 根实例,也包括其组件树中的所有子组件的模板中;
Vue.component('my-component-name', {
// ... options ...
})
- 注册组件的名字
(1) 使用kebab-caseVue.component('my-component-name', { /* ... */ })
<my-component-name></my-component-name>
3.模块系统中注册
(1) 场景
在开发中,通常会创建一个components目录用于专门放置组件,在该目录下,通常将每个组件放置在其各自的文件夹中。若某个组件需要使用其他组件,通常使用import / require模块系统.
(2) 使用
// 导入组件
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
// 在模块系统中局部注册
export default {
components: {
ComponentA,
ComponentC
},
// ...
}
(3) 基础组件的自动化全局注册
某些组件是相对通用的,会被作为基础组件,在各个组件中被频繁使用,会导致很多组件里都会有一个包含基础组件的长列表:
import BaseButton from './BaseButton.vue'
import BaseIcon from './BaseIcon.vue'
import BaseInput from './BaseInput.vue'
export default {
components: {
BaseButton,
BaseIcon,
BaseInput
}
}
<BaseInput
v-model="searchText"
@keydown.enter="search"
/>
<BaseButton @click="search">
<BaseIcon name="search"/>
</BaseButton>
因此,若使用了webpack(或在内部使用了webpack的Vue cli 3+),就可以考虑在应用入口文件(src/main.js)中全局导入基础组件(全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生):
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)$/)
// 遍历components目录下的文件
requireComponent.keys().forEach(fileName => {
const componentConfig = requireComponent(fileName) // 获取组件配置
// 先获取和目录深度无关的文件名,再获取组件的 PascalCase 命名
const componentName = upperFirst(camelCase(fileName.split('/').pop().replace(/\.\w+$/, '')))
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})
4.通过Prop向子组件传递数据(父->子)
- 使用场景:在当前组件中引入了某个组件,需要向该组件传递一些数据;父组件向子组件传值
- Prop 是可以在组件上注册的一些自定义 attribute,当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property;
- 在组件中使用props属性接受父组件传递的数据;
- 一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop;、
- 示例1:传递字符串等单一数值变量
// 1.注册子组件
Vue.component('blog-post', {
// 2.接收父组件参数
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
<!-- 3.在父组件中使用 -->
<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>
- 示例2:传递对象
// 1.注册子组件
Vue.component('blog-post', {
// 2.接收父组件参数
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
`
})
<!-- 3.在父组件中使用 -->
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:post="post"
></blog-post>
5.监听子组件事件(子->父)
- 使用场景:子组件向父组件传递值
- 子组件通过调用$emit方法并传入事件名称来出发事件,父组件通过监听事件从而获取子组件传递的数据;
- 举例:子组件中有一个放大字体的功能,用户点击后,希望让页面中所有元素的字体都被放大,因此需要把子组件中改变的字体大小传递回父组件;
// 1.子组件定义
Vue.component('blog-post', {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>
//通过$emit向父组件提交事件
<button v-on:click="$emit('enlarge-text',0.1)">Enlarge text</button>
<div v-html="post.content"></div>
</div>
`
})
<!-- 2.父组件 -->
<div id="blog-posts-events-demo">
<div :style="{ fontSize: postFontSize + 'em' }">
<!-- 通过v-on监听子组件提交的事件和数据 -->
<blog-post
v-for="post in posts" v-bind:key="post.id"
v-bind:post="post" v-on:enlarge-text="onEnlargeText"
v-on:enlarge-text="postFontSize += 0.1">
</blog-post>
</div>
</div>
new Vue({
el: '#blog-posts-events-demo',
data: {
posts: [/* ... */],
postFontSize: 1
},
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
})
6.在组件上使用v-model
- 自定义事件也可以用于创建支持 v-model 的自定义输入组件;
<input v-model="searchText">
<!-- 等价于 -->
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
<!-- 用在组件上 -->
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event">
</custom-input>
<!-- 等价于 -- >
<custom-input v-model="searchText"></custom-input>
- 【重要】为了自定义组件的v-model正常工作,组件内的input必须满足:
- 将其 value attribute 绑定到一个名叫 value 的 prop 上;
- 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出;
Vue.component('custom-input', {
props: ['value'],
template: `<input v-bind:value="value" v-on:input="$emit('input', $event.target.value)">`
})
7.动态组件
(1)基础知识
- 作用:用于在不同组件之间进行动态切换
- 使用:在组件上加入is属性;
- 绑定的is属性可以包括已注册组件的名字或一个组件的选项对象;
- 【注意】当is用于常规HTML元素,这些元素将被视为组件,所有的属性都会作为DOM attribute被绑定。对于像 value 这样的 property,若想让其如预期般工作,需要使用 .prop 修饰器。
<component v-bind:is="currentTabComponent"></component>
(2)用于解析DOM模板
- 场景:有些 HTML 元素,诸如<ul>、<ol>、<table> 和<select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>、<tr> 和<option>,只能出现在其它某些特定的元素内部。这会导致我们使用这些有约束条件的元素时遇到一些问题,如:
<!-- 自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,
并导致最终渲染结果出错 -->
<table>
<blog-post-row></blog-post-row>
</table>
<!-- 使用is -->
<table>
<tr is="blog-post-row"></tr>
</table>
- 使用以下方式使用模板,不存在该限制:
- 字符串 (例如:template: ‘…’)
- 单文件组件 (.vue)
- <script type=“text/x-template”>