在 Vant UI 组件库中使用 TreeSelect 分类选择组件时,可以使用 content
插槽自定义右侧区域内容,那么左侧是否也可以自定义呢?
有人提出了这样一个 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 核心维护者回复了我:
他认为提供一个 slot 会比一个 prop 好,主要有两个原因:
- slot 可以渲染 Vue 组件,prop 只能渲染 HTML
- 提供 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 中也可以看到我的头像
后续
Vant 核心维护者对我提交的代码做了一些优化:
- 优化文档描述
- 针对该 feature 增加单元测试
- 移除了组件 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 吧
阅读更多
我和我的自行车2023/10/07
Vue3 使用 setup 语法糖编写组件定义 props2022/03/02