自定义组件的开发
自定义组件的必要性
uni-app开发中,内置组件无法满足所有业务场景时,自定义组件成为扩展功能的关键手段。封装可复用的UI模块能显著提升开发效率,比如电商项目的商品卡片、社交平台的评论组件等。通过组件化开发,可以实现高内聚低耦合的代码结构。
组件创建基础步骤
在uni-app中创建自定义组件需要遵循特定目录结构。通常建议在项目根目录下新建components
文件夹存放所有自定义组件。每个组件应当单独建立文件夹,例如创建my-button
组件:
├── components
│ └── my-button
│ ├── my-button.vue
│ └── my-button.scss
组件文件基本结构示例:
<template>
<view class="my-button" @click="handleClick">
<slot></slot>
</view>
</template>
<script>
export default {
name: 'my-button',
props: {
type: {
type: String,
default: 'default'
}
},
methods: {
handleClick() {
this.$emit('click')
}
}
}
</script>
<style scoped>
.my-button {
padding: 12px 24px;
border-radius: 4px;
display: inline-block;
}
</style>
组件通信机制
父子组件通信主要通过props和events实现。props用于父向子传递数据,events用于子向父发送消息。以下是增强型输入框组件的示例:
<!-- 父组件 -->
<template>
<enhanced-input
v-model="inputValue"
@search="handleSearch"
/>
</template>
<script>
export default {
data() {
return {
inputValue: ''
}
},
methods: {
handleSearch(value) {
console.log('搜索内容:', value)
}
}
}
</script>
<!-- 子组件 enhanced-input.vue -->
<template>
<view class="input-wrapper">
<input
:value="value"
@input="$emit('input', $event.target.value)"
@keyup.enter="$emit('search', value)"
/>
<button @click="$emit('search', value)">搜索</button>
</view>
</template>
<script>
export default {
name: 'enhanced-input',
props: {
value: {
type: String,
default: ''
}
}
}
</script>
插槽的高级应用
具名插槽和作用域插槽能实现更灵活的组件内容分发。以下是带标题和操作区域的卡片组件:
<template>
<view class="card">
<view class="card-header">
<slot name="header" :title="title">
<text>{{ title }}</text>
</slot>
</view>
<view class="card-body">
<slot></slot>
</view>
<view class="card-footer">
<slot name="footer" :actions="actions">
<button
v-for="(action, index) in actions"
:key="index"
@click="action.handler"
>
{{ action.text }}
</button>
</slot>
</view>
</view>
</template>
<script>
export default {
props: {
title: String,
actions: Array
}
}
</script>
组件生命周期管理
uni-app自定义组件除了支持Vue标准生命周期外,还有特有的小程序生命周期。重要生命周期执行顺序示例:
<script>
export default {
beforeCreate() {
console.log('1. beforeCreate')
},
created() {
console.log('2. created')
},
beforeMount() {
console.log('3. beforeMount')
},
mounted() {
console.log('4. mounted')
},
// 小程序特有生命周期
onLoad() {
console.log('5. onLoad - 小程序页面加载')
},
onShow() {
console.log('6. onShow - 小程序页面显示')
}
}
</script>
全局组件注册方案
频繁使用的组件可以通过全局注册避免重复导入。在main.js
中进行全局注册:
import Vue from 'vue'
import MyButton from '@/components/my-button/my-button.vue'
Vue.component('my-button', MyButton)
// 或者批量注册
const components = require.context('@/components', true, /\.vue$/)
components.keys().forEach(fileName => {
const componentConfig = components(fileName)
const componentName = fileName.split('/').pop().replace(/\.\w+$/, '')
Vue.component(componentName, componentConfig.default || componentConfig)
})
组件性能优化策略
大型组件库需要考虑渲染性能优化。以下是几种实用技巧:
- 条件渲染优化:
<template>
<view>
<block v-if="heavyCondition">
<!-- 复杂内容 -->
</block>
<block v-else>
<!-- 轻量内容 -->
</block>
</view>
</template>
- 事件防抖处理:
<script>
import { debounce } from 'lodash-es'
export default {
methods: {
handleInput: debounce(function(value) {
this.$emit('input', value)
}, 300)
}
}
</script>
- 图片懒加载组件:
<template>
<image
:src="show ? realSrc : placeholder"
@load="handleLoad"
@error="handleError"
/>
</template>
<script>
export default {
props: {
src: String,
placeholder: {
type: String,
default: '/static/placeholder.png'
}
},
data() {
return {
show: false,
realSrc: ''
}
},
mounted() {
this.$nextTick(() => {
const observer = uni.createIntersectionObserver(this)
observer.relativeToViewport()
.observe('.lazy-img', (res) => {
if (res.intersectionRatio > 0) {
this.show = true
this.realSrc = this.src
observer.disconnect()
}
})
})
}
}
</script>
组件主题定制方案
通过CSS变量和混入实现主题系统:
<template>
<view class="theme-container" :style="themeStyle">
<slot></slot>
</view>
</template>
<script>
export default {
props: {
theme: {
type: Object,
default: () => ({
'--primary-color': '#1890ff',
'--text-color': '#333'
})
}
},
computed: {
themeStyle() {
return Object.entries(this.theme)
.map(([key, value]) => `${key}:${value}`)
.join(';')
}
}
}
</script>
<style>
.theme-container {
color: var(--text-color);
}
.theme-container button {
background: var(--primary-color);
}
</style>
组件单元测试实践
使用Jest进行组件测试的配置示例:
// jest.config.js
module.exports = {
preset: '@vue/cli-plugin-unit-jest/presets/no-babel',
moduleFileExtensions: ['js', 'json', 'vue'],
transform: {
'^.+\\.vue$': 'vue-jest'
},
testMatch: ['**/__tests__/**/*.spec.js']
}
// components/__tests__/my-button.spec.js
import { mount } from '@vue/test-utils'
import MyButton from '../my-button.vue'
describe('MyButton', () => {
it('emits click event', async () => {
const wrapper = mount(MyButton, {
slots: {
default: 'Click me'
}
})
await wrapper.trigger('click')
expect(wrapper.emitted().click).toBeTruthy()
})
it('renders default slot content', () => {
const text = 'Custom Text'
const wrapper = mount(MyButton, {
slots: {
default: text
}
})
expect(wrapper.text()).toContain(text)
})
})
组件文档自动生成
使用VuePress结合jsdoc自动生成组件文档:
```vue
<!-- docs/.vuepress/components/demo-block.vue -->
<template>
<div class="demo-block">
<slot name="demo"></slot>
<slot name="code"></slot>
</div>
</template>
```
```javascript
// docs/.vuepress/config.js
module.exports = {
plugins: [
[
'vuepress-plugin-demo-code',
{
jsLibs: ['//unpkg.com/vue/dist/vue.min.js'],
cssLibs: ['//unpkg.com/element-ui/lib/theme-chalk/index.css']
}
]
]
}
```
组件发布到npm仓库
准备组件库发布的关键配置:
// package.json
{
"name": "my-uni-components",
"version": "1.0.0",
"main": "dist/my-uni-components.umd.min.js",
"files": [
"dist",
"src"
],
"scripts": {
"build": "vue-cli-service build --target lib --name my-uni-components src/index.js"
}
}
// src/index.js
import MyButton from './components/my-button'
import EnhancedInput from './components/enhanced-input'
const components = {
MyButton,
EnhancedInput
}
const install = function(Vue) {
Object.values(components).forEach(component => {
Vue.component(component.name, component)
})
}
export default {
install,
...components
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:第三方 UI 库的适配
下一篇:主题与样式的动态切换