关于单多选组件封装的实践

438 阅读2分钟

1. 背景

前段时间在做一个筛选功能的弹窗,里面有单选选项,有多选选项,其中还有可能包括自定义的选项(例如点击按钮后继续弹出日期选择时间选择),开始写代码写得很烂,导致后面不好维护,这两天才把它抽出来做成基础组件(偏我们业务)业务组件

2.基础组件(buttonList)

<template>
  <view class="button-list">
    <text
      wx:for="{{optionsList}}"
      wx:key="text"
      bindtap="onClick(item, index)"
      wx:class="{{ ['button-list_item', activedValue[item.text] ? 'button-list_active-item' : '']}}"
      >{{ item.text }}</text
    >
  </view>
</template>
 properties: {
      /** [
         {
            text: String, 展示的文字
            value: String || Number || Boolean 等等  选中的值
         }
      ] */
      // 选项列表
      optionsList: Array,
      // 默认选中值(这里为了后面有更高的扩展性,要求每一项都为item)
      defaultValue: {
        type: Array,
        value: []
      }
    },
    data() {
      return {
        activeValue: []
      }
    },
    lifetimes: {
      // 组件被激活时单独保存一份数据作为当前选中项
      attached() {
        this.activeValue = [...this.defaultValue]
      }
    },
    observers: {
      // 当选中按钮发生改变时,触发父组件的change事件并传值给父组件。
      activeValue(newVal) {
        this.triggerEvent('change', newVal)
      }
    },
    computed: {
      // 处理当前选中按钮样式
      // 因为框架不支持在template中使用数组的方法去判断某个值是否存在
      // 所以需要在这里做一次转换
      activedValue() {
        const activeObject = {}
        this.optionsList.forEach(optionsItem => {
          this.activeValue.forEach(defaultItem => {
            const defalutText = defaultItem.text
            const optionsText = optionsItem.text
            if (defalutText === optionsText) {
              activeObject[`${defalutText}`] = defalutText
            }
          })
        })
        return activeObject
      }
    },
    methods: {
      // 处理点击事件
      // 控制按钮重复点击可取消选中
      onClick(item, index) {
        const idx = this.activeValue.findIndex(activeItem => activeItem.text === item.text)
        idx === -1 ? this.activeValue.push(item) : this.activeValue.splice(idx, 1)
        this.triggerEvent('click', { item, index }) // 这里传index是为了我在使用业务组件时,可以根据索引值来判断我点击的是否需要一个自定义选项来实现我的业务需求
      }
    } 

3.业务组件(buttonGroup)

<template>
  <view class="button-group">
    <view class="button-group_title">{{ groupInfo.title }}</view>
    <button bind:click="handleClick" bind:change="handleChange" optionsList="{{groupInfo.optionsList}}" defaultValue="{{groupInfo.defaultValue}}" />
  </view>
</template>
    properties: {
      /** [
         {
            title: String 每组的title
            optionsList: 同上
            defaultValue: 同上
         }
      ] */
      groupInfo: Object
    },
    methods: {
      // 处理子组件的点击事件
      handleClick({ detail }) {
        // const { item, index } = detail // 获取点击选中的item和index
        this.triggerEvent('click', detail) // 传给外面
      },
      // 处理子组件选中的值发生变化值
      handleChange({ detail }) {
        this.triggerEvent('change', detail) // 当子组件所选中的值发生变化时 传给父组件更新当前激活的值
      }
    }

4.使用实例

<button-group groupInfo="{{groupInfo}}" bind:click="onClick" bind:change="onChange" />

    data() {
      return {
      groupInfo: {
          title: '城市',
          optionsList: [
            {
              text: '广州',
              value: 'GuangZhow'
            },
            {
              text: '深圳',
              value: 'ShenZhen'
            },
            {
              text: '上海',
              value: 'ShangHai'
            }
          ],
          defaultValue: [
            {
              text: '广州',
              value: 'GuangZhow'
            },
            {
              text: '深圳',
              value: 'ShenZhen'
            }
          ]
        },
        // 当前选中的items
        activeInfo: [],
      }
    },
    methods: {
      onclick({ detail }) {
        console.log(detail) // {item, index}
      },
      onchange({ detail }) {
        this.activeInfo = detail // 绑定当前选中的值
      }
    }

在本文中css样式不做过多展示,只是单纯分享我在开发过程中对于组件封装的思考以及实践。

5.更好的方法

在小程序官方文档中有relations适合用于阐述两个自定义组件之间的关系,也许使用这个会有更加合理的解决方案 developers.weixin.qq.com/miniprogram…

写的东西其实很简单,但对于初学者的我来说,已经是很大的进步了。 希望在未来我能够在开发前写出更好的组件,而不是在做完需求后才重新反省自己。 路还长,自己还需努力~