在鸿蒙应用的表单开发中,多选场景非常常见——兴趣爱好、筛选条件、商品标签等都会用到。ASCF组件库把多选功能拆成了checkbox和checkbox-group两个组件:checkbox负责单个选项的展示,checkbox-group负责统一收集选中值并向外抛事件。实际开发中,单独使用checkbox的情况很少,基本都是配合checkbox-group和label一起用。
单独看checkbox的属性,最值得注意的是checked的更新行为:它不会自动同步到数据层。即使用户点击了勾选框,视图上发生了变化,通过getData拿到的checked仍然可能是旧值。要获取正确的选中状态,正确做法是绑定checkbox-group的bindchange事件,从e.detail.value中读当前所有选中项。
下面是这个组合的基本写法:
- <checkbox-group bindchange="onCheckboxChange">
- <label class="checkbox-item" for="{{itemList}}">
- <checkbox value="{{$item.value}}" checked="{{$item.checked}}" disabled="{{$item.disabled}}"></checkbox>
- <text>{{$item.label}}</text>
- </label>
- </checkbox-group>
复制代码- Page({
- data: {
- itemList: [
- { value: 'vue', label: 'Vue.js', checked: true },
- { value: 'react', label: 'React' },
- { value: 'flutter', label: 'Flutter', disabled: true }
- ]
- },
- onCheckboxChange(e) {
- console.info('选中值:', e.detail.value);
- }
- });
复制代码
e.detail.value是一个数组,比如['vue', 'react'],表示所有当前选中的值。
为什么要把checkbox包在label里?因为点击文字也能触发勾选。如果不包label,用户只能精确点击那个小方块才能选中;移动端手指较粗,小方块很容易点歪。包一层label后,点击文字区域就能选中,体验提升非常明显。
除了包裹式,label也可以使用for属性关联checkbox的id:
- <checkbox id="cbAgree" value="agree"></checkbox>
- <label for="cbAgree">我已阅读并同意</label>
复制代码
效果和包裹式一样。实际开发中很多人更倾向包裹式,能少写一个id。
label不只是为checkbox服务,它同样支持button、radio、switch等组件:
- <!-- label + button -->
- <label for="btnSubmit">
- <button id="btnSubmit" size="mini">提交</button>
- </label>
- <!-- label + radio -->
- <label>
- <radio value="male" checked="{{true}}"></radio>
- <text>男</text>
- </label>
- <!-- label + switch -->
- <label>
- <switch checked="{{true}}"></switch>
- <text>开启通知</text>
- </label>
复制代码
用label包裹或关联,点击文字区域就能触发组件交互,这个模式在表单组件中是通用的。
常见场景一:综合筛选。很多页面会用标签式筛选,比如分类筛选、条件过滤,把checkbox包在带样式的label里,视觉和交互都更友好:
- <checkbox-group bindchange="onFilterChange">
- <view class="filter-tags">
- <label class="filter-tag" for="{{filterList}}">
- <checkbox value="{{$item.value}}" checked="{{$item.checked}}"></checkbox>
- <text class="tag-text">{{$item.label}}</text>
- </label>
- </view>
- </checkbox-group>
复制代码- .filter-tags {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- }
- .filter-tag {
- display: flex;
- align-items: center;
- padding: 8px 14px;
- background: #f5f5f5;
- border-radius: 20px;
- font-size: 13px;
- }
- .filter-tag:active {
- background: #e0e7ff;
- }
复制代码
常见场景二:全选/反选。比如商品列表、数据管理页面,通常需要一个全选按钮,点击后所有项全部选中,再次点击取消全选。需要维护一个allChecked状态,并在单个选项变化时重新计算:
- <label class="select-all" bindtap="toggleAll">
- <checkbox value="all" checked="{{allChecked}}"></checkbox>
- <text>{{allChecked ? '取消全选' : '全选'}}</text>
- </label>
- <checkbox-group bindchange="onItemChange">
- <label class="list-item" for="{{selectList}}">
- <checkbox value="{{$item.id}}" checked="{{$item.checked}}"></checkbox>
- <text>{{$item.name}}</text>
- </label>
- </checkbox-group>
复制代码- Page({
- data: {
- allChecked: false,
- selectList: [
- { id: '1', name: '商品 A', checked: false },
- { id: '2', name: '商品 B', checked: false },
- { id: '3', name: '商品 C', checked: false }
- ]
- },
- toggleAll() {
- const allChecked = !this.data.allChecked;
- const list = this.data.selectList.map(item => ({
- ...item, checked: allChecked
- }));
- this.setData({ allChecked, selectList: list });
- },
- onItemChange(e) {
- const selected = e.detail.value; // ['1', '3']
- const list = this.data.selectList.map(item => ({
- ...item, checked: selected.indexOf(item.id) > -1
- }));
- const allChecked = list.every(item => item.checked);
- this.setData({ selectList: list, allChecked });
- }
- });
复制代码
常见场景三:颜色定制。checkbox的color属性控制勾选后的颜色,可以结合品牌色或不同分类做视觉区分:
- <checkbox value="a" color="#ff6b35"></checkbox>
- <checkbox value="b" color="#10b981"></checkbox>
- <checkbox value="c" color="#8b5cf6"></checkbox>
复制代码
常见场景四:form表单提交。checkbox-group可以直接放进form里,通过form的bindsubmit获取选中的值,注意checkbox-group需要设置name:
- <form bindsubmit="onSubmit">
- <checkbox-group name="hobbies">
- <label>
- <checkbox value="reading"></checkbox>
- <text>阅读</text>
- </label>
- <label>
- <checkbox value="music"></checkbox>
- <text>音乐</text>
- </label>
- <label>
- <checkbox value="sports"></checkbox>
- <text>运动</text>
- </label>
- </checkbox-group>
- <button form-type="submit">提交</button>
- </form>
复制代码- onSubmit(e) {
- console.info('选中的爱好:', e.detail.value.hobbies);
- // ['reading', 'music']
- }
复制代码
实际开发中,checkbox相关的问题往往出现在细节上。这里汇总几个常踩的坑。
坑一:e.detail.value可能为undefined。当checkbox-group没有任何选中项时,e.detail.value可能是undefined而不是空数组,直接使用会报错,处理时要做兜底:
- const selected = e.detail.value || [];
复制代码
坑二:checked设了但视图不更新。checkbox的checked属性虽然可以设置默认选中,但不支持通过setData直接修改某个checkbox的checked来更新视图。正确做法是配合checkbox-group的bindchange事件,重新渲染整个列表数据。
坑三:一个label里包多个checkbox时点错。如果把多个checkbox放在同一个label里,点击文字区域会触发离点击位置最近的那个checkbox,行为跟浏览器不一样。所以建议每个checkbox单独包一个label:
- <!-- 正确:每个 checkbox 一个 label -->
- <label><checkbox value="a"></checkbox>选项 A</label>
- <label><checkbox value="b"></checkbox>选项 B</label>
- <!-- 不建议:整个列表包一个 label -->
- <label>
- <checkbox value="a"></checkbox>选项 A
- <checkbox value="b"></checkbox>选项 B
- </label>
复制代码
坑四:checkbox的checked初始值问题。在for循环里使用checkbox时,如果数据中没有checked字段,它的值是undefined。undefined是假值,表现跟false一样,但有时会触发奇怪的行为。建议每个checkbox数据都显式设置checked属性。
坑五:disabled的checkbox样式不明显。默认disabled的checkbox只是变灰,在浅色背景下和正常的区分度不高,可以额外加样式降低透明度,让用户感知到不可操作:
- checkbox[disabled] + text {
- opacity: 0.4;
- }
复制代码
总结一下:checkbox本身并不复杂,但正确用法是checkbox-group加label配合使用。全选/反选、筛选、form提交是最常用的三个场景。另外,如果你只需要单选(二选一),用switch组件更合适;checkbox适合多选场景,单选优先考虑switch或radio。 |