组件化是现代前端开发的核心思想。通过将页面拆分为一个个独立、可复用的“积木块”,我们可以大幅提升开发效率、降低维护成本。
本文将带你深入掌握 UniApp X 的组件化开发,涵盖:
- ✅ 自定义组件创建与注册
- ✅ 组件间通信方式
- ✅ 第三方组件库集成
- ✅ 组件复用与封装技巧
💡 一句话理解:
组件 = 可重复使用的页面“零件”,比如按钮、卡片、导航栏等
一、自定义组件创建与注册
1. 创建组件
在 components/
目录下创建组件文件(推荐结构):
components/
└── MyCard/
└── index.uvue # 组件主文件
✅ 示例:MyCard/index.uvue
<template>
<view class="my-card">
<image class="card-img" :src="cover" mode="aspectFill"></image>
<view class="card-content">
<text class="title">{{ title }}</text>
<text class="desc">{{ desc }}</text>
</view>
</view>
</template>
<script setup>
defineProps(['title', 'cover','desc'])
</script>
<style scoped lang="scss">
.my-card {
background: white;
border-radius: 12rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
margin-bottom: 20rpx;
}
.card-img {
width: 100%;
height: 200rpx;
}
.card-content {
padding: 20rpx;
}
.title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.desc {
font-size: 28rpx;
color: #666;
margin-top: 10rpx;
}
</style>
配置根目录@
为了让TypeScript识别@路径别名,需要创建tsconfig.json并配置路径映射。
示例:tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
}
}
创建vite.config.js文件并设置resolve.alias。
示例:vite.config.uts
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './')
}
}
});
2. 组件注册方式
✅ 方式 1:局部注册(推荐新手)
在使用组件的页面中引入:
<template>
<view>
<my-card
title="新闻标题"
desc="这是一条重要的新闻内容..."
cover="/static/news1.jpg"
/>
</view>
</template>
<script setup>
// 局部注册组件
import MyCard from '@/components/MyCard/index.uvue'
</script>
✅ 方式 2:全局注册(适合常用组件)
在 main.uts
中注册:
// main.ts
import App from './App.uvue'
import MyCard from '@/components/MyCard/index.uvue'
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.component('MyCard', MyCard) // 全局注册
return {
app
}
}
⚠️ 注意:全局组件会增加包体积,建议只注册高频组件
二、组件通信方式
组件之间需要“对话”,常见场景:
- 父组件 → 子组件:传数据
- 子组件 → 父组件:触发事件
- 兄弟组件:通过父组件中转
1. 父传子:props
<!-- 父组件 -->
<my-card :title="item.title" :cover="item.image" />
<script setup>
const item = {
title: 'Hello World',
image: '/static/cover.jpg'
}
</script>
<!-- 子组件 MyCard -->
<script setup>
// 接收 props
defineProps(['title', 'cover'])
</script>
2. 子传父:emit
<!-- 子组件 MyCard -->
<template>
<view @click="handleClick">
<!-- 内容 -->
卡片
</view>
</template>
<script setup>
// 定义可触发的事件
const emit = defineEmits(['click', 'favorite'])
function handleClick() {
emit('click', { id: 123, title: '新闻标题' })
}
</script>
<!-- 父组件 -->
<my-card @click="onCardClick" />
<script setup>
function onCardClick(data) {
console.log('点击了卡片:', data)
}
</script>
3. 任意组件通信:mitt
或 Pinia
✅ 使用 mitt
(轻量级事件总线)
npm install mitt
// utils/eventBus.ts
import mitt from 'mitt'
export default mitt()
// 组件 A:发送事件
import eventBus from '@/utils/eventBus'
eventBus.emit('news-update', { news: [...] })
// 组件 B:监听事件
import eventBus from '@/utils/eventBus'
eventBus.on('news-update', (data) => {
console.log(data)
})
三、第三方组件库集成
除了 uView,还有很多优秀组件库可以集成。
1. 使用 uni_modules
(推荐)
UniApp 官方插件市场提供标准化组件。
✅ 示例:安装 uni-icons
# 方法 1:HBuilderX 右键 -> 从插件市场下载
# 方法 2:命令行
npm install @dcloudio/uni-ui
<template>
<uni-icons type="home" size="24" color="#007AFF"></uni-icons>
</template>
<script setup>
// 自动注册(uni_modules 支持 easycom)
</script>
2. 集成 npm 组件库
✅ 示例:集成 vue3-carousel
npm install vue3-carousel
<template>
<carousel :items-to-show="1.2">
<slide v-for="i in 3" :key="i">Slide {{ i }}</slide>
<template #addons>
<navigation />
<pagination />
</template>
</carousel>
</template>
<script setup>
import { Carousel, Slide, Pagination, Navigation } from 'vue3-carousel'
import 'vue3-carousel/dist/carousel.css'
</script>
⚠️ 注意:部分 Web 组件可能不兼容小程序端
四、组件复用与封装技巧
1. 提高复用性的设计原则
原则 | 说明 |
---|---|
单一职责 | 一个组件只做一件事(如:只负责显示卡片) |
高内聚 | 相关逻辑放在一起 |
低耦合 | 减少对外部的依赖 |
可配置 | 通过 props 控制行为 |
2. 封装技巧实战
✅ 技巧 1:默认插槽 + 具名插槽
让组件更灵活:
<!-- MyCard.vue -->
<template>
<view class="my-card">
<slot name="cover">
<image :src="cover" mode="aspectFill"></image>
</slot>
<view class="content">
<slot name="title">
<text class="title">{{ title }}</text>
</slot>
<slot name="desc">
<text class="desc">{{ desc }}</text>
</slot>
<!-- 默认插槽 -->
<slot></slot>
</view>
</view>
</template>
<!-- 使用 -->
<my-card :title="item.title">
<!-- 自定义底部按钮 -->
<view slot="footer" class="footer">
<button>点赞</button>
<button>分享</button>
</view>
</my-card>
✅ 技巧 2:使用 v-model
双向绑定
<!-- MyInput.vue -->
<template>
<input :value="modelValue" @input="onInput" />
</template>
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function onInput(e) {
emit('update:modelValue', e.target.value)
}
</script>
<!-- 使用 -->
<my-input v-model="searchText" />
✅ 技巧 3:属性透传($attrs
)
<!-- MyButton.vue -->
<template>
<!-- 所有未声明的属性会自动传给 button -->
<button v-bind="$attrs" class="my-btn">
<slot></slot>
</button>
</template>
<script setup>
defineOptions({
inheritAttrs: false // 避免 class 被覆盖
})
</script>
<!-- 使用:直接传原生属性 -->
<my-button type="primary" disabled @click="doSomething">提交</my-button>
✅ 技巧 4:动态组件 + is
<template>
<component :is="currentTab.component"></component>
</template>
<script setup>
const tabs = [
{ name: '首页', component: 'HomePanel' },
{ name: '设置', component: 'SettingPanel' }
]
</script>
五、小白避坑指南
❌ 常见错误 1:组件名冲突
- 避免使用 HTML 标签名(如
button
、input
) - 推荐前缀命名:
MyCard
、UButton
、AppHeader
❌ 常见错误 2:props 修改警告
// ❌ 错误:直接修改 props
props.title = 'new title'
// ✅ 正确:使用本地变量
const localTitle = ref(props.title)
❌ 常见错误 3:事件未卸载
使用 mitt
时记得卸载:
onUnmounted(() => {
eventBus.off('news-update')
})
结语
你已经掌握了 UniApp X 组件化开发的核心能力:
✅ 创建可复用的自定义组件
✅ 掌握父子组件通信方式
✅ 集成第三方组件库
✅ 运用高级封装技巧提升灵活性
立即行动:
- 将项目中的重复代码封装成组件
- 使用
easycom
实现自动注册- 尝试用
mitt
实现跨页面通信- 为组件添加插槽支持