组件化是Vue.js开发的核心思想。本文将通过2000字长文+30个代码示例+逐行注释,手把手带你掌握组件开发的每个细节。无论是新手还是进阶开发者,都能从中获得实用知识。
<!-- ButtonCounter.vue -->
<template>
<!-- 模板部分:定义组件的HTML结构 -->
<button class="my-btn" @click="handleClick">
点击次数:{{ count }} <!-- 绑定数据 -->
</button>
</template>
<script>
// 脚本部分:定义组件逻辑
export default {
// 组件名称(推荐PascalCase)
name: 'ButtonCounter',
// 数据对象必须是函数形式(避免多个实例共享数据)
data() {
return {
count: 0 // 初始状态
}
},
// 方法定义
methods: {
handleClick() {
this.count++ // 修改数据会触发视图更新
this.$emit('count-change', this.count) // 触发自定义事件
}
},
// 生命周期钩子
mounted() {
console.log('组件已挂载到DOM')
}
}
</script>
<style scoped>
/* 样式部分:scoped表示仅作用于当前组件 */
.my-btn {
padding: 8px 16px;
background-color: #42b983;
}
</style>
// 全局注册(在main.js中)
import { createApp } from 'vue'
import App from './App.vue'
import ButtonCounter from './components/ButtonCounter.vue'
const app = createApp(App)
// 全局注册:所有组件中无需导入即可使用
app.component('ButtonCounter', ButtonCounter)
app.mount('#app')
// -----------------------------------------------------------------
// 局部注册(在父组件中)
import ButtonCounter from './ButtonCounter.vue'
export default {
components: {
// 注册为局部组件
// 使用方式:
ButtonCounter,
// 也可以自定义标签名
'CustomButton': ButtonCounter
}
}
<!-- ParentComponent.vue -->
<template>
<div>
<!-- 传递静态值 -->
<Child title="用户列表" />
<!-- 传递动态值(使用v-bind或:简写) -->
<Child :items="userList" />
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: { Child },
data() {
return {
userList: ['张三', '李四']
}
}
}
</script>
<!-- ChildComponent.vue -->
<script>
export default {
props: {
// 基础类型检查
title: {
type: String, // 类型校验
required: true, // 必填项
default: '默认标题' // 默认值(当required为false时生效)
},
// 复杂类型校验
items: {
type: Array,
// 对象/数组的默认值必须从工厂函数获取
default: () => [],
// 自定义验证函数
validator: (value) => {
return value.length <= 10 // 限制最大长度
}
}
}
}
</script>
<!-- ChildComponent.vue -->
<template>
<button @click="notifyParent">
通知父组件
</button>
</template>
<script>
export default {
methods: {
notifyParent() {
// 触发自定义事件,传递参数
this.$emit('message', {
text: '来自子组件的消息',
time: new Date()
})
}
}
}
</script>
<!-- ParentComponent.vue -->
<template>
<Child @message="handleMessage" />
</template>
<script>
export default {
methods: {
// 接收事件参数
handleMessage(payload) {
console.log('收到子组件消息:', payload)
// 典型应用:更新父组件状态
this.receivedMessage = payload
}
}
}
</script>
<!-- CustomInput.vue -->
<template>
<input
:value="modelValue" // 接收父组件值
@input="$emit('update:modelValue', $event.target.value)" // 通知更新
/>
</template>
<script>
export default {
props: ['modelValue'], // 必须用modelValue作为prop名
emits: ['update:modelValue'] // 必须声明触发事件
}
</script>
<!-- 使用方式 -->
<template>
<CustomInput v-model="username" />
<!-- 等价于 -->
<CustomInput
:modelValue="username"
@update:modelValue="newValue => username = newValue"
/>
</template>
<!-- BaseLayout.vue -->
<template>
<div class="container">
<header>
<!-- 具名插槽 -->
<slot name="header">
<!-- 后备内容:当父组件不提供时显示 -->
<h1>默认标题</h1>
</slot>
</header>
<main>
<!-- 默认插槽 -->
<slot></slot>
</main>
<footer>
<!-- 作用域插槽 -->
<slot name="footer" :year="new Date().getFullYear()" />
</footer>
</div>
</template>
<!-- 使用示例 -->
<BaseLayout>
<template #header>
<h1>自定义标题</h1>
<p>副标题</p>
</template>
<p>主要内容...</p>
<template #footer="{ year }">
<!-- 接收子组件传递的数据 -->
<p>© {{ year }} 公司名称</p>
</template>
</BaseLayout>
<template>
<!-- 动态切换组件 -->
<component :is="currentComponent" />
<!-- 保持组件状态 -->
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</template>
<script>
import CompA from './CompA.vue'
import CompB from './CompB.vue'
export default {
data() {
return {
components: ['CompA', 'CompB'],
currentComponent: 'CompA'
}
},
components: { CompA, CompB }
}
</script>
// 1. 异步组件(路由懒加载)
const UserProfile = () => import('./UserProfile.vue')
// 2. 函数式组件(无状态组件)
Vue.component('functional-button', {
functional: true,
render(h, context) {
return h('button', context.data, context.children)
}
})
// 3. 避免不必要的重新渲染
export default {
// 仅当id变化时重新渲染
props: ['id'],
computed: {
user() {
return this.$store.getters.getUser(this.id)
}
}
}
五、实战:构建可复用的表单组件
<!-- SmartForm.vue -->
<template>
<form @submit.prevent="handleSubmit">
<!-- 匿名插槽放置表单项 -->
<slot />
<!-- 具名插槽放置提交按钮 -->
<slot name="submit">
<button type="submit">提交</button>
</slot>
</form>
</template>
<script>
export default {
methods: {
handleSubmit() {
// 收集所有子组件的验证结果
const results = []
// 递归查找所有子组件
const validate = (children) => {
children.forEach(child => {
if (child.$options.name === 'FormItem') {
results.push(child.validate())
}
if (child.$children) {
validate(child.$children)
}
})
}
validate(this.$children)
// 所有验证通过才提交
Promise.all(results).then(() => {
this.$emit('submit')
}).catch(() => {
alert('表单验证失败')
})
}
}
}
</script>