程序地带

浅谈 Checkbox Group 的双向数据绑定



前言

Checkbox 作为表单中最常见的一类元素,使用方式分为单值和多值,其中单值的绑定很简单,就是 true 和 false,但是多值(Checkbox Group)的绑定就有一点复杂了。在实际工作中发现很多组件库关于 checkbox-group 的双向绑定一直很别扭,或者说多多少少都有一些瑕疵。


开始本文之前,我们先假定有如下需求:



数据列表和输出值都是对象数组。能否只用一个双向绑定就完成数据的输入输出,而不是在得到绑定的数据之后再使用数组的 filter、map 这些方法去过滤和筛选。


着急的同学可以直接看最终的实现方案:Checkbox Group


现有组件库的实现及缺陷

调研一下市面上的组件库会发现,checkbox-group 并不是一个通用组件,很多组件库并没有这个组件,其中 Ant Design 的 checkbox-group 的设计方案算是比较完善的。简单看一下 Ant Design 是如何设计这个组件的。


1、Ant Design React 版的实现:


<Checkbox.Group options={options} defaultValue={["Pear"]} onChange={onChange} />

options 和 defaultValue 的类型定义如下:


interface Option {
label: string;
value: string;
disabled?: boolean;
}
defaultValue: string[];

2、Ant Design Angular 版的实现:


<nz-checkbox-group [(ngModel)]="options" (ngModelChange)="log(checkOptions)">
</nz-checkbox-group>

其中双向绑定的数据类型如下:


options : Array<{ label: string; value: string; checked?: boolean; disabled?: boolean; }>
问题剖析

不管是 React 版还是 Angular 版,它们的 checkbox-group 都有一个共同点或者说缺陷,那就是 Option 的类型是固定的,假设需要绑定的数据如下:


cars = [
{ id: 1, name: "Ford" },
{ id: 2, name: "Chevrolet" },
{ id: 3, name: "Dodge" },
];

那我们必须先将这个 cars 数组 map 成 Option 类型,然后才能绑定渲染。


另外,React 版和 Angular 版的输出值类型也是固定的,其中 React 版输出的是一个关于 value 的字符串数组,Angular 版是则是一个双向绑定 checked 的原数组(个人觉得 Angular 版的绑定比 React 版的要灵活,至少从原数组取值更容易一点)。


还是以上面的 cars 数组为例,如果后端同事告诉我们想要一个完整的对象数组,比如下面这样:


selectedCars = [
{ id: 2, name: "Chevrolet" }
];

那我们就必须再遍历一次 selectedCars 数组才能得到需要的数据。也就是说,对于上面展示的这种情况,我们必须要做一些额外的数据处理工作才能完成目标,但是这对于双向绑定功能来说显得有些繁琐。


那到底应该怎样设计 checkbox-group 的双向数据绑定才能更灵活的使用呢?


如何设计 Checkbox Group

在介绍如何设计之前,我们先尝试能否从其它组件设计中找到灵感。


Checkbox 与 Select 的共性


Checkbox Group 和 Multiple Select 除了很细小的交互差异之外,几乎看不出太大的不同。大多数情况下两者可以相互替换,所以很多人总是困惑两种组件到底应该如何选择。这里 有篇文章 专门对比了两种组件的交互场景,甚至使用 A/B test 去分析用户的偏好。


好像有点跑题了,言归正传,基于这种相似性,我们完全可以仿照 Select 的双向绑定机制去设计 Checkbox Group。


Select 的双向数据绑定

下面我们看一下 Material Select 和 Ng-Select 是如何设计双向绑定的,数据就以上面的 cars 为例。


cars = [
{ id: 1, name: "Ford" },
{ id: 2, name: "Chevrolet" },
{ id: 3, name: "Dodge" },
];
selectedCars = [
{ id: 2, name: "Chevrolet" }
];

1、Material Select


<mat-select multiple [(ngModel)]="selectedCars" [compareWith]="compareWith">
<mat-option *ngFor="let car of cars" [value]="car">{{car.name}}</mat-option>
</mat-select>

2、Ng-Select


<ng-select [multiple]="true" [items]="cars" bindLabel="name"
[(ngModel)]="selectedCars" [compareWith]="compareWith">
</ng-select>

Material Select 和 Ng-Select 在设计上稍微有一些差别。Material Select 完全基于模板渲染,Ng-Select 则是属性配置优先,两者的数据回显都是通过 compareWith。它们的双向绑定都非常简单,我们没有写任何多余的代码就按规定的格式完成了数据的输入输出,这种设计思路同样可以用在 Checkbox Group 上面。


Checkbox Group 的设计实现

看完上面关于 Select 的两个例子,或许已经不需要我再多说什么了,最终设计的 Checkbox Group 代码如下:


<mtx-checkbox-group [items]="cars"
bindLabel="name"
[(ngModel)]="selectedCars"
[compareWith]="compareWith">
</mtx-checkbox-group>

线上 DEMO


上面的代码没有任何多余的过滤筛选就完成了开篇提出的需求,对数据的操作全都隐藏在双向绑定的内部。


如果后端同事希望 selectedCars 是一个 id 数组,比如 selectedCars=[2],那么只需要设置一下 bindValue 就可以了。


<mtx-checkbox-group [items]="cars"
bindLabel="name"
bindValue="id"
[(ngModel)]="selectedCars">
</mtx-checkbox-group>

非对象数据的回显就不用 compareWith 了。


总结

这篇文章拖沓了非常久,一方面是自己工作很忙,另一方面做开源项目占据了大部分时间。


从最开始考虑 Checkbox Group 的重构方案到最终实现差不多用了半年多的时间,不过实际开发时间大概也就一周吧。相比之前借鉴 Ant Design 的方案来说,现在的方案更加灵活,有效减少了数据操作的代码,不过仍然有很大的优化和提升空间。


如果大家发现本文有不当之处,欢迎交流指正!



版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/nzbin/p/9038154.html

随机推荐

一起学java!java环境搭建

一起学java->语言基础篇(1)文章目录一起学java->语言基础篇(1)前言一、java概述(可略过)...

高大晟 阅读(233)

jenkins更改默认端口

jenkins更改默认端口

默认端口是8080,更改为8086: 参考链接:https://blog.csdn.net/wudinaniya/article/details/976284...

学亮编程手记 阅读(368)

奇数偶数分别累加

对100以内的奇数偶数分别累加publicclassthree1{publicstaticvoidmain(String[]args){//TODOAuto-generatedmethodstubin...

芋头~ 阅读(895)