淺談 Checkbox Group 的雙向數(shù)據(jù)綁定

image

前言

Checkbox 作為表單中最常見的一類元素,使用方式分為單值和多值,其中單值的綁定很簡單,就是 truefalse,但是多值(Checkbox Group)的綁定就有一點復雜了。在實際工作中發(fā)現(xiàn)很多組件庫關(guān)于 checkbox-group 的雙向綁定一直很別扭,或者說多多少少都有一些瑕疵。

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

image

數(shù)據(jù)列表和輸出值都是對象數(shù)組。能否只用一個雙向綁定就完成數(shù)據(jù)的輸入輸出,而不是在得到綁定的數(shù)據(jù)之后再使用數(shù)組的 filter、map 這些方法去過濾和篩選。

著急的同學可以直接看最終的實現(xiàn)方案:Checkbox Group

現(xiàn)有組件庫的實現(xiàn)及缺陷

調(diào)研一下市面上的組件庫會發(fā)現(xiàn),checkbox-group 并不是一個通用組件,很多組件庫并沒有這個組件,其中 Ant Design 的 checkbox-group 的設(shè)計方案算是比較完善的。簡單看一下 Ant Design 是如何設(shè)計這個組件的。

1、Ant Design React 版的實現(xiàn):

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

optionsdefaultValue 的類型定義如下:

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

defaultValue: string[];

2、Ant Design Angular 版的實現(xiàn):

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

其中雙向綁定的數(shù)據(jù)類型如下:

options : Array<{ label: string; value: string; checked?: boolean; disabled?: boolean; }>

問題剖析

不管是 React 版還是 Angular 版,它們的 checkbox-group 都有一個共同點或者說缺陷,那就是 Option 的類型是固定的,假設(shè)需要綁定的數(shù)據(jù)如下:

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

那我們必須先將這個 cars 數(shù)組 map 成 Option 類型,然后才能綁定渲染。

另外,React 版和 Angular 版的輸出值類型也是固定的,其中 React 版輸出的是一個關(guān)于 value 的字符串數(shù)組,Angular 版是則是一個雙向綁定 checked 的原數(shù)組(個人覺得 Angular 版的綁定比 React 版的要靈活,至少從原數(shù)組取值更容易一點)。

還是以上面的 cars 數(shù)組為例,如果后端同事告訴我們想要一個完整的對象數(shù)組,比如下面這樣:

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

那我們就必須再遍歷一次 selectedCars 數(shù)組才能得到需要的數(shù)據(jù)。也就是說,對于上面展示的這種情況,我們必須要做一些額外的數(shù)據(jù)處理工作才能完成目標,但是這對于雙向綁定功能來說顯得有些繁瑣。

那到底應(yīng)該怎樣設(shè)計 checkbox-group 的雙向數(shù)據(jù)綁定才能更靈活的使用呢?

如何設(shè)計 Checkbox Group

在介紹如何設(shè)計之前,我們先嘗試能否從其它組件設(shè)計中找到靈感。

Checkbox 與 Select 的共性

image

Checkbox Group 和 Multiple Select 除了很細小的交互差異之外,幾乎看不出太大的不同。大多數(shù)情況下兩者可以相互替換,所以很多人總是困惑兩種組件到底應(yīng)該如何選擇。這里 有篇文章 專門對比了兩種組件的交互場景,甚至使用 A/B test 去分析用戶的偏好。

好像有點跑題了,言歸正傳,基于這種相似性,我們完全可以仿照 Select 的雙向綁定機制去設(shè)計 Checkbox Group。

Select 的雙向數(shù)據(jù)綁定

下面我們看一下 Material Select 和 Ng-Select 是如何設(shè)計雙向綁定的,數(shù)據(jù)就以上面的 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 在設(shè)計上稍微有一些差別。Material Select 完全基于模板渲染,Ng-Select 則是屬性配置優(yōu)先,兩者的數(shù)據(jù)回顯都是通過 compareWith。它們的雙向綁定都非常簡單,我們沒有寫任何多余的代碼就按規(guī)定的格式完成了數(shù)據(jù)的輸入輸出,這種設(shè)計思路同樣可以用在 Checkbox Group 上面。

Checkbox Group 的設(shè)計實現(xiàn)

看完上面關(guān)于 Select 的兩個例子,或許已經(jīng)不需要我再多說什么了,最終設(shè)計的 Checkbox Group 代碼如下:

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

線上 DEMO

上面的代碼沒有任何多余的過濾篩選就完成了開篇提出的需求,對數(shù)據(jù)的操作全都隱藏在雙向綁定的內(nèi)部。

如果后端同事希望 selectedCars 是一個 id 數(shù)組,比如 selectedCars=[2],那么只需要設(shè)置一下 bindValue 就可以了。

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

非對象數(shù)據(jù)的回顯就不用 compareWith 了。

總結(jié)

這篇文章拖沓了非常久,一方面是自己工作很忙,另一方面做開源項目占據(jù)了大部分時間。

從最開始考慮 Checkbox Group 的重構(gòu)方案到最終實現(xiàn)差不多用了半年多的時間,不過實際開發(fā)時間大概也就一周吧。相比之前借鑒 Ant Design 的方案來說,現(xiàn)在的方案更加靈活,有效減少了數(shù)據(jù)操作的代碼,不過仍然有很大的優(yōu)化和提升空間。

如果大家發(fā)現(xiàn)本文有不當之處,歡迎交流指正!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容