给开源组件库提 PR 没想到一行代码就搞定啦

2023/03/06
4分钟阅读

在 Vant UI 组件库中使用 TreeSelect 分类选择组件时,可以使用 content 插槽自定义右侧区域内容,那么左侧是否也可以自定义呢?

原文档组件 slots

有人提出了这样一个 Issue,并且被贴上 PR welcome 标签

[Feature Request] van-tree-select 最左侧希望可以插入 html (opens in a new tab)

最近正好在看 Vant 源码,于是我就尝试去实现这个 feature

初次尝试

对于这个功能首先让我想到的是平时使用最多的 dialog 弹出框组件,当使用函数调用时,组件提供了一个选项 allowHtml 表示是否允许在弹出框内容中渲染 HTML

同理,我在 TreeSelect 组件中也加上了这个逻辑,让组件左侧的 Sidebar 也能自定义

const renderSidebar = () => {
  const Items = props.items.map((item) => {
    const renderContent = item.allowHtml ? (
      <div innerHTML={String(item.text)} />
    ) : (
      <>{item.text}</>
    )
 
    return (
      <SidebarItem
        dot={item.dot}
        badge={item.badge}
        class={[bem('nav-item'), item.className]}
        disabled={item.disabled}
        onClick={onClickSidebarItem}
        v-slots={{ title: renderContent }}
      />
    )
  })
}

开发者在使用该组件时,需要提前定义TreeSelectItem 数据结构:

[
  {
    // 导航名称
    text: '所有城市',
    // 导航名称右上角徽标
    badge: 3,
    // 是否在导航名称右上角显示小红点
    dot: true,
    // 导航节点额外类名
    className: 'my-class',
    // 是否允许 text 内容中渲染 HTML
    allowHtml: true,
    // 该导航下所有的可选项
    children: [
      {
        // 名称
        text: '<span style="color:red;">温州</span>',
        // id,作为匹配选中状态的标识符
        id: 1,
        // 禁用选项
        disabled: true,
      },
      {
        text: '杭州',
        id: 2,
      },
    ],
  },
];

相较于之前增加了allowHtml 选项,用于判断是否允许使用 HTML 自定义 text 中的内容。加上对应的文档描述就提交了 PR

一周后,Vant 核心维护者回复了我:

Vant核心维护者回复

他认为提供一个 slot 会比一个 prop 好,主要有两个原因:

  1. slot 可以渲染 Vue 组件,prop 只能渲染 HTML
  2. 提供 prop 渲染自定义 HTML 可能会导致 XSS 安全问题

再次尝试

const Items = props.items.map((item) => (
  <SidebarItem
    dot={item.dot}
    title={item.text}
    badge={item.badge}
    class={[bem('nav-item'), item.className]}
    disabled={item.disabled}
    onClick={onClickSidebarItem}
    v-slots={{ title: slots['nav-text']?.() }}
  />
))

这次就简单多了,核心代码就一句:

v-slots={{ title: slots['nav-text']?.() }}

判断开发者是否传入了 nav-text 这个 slot,将其结果再次传入给 SidebarItem 组件名为 title 的 slot,在此其中也做了判断:

// SidebarItem 组件
{
  slots.title ? slots.title() : title
}

这样一来就实现了自定义组件左侧 Sidebar 内容的需求

最终效果

终于,我的 PR 被合并了,成为了 Contributor , 在 README 中也可以看到我的头像

Contributor

后续

Vant 核心维护者对我提交的代码做了一些优化:

  1. 优化文档描述
  2. 针对该 feature 增加单元测试
  3. 移除了组件 prop 传入 text 的方式,改用在 slot 中使用三元运算符判断传入
const Items = props.items.map((item) => (
  <SidebarItem
    dot={item.dot}
    badge={item.badge}
    class={[bem('nav-item'), item.className]}
    disabled={item.disabled}
    onClick={onClickSidebarItem}
    v-slots={{
      title: () => (slots['nav-text'] ? slots['nav-text'](item) : item.text),
    }}
  />
))

目前该 feature 已在 v4.1.0 中正式被发布,详情见 更新日志 (opens in a new tab)

总结

通过参与开源项目,可以提高自己的技术水平和知名度,在贡献的过程中,可以学到更多的知识和技能。如果你也在学习源码,不妨试着给项目提一个 PR 吧

阅读更多

返回
+1