最近做开源的一些思考和收获

7,346 阅读4分钟

前言

最近给 varlet 组件库的 checkbox group 组件添加了一些新的feature,如optionslabel-keyvalue-key,这里记录下我的思考和收获。

支持 options

支持 options 的缘由

这个最开始考虑支持是因为这个 issue

image.png

耗子哥 商量了下,准备开始先支持 options,再去做这个组件

支持 options 的好处

简化代码结构

不用手动重复定义每个复选框的代码,只需在 options 属性中简单地传递复选框列表

动态生成

通过编程方式可以方便地动态生成或变更复选框选项,例如从后端获取数据后直接设置成 options

统一管理和配置

所有选项可以集中在一个数据结构中维护,方便在整个组件中统一对复选框选项数据的管理和使用,比如开关选项排布,值有所变更等均可写入该结构的逻辑内。

实现考量

label

这个字段在设计的时候,考虑支持三种类型,分别是 stringVNodeFunction,如下

export type CheckboxGroupOptionLabelRender = (option: CheckboxGroupOption, checked: boolean) => VNodeChild

export interface CheckboxGroupOption {
  label?: string | VNode | CheckboxGroupOptionLabelRender
  value?: any
  disabled?: boolean

  [key: PropertyKey]: any
}

最开始,我写这块内容的时候,考虑支持的是 label 插槽,类似于

image.png

后面和耗子哥交流了下,找到了另外一个思路,目前收集组件这种实现方式就很类似于插槽,开发者如果想使用 VNode 的话是行不通的,比如我想拿 h 函数来展示标签,模板是不支持的,就确定要支持VNode,考虑借助 tsx 的能力来实现。

image.png

支持 label-key 和 value-key

为什么要支持 label-key 和 value-key

为什么要支持 label-keyvalue-key 呢?这个我思考了下,感觉有以下几个方面

通用性与配置性

label-key 允许指定数据源中哪个属性应该显示给用户。数据源(通常是一个数据对象数组)上的属性名字可以不固定,通过配置 label-key,组件变得更加通用和可重用。例如,不同的应用场景可能有不同的数据接口

const options1 = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' } ]; 

const options2 = [ 
  { id: 'a', transportation: 'Car' }, 
  { id: 'b', transportation: 'Bike' } 
 ]; 
<CheckboxGroup options={options1} labelKey="name" /> 

<CheckboxGroup options={options2} labelKey="transportation" /> 

国际化

在国际化或者多语言支持的场景下,不同语言可能会有不同的展示文案。label-key 可以灵活地允许开发者动态更改显示语言

const options = [ 
  { id: 1, enName: 'Apple', esName: 'Manzana' }, 
  { id: 2, enName: 'Orange', esName: 'Naranja' } 
]; 
<CheckboxGroup options={options} labelKey={isSpanish ? 'esName' : 'enName'} />

自定义渲染

在某些复杂场景下,数据展示不一定是单一属性。例如

const options = [ 
  { id: 1, firstName: 'Alice', lastName: 'Smith' }, 
  { id: 2, firstName: 'Bob', lastName: 'Johnson' } 
];
<CheckboxGroup 
  options={options} 
  labelKey={(option) => `${option.firstName} ${option.lastName}`} 
/> 

代码

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>
      )
    }
  },
})

高内聚概念的实践

相信很多掘友都听说过 高内聚低耦合 这个软件工程的概念,我刚开始参加工作的时候,也知道这个概念,但是自己在真正的编码过程中,很少意识到自己可以主动践行这个概念。

前两天早上起床的时候,我打开微信,看到了耗子哥给我的留言,

image.png

一下子就想到了 高内聚 这个概念,对于 checkbox 组件,是否选中这个状态,是由它本身产生并控制的;而对于 checkbox group 组件,渲染出来的每一个 checkbox 组件,都能获取到组件是否被选中了。

但是,这里有两种思路去实现。

checkbox group 组件自己处理选中状态

checkbox group 组件这里加个判断,当前这个 optionvalue 是否在 modelValue 这个数组里面,对应我pr里面的code就是

image.png

image.png

checkbox 组件通过插槽暴露选中状态

checkbox 组件有几个插槽,如下图

image.png

其中,三个是 icon 相关的,肯定不合适,因为这三个插槽和选中状态没有深的关联,一般作为ui展示部分。

对于默认插槽,一般会渲染选项的标签,是比较适合来传递选中状态的,对应pr的code是

image.png

image.png

第二种选择就对应了 高内聚 概念,选中这个状态只有 checkbox 组件本身去控制,其他地方需要,可以通过插槽去接收,而第一种就显得代码会不凝练,假如我有需要去修改选中这块逻辑,那我两个地方都要改,而且要做出一样的改动,维护的成本就无形之中增加了。

结尾

以上就是我要分享的全部内容了,如有错误,欢迎指正~