前言
最近给 varlet 组件库的 checkbox group
组件添加了一些新的feature,如options
、label-key
和 value-key
,这里记录下我的思考和收获。
支持 options
支持 options 的缘由
这个最开始考虑支持是因为这个 issue
和 耗子哥 商量了下,准备开始先支持 options
,再去做这个组件
支持 options 的好处
简化代码结构
不用手动重复定义每个复选框的代码,只需在 options
属性中简单地传递复选框列表
动态生成
通过编程方式可以方便地动态生成或变更复选框选项,例如从后端获取数据后直接设置成 options
统一管理和配置
所有选项可以集中在一个数据结构中维护,方便在整个组件中统一对复选框选项数据的管理和使用,比如开关选项排布,值有所变更等均可写入该结构的逻辑内。
实现考量
label
这个字段在设计的时候,考虑支持三种类型,分别是 string
、VNode
和 Function
,如下
ts
export type CheckboxGroupOptionLabelRender = (option: CheckboxGroupOption, checked: boolean) => VNodeChild
export interface CheckboxGroupOption {
label?: string | VNode | CheckboxGroupOptionLabelRender
value?: any
disabled?: boolean
[key: PropertyKey]: any
}
最开始,我写这块内容的时候,考虑支持的是 label 插槽
,类似于
后面和耗子哥交流了下,找到了另外一个思路,目前收集组件这种实现方式就很类似于插槽,开发者如果想使用 VNode
的话是行不通的,比如我想拿 h
函数来展示标签,模板是不支持的,就确定要支持VNode
,考虑借助 tsx
的能力来实现。
支持 label-key 和 value-key
为什么要支持 label-key 和 value-key
为什么要支持 label-key
和 value-key
呢?这个我思考了下,感觉有以下几个方面
通用性与配置性
label-key
允许指定数据源中哪个属性应该显示给用户。数据源(通常是一个数据对象数组)上的属性名字可以不固定,通过配置 label-key
,组件变得更加通用和可重用。例如,不同的应用场景可能有不同的数据接口
js
const options1 = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ];
const options2 = [
{ id: 'a', transportation: 'Car' },
{ id: 'b', transportation: 'Bike' }
];
html
<CheckboxGroup options={options1} labelKey="name" />
<CheckboxGroup options={options2} labelKey="transportation" />
国际化
在国际化或者多语言支持的场景下,不同语言可能会有不同的展示文案。label-key
可以灵活地允许开发者动态更改显示语言
js
const options = [
{ id: 1, enName: 'Apple', esName: 'Manzana' },
{ id: 2, enName: 'Orange', esName: 'Naranja' }
];
html
<CheckboxGroup options={options} labelKey={isSpanish ? 'esName' : 'enName'} />
自定义渲染
在某些复杂场景下,数据展示不一定是单一属性。例如
js
const options = [
{ id: 1, firstName: 'Alice', lastName: 'Smith' },
{ id: 2, firstName: 'Bob', lastName: 'Johnson' }
];
html
<CheckboxGroup
options={options}
labelKey={(option) => `${option.firstName} ${option.lastName}`}
/>
代码
jsx
import { defineComponent, type PropType } from 'vue'
import { isFunction } from '@varlet/shared'
import { CheckboxGroupOption } from './props'
import { createNamespace } from '../utils/components'
import Checkbox from '../checkbox'
const { name } = createNamespace('checkbox-group-option')
export default defineComponent({
name,
props: {
labelKey: {
type: String,
required: true,
},
valueKey: {
type: String,
required: true,
},
option: {
type: Object as PropType<CheckboxGroupOption>,
required: true,
},
},
setup(props) {
return () => {
const { option, labelKey, valueKey } = props
return (
<Checkbox checkedValue={option[valueKey]} disabled={option.disabled}>
{{
default: ({ checked }: { checked: boolean }) =>
isFunction(option[labelKey]) ? option[labelKey](option, checked) : option[labelKey],
}}
</Checkbox>
)
}
},
})
高内聚概念的实践
相信很多掘友都听说过 高内聚低耦合
这个软件工程的概念,我刚开始参加工作的时候,也知道这个概念,但是自己在真正的编码过程中,很少意识到自己可以主动践行这个概念。
前两天早上起床的时候,我打开微信,看到了耗子哥给我的留言,
一下子就想到了 高内聚
这个概念,对于 checkbox
组件,是否选中这个状态,是由它本身产生并控制的;而对于 checkbox group
组件,渲染出来的每一个 checkbox
组件,都能获取到组件是否被选中了。
但是,这里有两种思路去实现。
checkbox group 组件自己处理选中状态
checkbox group
组件这里加个判断,当前这个 option
的 value
是否在 modelValue
这个数组里面,对应我pr里面的code就是
checkbox 组件通过插槽暴露选中状态
checkbox 组件有几个插槽,如下图
其中,三个是 icon
相关的,肯定不合适,因为这三个插槽和选中状态没有深的关联,一般作为ui展示部分。
对于默认插槽,一般会渲染选项的标签,是比较适合来传递选中状态的,对应pr的code是
第二种选择就对应了 高内聚
概念,选中这个状态只有 checkbox
组件本身去控制,其他地方需要,可以通过插槽去接收,而第一种就显得代码会不凝练,假如我有需要去修改选中这块逻辑,那我两个地方都要改,而且要做出一样的改动,维护的成本就无形之中增加了。
结尾
以上就是我要分享的全部内容了,如有错误,欢迎指正~