Vue 2 组件封装全面指南
Vue 2 组件封装全面指南
1. 组件基础定义
1.1 全局组件注册
Vue.component('my-component', {
// 组件配置选项
template: '<div>Hello Vue 2 Component</div>',
data: function() {
return {
message: 'Hello'
}
}
})
1.2 局部组件注册
new Vue({
el: '#app',
components: {
'my-component': {
template: '<div>Hello Local Component</div>',
data: function() {
return {}
}
}
}
})
2. 组件核心选项
2.1 data(必须是函数)
data: function() {
return {
counter: 0
}
}
2.2 props(父子组件通信)
props: ['title', 'userId'],
// 带验证的props
props: {
title: {
type: String,
required: true,
default: 'Default Title'
},
userId: {
type: Number,
validator: function(value) {
return value > 0
}
}
}
2.3 methods
methods: {
handleClick: function(event) {
this.counter++
this.$emit('clicked', this.counter)
}
}
2.4 computed
computed: {
fullName: function() {
return this.firstName + ' ' + this.lastName
},
// 带getter和setter的计算属性
fullName: {
get: function() {
return this.firstName + ' ' + this.lastName
},
set: function(newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
2.5 watch
watch: {
message: function(newValue, oldValue) {
console.log('Message changed from', oldValue, 'to', newValue)
},
// 深度监听
user: {
handler: function(newUser) {
console.log('User changed', newUser)
},
deep: true,
immediate: true // 立即执行一次handler
}
}
3. 组件通信方式
3.1 父组件向子组件传递数据 (props)
// 父组件
<template>
<child-component :title="parentTitle" :user-id="userId"></child-component>
</template>
// 子组件
props: ['title', 'userId']
3.2 子组件向父组件通信 ($emit)
// 子组件
methods: {
sendData: function() {
this.$emit('data-sent', { message: 'Hello from child' })
}
}
// 父组件
<template>
<child-component @data-sent="handleDataReceived"></child-component>
</template>
methods: {
handleDataReceived: function(data) {
console.log(data.message) // 'Hello from child'
}
}
3.3 非父子组件通信
3.3.1 事件总线 (EventBus)
// 创建事件总线
const EventBus = new Vue()
// 组件A发送事件
EventBus.$emit('custom-event', data)
// 组件B接收事件
EventBus.$on('custom-event', function(data) {
console.log(data)
})
// 销毁事件监听
EventBus.$off('custom-event')
3.3.2 Vuex (状态管理)
// store.js
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount: state => state.count * 2
}
})
// 在组件中使用
this.$store.commit('increment')
this.$store.dispatch('incrementAsync')
4. 组件插槽 (Slots)
4.1 基本插槽
// 子组件
Vue.component('my-component', {
template: `
<div>
<h2>标题</h2>
<slot></slot>
</div>
`
})
// 父组件使用
<my-component>
<p>这是插槽内容</p>
</my-component>
4.2 命名插槽
// 子组件
Vue.component('my-layout', {
template: `
<div>
<header><slot name="header"></slot></header>
<main><slot></slot></main>
<footer><slot name="footer"></slot></footer>
</div>
`
})
// 父组件使用
<my-layout>
<template slot="header">
<h1>页面标题</h1>
</template>
<p>主要内容</p>
<template slot="footer">
<p>页脚信息</p>
</template>
</my-layout>
4.3 作用域插槽
// 子组件
Vue.component('my-list', {
data: function() {
return {
items: ['item1', 'item2', 'item3']
}
},
template: `
<ul>
<li v-for="item in items" :key="item">
<slot :item="item" index="index"></slot>
</li>
</ul>
`
})
// 父组件使用
<my-list>
<template slot-scope="props">
<span>{{ props.index }} - {{ props.item }}</span>
</template>
</my-list>
5. 组件生命周期钩子
Vue.component('lifecycle-component', {
beforeCreate: function() {
// 实例初始化之后,数据观测和事件配置之前
},
created: function() {
// 实例创建完成,属性已绑定,但DOM还未生成
},
beforeMount: function() {
// 模板编译/挂载之前
},
mounted: function() {
// 模板编译/挂载完成
},
beforeUpdate: function() {
// 数据更新之前
},
updated: function() {
// 数据更新完成
},
beforeDestroy: function() {
// 实例销毁之前
},
destroyed: function() {
// 实例销毁之后
},
// 激活和停用钩子(keep-alive组件专用)
activated: function() {
// keep-alive 组件激活时调用
},
deactivated: function() {
// keep-alive 组件停用时调用
}
})
6. 高级组件封装技巧
6.1 使用 mixins
// 定义一个mixin
const myMixin = {
data: function() {
return {
mixinMessage: 'This is from mixin'
}
},
methods: {
mixinMethod: function() {
console.log('Mixin method called')
}
}
}
// 在组件中使用
Vue.component('my-component', {
mixins: [myMixin],
mounted: function() {
this.mixinMethod() // 调用mixin中的方法
}
})
6.2 使用extends扩展组件
const baseComponent = {
methods: {
baseMethod: function() {
console.log('Base method')
}
}
}
Vue.component('extended-component', {
extends: baseComponent,
mounted: function() {
this.baseMethod() // 可以访问基类的方法
}
})
6.3 动态组件
Vue.component('tab-a', { template: '<div>Tab A content</div>' })
Vue.component('tab-b', { template: '<div>Tab B content</div>' })
new Vue({
el: '#dynamic-component',
data: {
currentTab: 'tab-a'
}
})
// 模板中使用
<component v-bind:is="currentTab"></component>
6.4 异步组件
Vue.component('async-component', function(resolve, reject) {
setTimeout(function() {
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
// 或使用import()
Vue.component('async-component', () => import('./AsyncComponent.vue'))
7. 组件封装最佳实践
7.1 单一职责原则
一个组件只负责一个功能,保持组件简洁。
7.2 组件状态管理
- 本地状态(仅组件内部使用):使用data
- 外部状态:使用props接收,$emit发送事件
- 全局状态:使用Vuex
7.3 组件复用性
- 定义清晰的props和事件接口
- 使用插槽增加灵活性
- 避免硬编码
7.4 性能优化
// 使用v-once缓存静态内容
<span v-once>{{ staticContent }}</span>
// 使用v-memo缓存计算结果
<div v-memo="[expensiveValue]">{{ expensiveComputation() }}</div>
// 避免不必要的watch深度监听
// 使用keep-alive缓存组件实例
<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
8. 组件高级API
8.1 provide/inject (2.2.0+) - 依赖注入
// 父组件提供数据
provide: function() {
return {
theme: this.theme
}
}
// 子组件注入数据
inject: ['theme']
8.2 程序化事件监听器
// 在组件中
this.$on('test', function(msg) {
console.log(msg)
})
this.$once('test', function(msg) {
// 只触发一次
console.log(msg)
})
this.$emit('test', 'hello')
// 销毁所有事件监听
this.$off()
// 销毁特定事件
this.$off('test')
9. 组件样式隔离
9.1 Scoped CSS
<style scoped>
.my-class {
color: red;
}
</style>
9.2 CSS Modules
<style module>
.myClass {
color: blue;
}
</style>
<template>
<div :class="$style.myClass">Styled with CSS Modules</div>
</template>
Vue 2的组件封装是构建大型应用的基础,通过组合这些技术,可以创建可复用、易维护的组件库,提高开发效率和代码质量。
10.Vue2 组件生命周期补充说明
Vue2 组件的生命周期钩子函数分为创建、挂载、更新、销毁四个阶段。
以下是常见的钩子函数及其补充说明:
beforeCreate
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。此时组件的 data 和 methods 还未初始化。
created
实例已经创建完成,数据观测 (data observer) 和 event/watcher 事件配置已完成。可以访问 data、computed、methods 等,但 DOM 还未生成,$el 不可用。
beforeMount
在挂载开始之前被调用,相关的 render 函数首次被调用。此时模板已编译完成,但尚未将 DOM 插入到页面中。
mounted
实例被挂载后调用,此时 DOM 已插入到页面中,可以访问到 $el。常用于执行需要 DOM 的操作,如初始化第三方库。
beforeUpdate
数据更新时调用,发生在虚拟 DOM 打补丁之前。可以在更新前访问现有的 DOM,如手动移除事件监听器。
updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁后调用。此时组件的 DOM 已经更新,可以执行依赖于新 DOM 的操作。
beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用,适合进行清理工作,如清除定时器、取消事件监听等。
destroyed
实例销毁后调用。所有的事件监听器和子实例已被移除,所有指令已解绑。
生命周期钩子的使用场景
beforeCreate:适合插件开发时进行一些全局设置。
created:常用于发起异步请求获取数据。
mounted:适合需要操作 DOM 的场景,如图表初始化。
beforeUpdate:适合在更新前保存当前状态。
updated:适合在更新后执行依赖于新 DOM 的操作。
beforeDestroy:适合清理工作,避免内存泄漏。
父子组件生命周期执行顺序
父组件 beforeCreate 父组件 created 父组件 beforeMount 子组件 beforeCreate 子组件 created 子组件 beforeMount 子组件 mounted 父组件 mounted
注意事项
不要在 updated 钩子中修改状态,可能导致无限更新循环。
避免在 beforeCreate 中访问 data 和 methods,因为它们还未初始化。
异步请求建议放在 created 而非 mounted 中,以便更早获取数据。
更多推荐
所有评论(0)