Chapter 06

组件开发

精通内置组件用法、掌握自定义组件开发体系与 behaviors 混入机制

6.1 核心内置组件

微信小程序提供了数十个内置组件,覆盖布局、表单、媒体、地图等场景。以下是最常用的几类:

view 与 scroll-view

<!-- scroll-view:可滚动区域,比 CSS overflow 更推荐 -->

<!-- 纵向滚动列表 -->
<scroll-view
  scroll-y
  style="height: 100vh;"
  bindscrolltolower="onReachBottom"
  refresher-enabled
  refresher-triggered="{{refreshing}}"
  bindrefresherrefresh="onRefresh"
>
  <view wx:for="{{list}}" wx:key="id">{{item.name}}</view>
  <view wx:if="{{loading}}">加载中...</view>
</scroll-view>

<!-- 横向滚动(标签栏)-->
<scroll-view scroll-x class="tab-scroll">
  <view class="tab-list">
    <view
      wx:for="{{tabs}}"
      wx:key="*this"
      class="tab {{currentTab === index ? 'active' : ''}}"
      bindtap="switchTab"
      data-index="{{index}}"
    >{{item}}</view>
  </view>
</scroll-view>

swiper 轮播图

<swiper
  class="banner"
  autoplay
  interval="{{3000}}"
  duration="{{500}}"
  circular
  indicator-dots
  indicator-color="rgba(255,255,255,.4)"
  indicator-active-color="#ffffff"
  bindchange="onBannerChange"
>
  <swiper-item wx:for="{{banners}}" wx:key="id">
    <image
      src="{{item.image}}"
      mode="aspectFill"
      bindtap="onBannerTap"
      data-url="{{item.link}}"
    />
  </swiper-item>
</swiper>

image 组件的 mode 属性

aspectFill
等比缩放,短边充满,长边裁剪。适合商品封面图、头像等需要填满容器的场景(最常用)
aspectFit
等比缩放,长边充满,短边留白。适合展示完整图片内容,如商品详情图
widthFix
宽度固定,高度自动按比例。适合文章内图片,不需要设置高度
scaleToFill
拉伸填满(不保持比例),慎用,会变形

6.2 自定义组件开发

当内置组件无法满足需求,或多个页面复用同一 UI 逻辑时,需要创建自定义组件。

组件文件结构

components/product-card/
├── product-card.js    # 组件逻辑
├── product-card.json  # 声明这是组件
├── product-card.wxml  # 组件模板
└── product-card.wxss  # 组件样式(隔离)
// product-card.json — 声明为组件
{
  "component": true,
  "usingComponents": {}
}

完整自定义组件示例

// product-card.js
Component({
  // 对外接口:接收父组件传入的数据
  properties: {
    product: {
      type: Object,
      value: {}
    },
    showBadge: {
      type: Boolean,
      value: false
    },
    size: {
      type: String,
      value: 'medium'   // 'small' | 'medium' | 'large'
    }
  },

  // 组件内部数据
  data: {
    isLiked: false
  },

  // 计算属性(Component 2.x 新增)
  computed: {
    discountText() {
      const p = this.data.product
      if (!p.originalPrice) return ''
      return Math.round(p.price / p.originalPrice * 10) + '折'
    }
  },

  // 生命周期
  lifetimes: {
    attached() {
      // 组件被添加到页面节点树时
      this.checkLikeStatus()
    },
    detached() {
      // 组件从页面节点树移除时
    }
  },

  // 所在页面的生命周期
  pageLifetimes: {
    show() {
      // 页面显示时
    },
    hide() {
      // 页面隐藏时
    }
  },

  methods: {
    onTap() {
      // 触发自定义事件,通知父组件
      this.triggerEvent('select', {
        product: this.data.product
      })
    },

    toggleLike(e) {
      e.stopPropagation()   // 阻止事件冒泡(组件方法中使用)
      this.setData({ isLiked: !this.data.isLiked })
      this.triggerEvent('like', {
        productId: this.data.product.id,
        liked: this.data.isLiked
      })
    },

    async checkLikeStatus() {
      // 检查是否已点赞
    }
  }
})
<!-- product-card.wxml -->
<view class="card card--{{size}}" bindtap="onTap">
  <view class="cover-wrap">
    <image class="cover" src="{{product.cover}}" mode="aspectFill"/>
    <view wx:if="{{showBadge && product.isNew}}" class="badge">新品</view>
  </view>
  <view class="info">
    <text class="name">{{product.name}}</text>
    <view class="price-row">
      <text class="price">¥{{product.price}}</text>
      <text wx:if="{{discountText}}" class="discount">{{discountText}}</text>
    </view>
  </view>
  <view class="like-btn {{isLiked ? 'liked' : ''}}" catchtap="toggleLike">
    ♥
  </view>
</view>

父组件使用

<!-- 在页面或父组件 json 中注册 -->
// page.json
{
  "usingComponents": {
    "product-card": "/components/product-card/product-card"
  }
}

<!-- 在 WXML 中使用 -->
<product-card
  wx:for="{{products}}"
  wx:key="id"
  product="{{item}}"
  show-badge
  bindselect="onProductSelect"
  bindlike="onProductLike"
/>

6.3 behaviors — 组件混入

behaviors 是小程序组件的混入机制,类似 Vue 的 mixins 或 React 的 HOC,用于在多个组件间共享逻辑。

// behaviors/loading.js — 定义一个通用的加载状态 behavior
module.exports = Behavior({
  data: {
    loading: false,
    error: null
  },

  methods: {
    startLoading() {
      this.setData({ loading: true, error: null })
    },
    stopLoading() {
      this.setData({ loading: false })
    },
    setError(msg) {
      this.setData({ loading: false, error: msg })
    },
    async withLoading(fn) {
      this.startLoading()
      try {
        return await fn()
      } catch (e) {
        this.setError(e.message)
        throw e
      } finally {
        this.stopLoading()
      }
    }
  }
})

// 在组件中使用 behavior
const loadingBehavior = require('../../behaviors/loading')

Component({
  behaviors: [loadingBehavior],

  methods: {
    async fetchData() {
      await this.withLoading(async () => {
        const data = await api.getList()
        this.setData({ list: data })
      })
    }
  }
})
💡
behaviors vs 自定义组件

behaviors 适合共享逻辑(数据+方法),不包含 UI 模板,类似工具函数的组合。自定义组件则包含完整的 WXML + WXSS + JS,共享 UI 结构。实际项目中二者结合使用效果最好。

6.4 Component 2.x(glass-easel)

Component 2.x 是 2024 年起推荐的新组件框架(通过 componentFramework: "glass-easel" 启用),提供更完整的响应式能力:

computed
计算属性,自动追踪依赖,依赖变化时重新计算,无需手动调用 setData
watch
侦听属性变化,支持深层监听(deep: true),替代 Component 1.x 的 observers
ref
在 WXML 中用 id 标记子组件,在父组件中通过 this.selectComponent 获取子组件实例并调用方法