逼我去挖掘 Component 知识
起因是这样的
备受微信小程序原生
Picker组件
摧残的运营同学终于提出了优化的需求:添加 NG 表单太痛苦了,有好多下拉选项要填写,但是这个picker组件
展开后展示项太少,每次翻滚半天,还经常出现没选中的问题,能不能换种形式改善下体验呀?包括一些多项筛选项,能不能也一并优化一下?
其实于我一个开发角色而言,小程序原生 Picker 大问题没有,就是有一点不太符合我的开发习惯,甚至有点影响开发效率了。具体而言就是:选中某一项后返回的是索引,而不是选中项的值,所以它的value
绑定的也自然是索引值
了。
那么在提交表单的时候,就带来了 2 个影响效率的问题:
- 提交的时候,需要通过
index
找到value
,这样表单才能提交正确的值; - 回填的时候,需要通过
value
找到index
,这样 UI 上才能显示正确的值。
于是,自定义一个类似的组件就成了众望所归的需求了。
预告
这里会实现select
和 dropdown
两个组件,实现过程用到了几个知识点,先预告一下。
select 组件涉及点,参考
- 组件的
selectOwnerComponent
方法; - 组件的
selectAllComponents
方法;
dropdown 组件涉及点,参考
- 定义组件间关系的
relations
属性; - 组件的
getRelationNodes
方法;
⚠️ 时间宝贵,如果了解的它们的话就不用往下看啦 😂 [抱拳]
select
组件
期望达到:
options
每一项的形式需要是{text: 'xxx', value: 'xxx'}
的形式;- 支持直接绑定
value
; - 支持
onchange
回调函数; - 支持
slot
; - 支持常规样式覆盖;
- 要实现多个
select
组件同时使用时,最多只有一个select
组件处于选中展开的状态;
使用方式:
<select
options="{{shops}}"
value="{{formData.shop_id}}"
bind:onchange="onChange"
></select>
<select
options="{{types}}"
value="{{formData.type_id}}"
bind:onchange="onChange"
></select>
Page({
data: {
formData: {
shop_id: 0,
type_id: 0,
// ...
},
shops: [
{
text: "请选择门店",
value: 0,
},
],
types: [
{
text: "请选择类型",
value: 0,
},
],
},
});
除了最后一个实现点,其它基本上都是常规操作了,所以就来记录一下最后一点的实现过程吧。
实现过程
很明显,如果有一个父容器的话,就可以 relations
控制所有 select
组件,更新他们的状态了。但是对于 select
这个组件来说,它的使用场景是比较分散了,在同一个页面中,他可能是一个筛选项,可能是一个操作列表项,也可能是表单的某一项,假如要用父容器的方式,那父容器基本上就得是页面级别的了,感觉用起来会比较费劲,也不那么优雅。
排除了父容器的方案,那还是得从 select
组件本身来入手。如果在它的内部,可以获取到其它激活的 select
组件实例,那不就简单了吗?
通过查看文档发现了两个不错的 API (selectOwnerComponent
和 selectAllComponents
)能够解决这个问题,请看代码:
// 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
组件的展开状态,问题就解决了。
不过在使用的时候需要两个必要属性class
和 classSelector
的配合,如下:
<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
。
dropdown 组件
在实现这个组件的时候参考了一下
Vant
的使用形式,通过relations
实现组件关系的维护。
有了 relations
后,初始化的时候 dropdown
组件可以获取所有 dropdownitem
组件的选中项,把它们按顺序显示在 dropdown
的选中项列表中;
而 dropdownitem
组件可以在触发 change
选中事件后,主动通知 dropdown
去更新“选中项列表”的 UI。
使用示例:
<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>