Skip to content

自定义组件

为什么需要自定义组件?

复用

  • 问题:在多个页面中需要重复使用相同的 UI 结构和逻辑
  • 示例:商品卡片、用户头像、底部按钮栏、加载状态等
  • 好处:一次开发,多处使用,提高开发效率
html
<!-- 没有组件时,每个页面都要重复写 -->
<!-- page1.wxml -->
<view class="product-card">
  <image src="{{product1.image}}"></image>
  <text>{{product1.name}}</text>
  <text>¥{{product1.price}}</text>
  <button>购买</button>
</view>

<!-- page2.wxml -->
<view class="product-card">
  <image src="{{product2.image}}"></image>
  <text>{{product2.name}}</text>
  <text>¥{{product2.price}}</text>
  <button>购买</button>
</view>

<!-- 使用组件后 -->
<!-- 所有页面都可以这样使用 -->
<product-card 
  product="{{product}}" 
  bind:buy="onBuy"
/>

解耦

  • 问题:复杂页面的逻辑和模板混杂在一起,难以维护
  • 好处:将页面拆分为独立的、职责单一的组件,便于开发和测试
html
<!-- 没有组件:一个庞大的页面 -->
<view class="user-profile-page">
  <!-- 用户信息区域 -->
  <view class="user-header">...</view>
  
  <!-- 订单列表 -->
  <view class="order-list">...</view>
  
  <!-- 商品推荐 -->
  <view class="product-recommend">...</view>
  
  <!-- 底部导航 -->
  <view class="tab-bar">...</view>
</view>

<!-- 使用组件:结构清晰 -->
<view class="user-profile-page">
  <user-header user="{{userInfo}}"></user-header>
  <order-list orders="{{orders}}"></order-list>
  <product-recommend></product-recommend>
  <tab-bar current="profile"></tab-bar>
</view>

创建和使用自定义组件

创建自定义组件

步骤1:创建组件目录和文件

components/
└── product-card/
    ├── product-card.js     # 组件逻辑
    ├── product-card.wxml   # 组件模板
    ├── product-card.wxss   # 组件样式
    └── product-card.json   # 组件配置

步骤2:声明组件

json
// components/product-card/product-card.json
{
  "component": true,
  "usingComponents": {}
}

步骤3:编写组件模板和样式

html
<!-- components/product-card/product-card.wxml -->
<view class="product-card">
  <image class="product-image" src="{{image}}"></image>
  <view class="product-info">
    <text class="product-name">{{name}}</text>
    <text class="product-price">¥{{price}}</text>
  </view>
  <button class="buy-btn" bindtap="onBuyTap">购买</button>
</view>
css
/* components/product-card/product-card.wxss */
.product-card {
  border: 1rpx solid #eee;
  border-radius: 10rpx;
  padding: 20rpx;
  margin: 20rpx;
}

.product-image {
  width: 100%;
  height: 300rpx;
}

.product-name {
  font-size: 32rpx;
  color: #333;
}

.product-price {
  font-size: 36rpx;
  color: #e4393c;
}

.buy-btn {
  background-color: #07c160;
  color: white;
}

步骤4:编写组件逻辑

javascript
// components/product-card/product-card.js
Component({
  // 组件的属性列表
  properties: {
    image: {
      type: String,
      value: ''
    },
    name: {
      type: String,
      value: ''
    },
    price: {
      type: Number,
      value: 0
    },
    productId: {
      type: String,
      value: ''
    }
  },

  // 组件的初始数据
  data: {
    // 组件内部数据
    isFavorite: false
  },

  // 组件的方法列表
  methods: {
    onBuyTap: function() {
      // 触发自定义事件,向父组件传递数据
      this.triggerEvent('buy', {
        productId: this.properties.productId,
        price: this.properties.price
      })
    },
    
    onFavoriteTap: function() {
      this.setData({
        isFavorite: !this.data.isFavorite
      })
    }
  }
})

使用自定义组件

步骤1:在页面中引用组件

json
// pages/index/index.json
{
  "usingComponents": {
    "product-card": "/components/product-card/product-card"
  }
}

步骤2:在页面模板中使用组件

html
<!-- pages/index/index.wxml -->
<view>
  <text>商品列表</text>
  
  <product-card
    image="{{products[0].image}}"
    name="{{products[0].name}}"
    price="{{products[0].price}}"
    product-id="{{products[0].id}}"
    bind:buy="onProductBuy"
  />
  
  <product-card
    image="{{products[1].image}}"
    name="{{products[1].name}}"
    price="{{products[1].price}}"
    product-id="{{products[1].id}}"
    bind:buy="onProductBuy"
  />
</view>

步骤3:在页面中处理组件事件

javascript
// pages/index/index.js
Page({
  data: {
    products: [
      {
        id: '1',
        name: 'iPhone 14',
        price: 5999,
        image: '/images/iphone14.jpg'
      },
      {
        id: '2',
        name: 'MacBook Pro',
        price: 12999,
        image: '/images/macbook.jpg'
      }
    ]
  },
  
  onProductBuy: function(event) {
    const { productId, price } = event.detail;
    console.log('购买商品:', productId, '价格:', price);
    
    // 处理购买逻辑
    wx.navigateTo({
      url: `/pages/order/order?productId=${productId}`
    });
  }
})

组件间的通信

properties - 父组件向子组件传递数据

javascript
// 子组件:components/my-component/my-component.js
Component({
  properties: {
    // 简化的定义方式
    title: String,
    
    // 完整的定义方式
    count: {
      type: Number,        // 类型
      value: 0,            // 默认值
      observer: function(newVal, oldVal) {
        // 属性值变化时的监听函数
        console.log('count changed:', newVal, oldVal);
      }
    },
    
    // 复杂的对象类型
    userInfo: {
      type: Object,
      value: {}
    }
  }
})
html
<!-- 父组件使用 -->
<my-component
  title="Hello World"
  count="{{currentCount}}"
  user-info="{{userData}}"
/>

triggerEvent - 子组件向父组件传递数据

javascript
// 子组件触发事件
// components/my-button/my-button.js
Component({
  methods: {
    onTap: function() {
      const myEventDetail = { // detail 对象,提供给事件监听函数
        message: '按钮被点击了',
        timestamp: new Date().getTime()
      };
      const myEventOption = { // 触发事件的选项
        bubbles: true,      // 事件是否冒泡
        composed: true,     // 事件是否可以穿越组件边界
        capturePhase: false // 事件是否拥有捕获阶段
      };
      
      this.triggerEvent('myevent', myEventDetail, myEventOption);
    },
    
    onCustomTap: function() {
      this.triggerEvent('customevent', {
        value: '自定义数据'
      });
    }
  }
})
html
<!-- 父组件监听事件 -->
<my-button
  bind:myevent="onMyEvent"
  bind:customevent="onCustomEvent"
/>

<!-- 也可以使用 bindmyevent 的写法 -->
<my-button
  bindmyevent="onMyEvent"
  bindcustomevent="onCustomEvent"
/>
javascript
// 父组件处理事件
// pages/index/index.js
Page({
  onMyEvent: function(event) {
    console.log('收到子组件事件:', event.detail.message);
    console.log('时间戳:', event.detail.timestamp);
  },
  
  onCustomEvent: function(event) {
    console.log('自定义事件数据:', event.detail.value);
  }
})

组件生命周期

javascript
// components/my-component/my-component.js
Component({
  lifetimes: {
    // 组件实例刚刚被创建时执行
    created: function() {
      console.log('组件 created - 还不能调用 setData');
    },
    
    // 组件实例进入页面节点树时执行
    attached: function() {
      console.log('组件 attached - 可以调用 setData');
      // 适合进行初始化工作
      this.setData({ isLoading: true });
      this.fetchData();
    },
    
    // 组件在视图层布局完成后执行
    ready: function() {
      console.log('组件 ready - 视图层布局完成');
      // 适合操作节点、开始动画
    },
    
    // 组件实例被从页面节点树移除时执行
    detached: function() {
      console.log('组件 detached - 被移除');
      // 适合清理工作,清除定时器等
      if (this.timer) {
        clearInterval(this.timer);
      }
    },
    
    // 组件发生错误时执行
    error: function(err) {
      console.error('组件 error:', err);
    }
  },
  
  // 组件所在页面的生命周期
  pageLifetimes: {
    // 页面显示时触发
    show: function() {
      console.log('页面显示了 - 组件可以执行相应操作');
    },
    
    // 页面隐藏时触发
    hide: function() {
      console.log('页面隐藏了 - 组件可以暂停操作');
    },
    
    // 页面尺寸变化时触发
    resize: function(size) {
      console.log('页面尺寸变化:', size);
    }
  },
  
  methods: {
    fetchData: function() {
      // 模拟数据加载
      setTimeout(() => {
        this.setData({ 
          isLoading: false,
          data: '加载完成的数据'
        });
      }, 1000);
    }
  }
})

插槽 slot 的使用

插槽用于让组件可以接收外部传入的任意内容,增加组件的灵活性。

单个插槽

html
<!-- 组件模板:components/container/container.wxml -->
<view class="container">
  <view class="header">
    <text>{{title}}</text>
  </view>
  
  <view class="content">
    <!-- 插槽,用于放置外部传入的内容 -->
    <slot></slot>
  </view>
  
  <view class="footer">
    <slot name="footer"></slot>
  </view>
</view>
html
<!-- 使用组件 -->
<container title="我的容器">
  <!-- 默认插槽的内容 -->
  <view>这是主体内容</view>
  <text>可以是任意内容</text>
  
  <!-- 具名插槽的内容 -->
  <view slot="footer">
    <button>底部按钮</button>
  </view>
</container>

多个插槽(需要在配置中启用)

javascript
// components/container/container.js
Component({
  options: {
    multipleSlots: true // 在组件定义时的选项中启用多slot支持
  },
  
  properties: {
    title: String
  }
})
html
<!-- 组件模板支持多个具名插槽 -->
<view class="card">
  <view class="card-header">
    <slot name="header"></slot>
  </view>
  
  <view class="card-body">
    <slot name="body"></slot>
  </view>
  
  <view class="card-footer">
    <slot name="footer"></slot>
  </view>
</view>
html
<!-- 使用多插槽组件 -->
<card>
  <view slot="header">
    <text>自定义头部</text>
    <image src="/images/icon.png"></image>
  </view>
  
  <view slot="body">
    <text>自定义主体内容</text>
  </view>
  
  <view slot="footer">
    <button>自定义底部按钮</button>
  </view>
</card>

插槽作用域

html
<!-- 组件内部数据不会暴露给插槽内容 -->
<view class="component">
  <text>组件内部数据: {{internalData}}</text>
  
  <view class="slot-area">
    <!-- 插槽内容只能访问使用组件页面的数据,不能访问组件内部数据 -->
    <slot></slot>
  </view>
</view>
编程洪同学服务平台是一个广泛收集编程相关内容和资源,旨在满足编程爱好者和专业开发人员的需求的网站。无论您是初学者还是经验丰富的开发者,都可以在这里找到有用的信息和资料,我们将助您提升编程技能和知识。
专业开发
高端定制
售后无忧
站内资源均为本站制作或收集于互联网等平台,如有侵权,请第一时间联系本站,敬请谅解!本站资源仅限于学习与参考,严禁用于各种非法活动,否则后果自行负责,本站概不承担!