阿里云主机折上折
  • 微信号
Current Site:Index > Development of custom components

Development of custom components

Author:Chuan Chen 阅读数:41189人阅读 分类: uni-app

The Necessity of Custom Components

In uni-app development, when built-in components cannot meet all business scenarios, custom components become a key means of extending functionality. Encapsulating reusable UI modules can significantly improve development efficiency, such as product cards in e-commerce projects or comment components in social platforms. Through component-based development, a high-cohesion and low-coupling code structure can be achieved.

Basic Steps for Creating Components

Creating custom components in uni-app requires following a specific directory structure. It is generally recommended to create a components folder in the project root directory to store all custom components. Each component should have its own folder, for example, creating a my-button component:

├── components  
│   └── my-button  
│       ├── my-button.vue  
│       └── my-button.scss  

Basic structure example of a component file:

<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>  

Component Communication Mechanism

Parent-child component communication is primarily achieved through props and events. props are used for passing data from parent to child, while events are used for sending messages from child to parent. Below is an example of an enhanced input component:

<!-- Parent Component -->  
<template>  
  <enhanced-input   
    v-model="inputValue"  
    @search="handleSearch"  
  />  
</template>  

<script>  
export default {  
  data() {  
    return {  
      inputValue: ''  
    }  
  },  
  methods: {  
    handleSearch(value) {  
      console.log('Search content:', value)  
    }  
  }  
}  
</script>  

<!-- Child Component 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)">Search</button>  
  </view>  
</template>  

<script>  
export default {  
  name: 'enhanced-input',  
  props: {  
    value: {  
      type: String,  
      default: ''  
    }  
  }  
}  
</script>  

Advanced Usage of Slots

Named slots and scoped slots enable more flexible component content distribution. Below is an example of a card component with a title and action area:

<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>  

Component Lifecycle Management

In addition to supporting standard Vue lifecycle hooks, uni-app custom components also have unique mini-program lifecycle hooks. Example of the execution order of important lifecycle hooks:

<script>  
export default {  
  beforeCreate() {  
    console.log('1. beforeCreate')  
  },  
  created() {  
    console.log('2. created')  
  },  
  beforeMount() {  
    console.log('3. beforeMount')  
  },  
  mounted() {  
    console.log('4. mounted')  
  },  
  // Mini-program-specific lifecycle hooks  
  onLoad() {  
    console.log('5. onLoad - Mini-program page load')  
  },  
  onShow() {  
    console.log('6. onShow - Mini-program page display')  
  }  
}  
</script>  

Global Component Registration

Frequently used components can be globally registered to avoid repeated imports. Register globally in main.js:

import Vue from 'vue'  
import MyButton from '@/components/my-button/my-button.vue'  

Vue.component('my-button', MyButton)  

// Or batch registration  
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)  
})  

Component Performance Optimization Strategies

For large component libraries, rendering performance optimization is essential. Below are several practical techniques:

  1. Conditional Rendering Optimization:
<template>  
  <view>  
    <block v-if="heavyCondition">  
      <!-- Complex content -->  
    </block>  
    <block v-else>  
      <!-- Lightweight content -->  
    </block>  
  </view>  
</template>  
  1. Event Debouncing:
<script>  
import { debounce } from 'lodash-es'  

export default {  
  methods: {  
    handleInput: debounce(function(value) {  
      this.$emit('input', value)  
    }, 300)  
  }  
}  
</script>  
  1. Lazy-Loading Image Component:
<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>  

Component Theme Customization

Implement a theme system using CSS variables and mixins:

<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>  

Component Unit Testing Practices

Example of configuring Jest for component testing:

// 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)  
  })  
})  

Automatic Component Documentation Generation

Use VuePress combined with jsdoc to automatically generate component documentation:

```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']  
      }  
    ]  
  ]  
}  
```  

Publishing Components to npm

Key configurations for preparing a component library for publishing:

// 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.