Skip to content

逼我去挖掘 Component 知识

起因是这样的

备受微信小程序原生Picker组件摧残的运营同学终于提出了优化的需求:添加 NG 表单太痛苦了,有好多下拉选项要填写,但是这个picker组件 展开后展示项太少,每次翻滚半天,还经常出现没选中的问题,能不能换种形式改善下体验呀?包括一些多项筛选项,能不能也一并优化一下?

其实于我一个开发角色而言,小程序原生 Picker 大问题没有,就是有一点不太符合我的开发习惯,甚至有点影响开发效率了。具体而言就是:选中某一项后返回的是索引,而不是选中项的值,所以它的value绑定的也自然是索引值了。

那么在提交表单的时候,就带来了 2 个影响效率的问题:

  • 提交的时候,需要通过 index 找到 value,这样表单才能提交正确的值;
  • 回填的时候,需要通过 value 找到 index,这样 UI 上才能显示正确的值。

于是,自定义一个类似的组件就成了众望所归的需求了。

预告

这里会实现selectdropdown 两个组件,实现过程用到了几个知识点,先预告一下。

select 组件涉及点,参考

  • 组件的 selectOwnerComponent 方法;
  • 组件的 selectAllComponents 方法;
  • 定义组件间关系的 relations 属性;
  • 组件的 getRelationNodes 方法;

⚠️ 时间宝贵,如果了解的它们的话就不用往下看啦 😂 [抱拳]

select 组件

期望达到:

  • options每一项的形式需要是{text: 'xxx', value: 'xxx'}的形式;
  • 支持直接绑定value
  • 支持onchange 回调函数;
  • 支持slot
  • 支持常规样式覆盖;
  • 要实现多个select组件同时使用时,最多只有一个select组件处于选中展开的状态;

使用方式:

html
<select
  options="{{shops}}"
  value="{{formData.shop_id}}"
  bind:onchange="onChange"
></select>

<select
  options="{{types}}"
  value="{{formData.type_id}}"
  bind:onchange="onChange"
></select>
js
Page({
  data: {
    formData: {
      shop_id: 0,
      type_id: 0,
      //   ...
    },
    shops: [
      {
        text: "请选择门店",
        value: 0,
      },
    ],
    types: [
      {
        text: "请选择类型",
        value: 0,
      },
    ],
  },
});

除了最后一个实现点,其它基本上都是常规操作了,所以就来记录一下最后一点的实现过程吧。

演示

实现过程

很明显,如果有一个父容器的话,就可以 relations 控制所有 select 组件,更新他们的状态了。但是对于 select 这个组件来说,它的使用场景是比较分散了,在同一个页面中,他可能是一个筛选项,可能是一个操作列表项,也可能是表单的某一项,假如要用父容器的方式,那父容器基本上就得是页面级别的了,感觉用起来会比较费劲,也不那么优雅。

排除了父容器的方案,那还是得从 select 组件本身来入手。如果在它的内部,可以获取到其它激活的 select 组件实例,那不就简单了吗?

通过查看文档发现了两个不错的 API (selectOwnerComponentselectAllComponents)能够解决这个问题,请看代码:

js
// Select 组件内部
function closeBrothers() {
  // 获取组件使用者,相当于拿到兄弟组件的关联容器
  let owner = this.selectOwnerComponent();
  // 获取所有兄弟组件实例
  let brothers = owner.selectAllComponents(this.properties.classSelector);
  if (brothers.length) {
    brothers.map((compItem) => {
      // 关闭当前状态是“展开”的组件
      if (compItem.data.showDropdown) {
        compItem.close();
      }
    });
  }
}

在点击任意select组件触发展开函数之前,先调用closeBrothers函数,关闭其它select组件的展开状态,问题就解决了。

不过在使用的时候需要两个必要属性classclassSelector的配合,如下:

html
<select
  class="dd_comp"
  classSelector=".dd_comp"
  custom_option_select_class="custom_option_select_class"
  options="{{shops}}"
  value="{{formData.shop_id}}"
  bind:onchange="onChange"
  data-key="shop_id"
>
  <view slot="slot_right" class="right-arrow"></view>
</select>

class 是为了让使用的页面通过selectAllComponents方法可以找到所有 select 组件,classSelector 是为了在select组件内部,传递 class 的值,比如这里传递的就是.dd_comp

完整代码

在实现这个组件的时候参考了一下Vant的使用形式,通过 relations 实现组件关系的维护。

有了 relations 后,初始化的时候 dropdown 组件可以获取所有 dropdownitem 组件的选中项,把它们按顺序显示在 dropdown 的选中项列表中;

dropdownitem 组件可以在触发 change 选中事件后,主动通知 dropdown 去更新“选中项列表”的 UI。

使用示例:

html
<dropdown>
  <dropdownitem
    value="{{filters.shop_id}}"
    options="{{shops}}"
    bind:onchange="updateFilter"
    data-key="shop_id"
  ></dropdownitem>
  <dropdownitem
    value="{{filters.to_uid}}"
    options="{{toUserList}}"
    bind:onchange="updateFilter"
    data-key="to_uid"
  ></dropdownitem>
  <dropdownitem
    value="{{filters.need_parts}}"
    options="{{needPartsList}}"
    bind:onchange="updateFilter"
    data-key="need_parts"
  ></dropdownitem>

  <view slot="menu-right" bind:tap="goHistory">
    <view class="f-r-e-c"> 历史 </view>
  </view>
</dropdown>

演示

dropdown 完整代码

dropdownitem 完整代码

Released under the MIT License.