问题描述
最近,测试给提了一个bug,在表单组件的某个子项前出现了一个冒号:
问题分析
作为一个刚进公司的菜鸟,第一反应可能是公司根据Element Plus二次封装的组件库出问题了,直接打开组件库一头扎进表单组件。好家伙,近两千行的.vue文件差点给我干蒙了。
好一阵寻找才发现,表单组件的布局是编写在另一个formLayout.js文件里,终于在该文件中找到了表单组件的渲染逻辑(为使代码简洁删除了部分属性)。
...
<el-form-item
label-width={labelWidth}
ref={itemRef}
// label={showLabel ? config.label : ''}
style={itemStyle}
v-slots={{
label: () => {
return <span>
<el-popover
placement="top"
width="200"
trigger="hover"
v-slots={{
reference: () => {
return config.tips?<el-icon class='tip-style'><Warning /></el-icon>:null
}
}}
>
<span class='tip-style-span'>{config.tips}</span>
</el-popover>
{showLabel ? config.label : ''}
</span>
}
}}
>
...
其中这个label属性引起了我的注意,原来是某个同事根据需求将原来的label属性用插槽重写,在label前增加了一个气泡弹框。但是在插槽中根据showLabel的值来决定是否渲染label的逻辑并没有改变,那为啥会多一个冒号呢?
这个bug看起来就像隐藏了该表单子项的label文本但没隐藏冒号,毕竟原来使用label属性时没出现问题啊?怀着试一试的心情,我把插槽注释掉,仍采用原来的逻辑使用label属性来渲染,这时奇迹出现了:
冒号居然消失了,看来是使用插槽带来的问题,但为什么会这样呢?(暂且埋个伏笔~~~)
问题解决
虽然问题出在插槽上,可也不能不顾需求不使用插槽来渲染气泡框啊,这要咋办呢?转念一想既然问题的根源出在冒号上,那就去找找这个冒号是在哪里定义的。通过定位页面上的冒号,找到了冒号定义的逻辑:
原来是通过一个伪元素::after来增加的,在代码中全局搜索content: ':'(ps:全局搜索yyds!!!),终于找到了冒号产生的根源:
...
.el-form-item__label {
&::after {
content: ':';
}
}
...
既然找到了冒号产生的原因,那目前的一个思路就是找到label这个节点或组件,并根据showLabel的值来判断是否动态渲染这个CSS属性就行了。说干就干,又准备在全局搜索el-form-item__label这个类名,看看是在哪个DOM节点或组件定义的。结果尴尬了,根本就找不到该类名定义的地方,原来这是Element Plus内部定义的类名。
作为一个菜鸟这时我是没辙了,毕竟总不能找到依赖文件中去给这个组件加判断吧?还好在请教公司大佬后得到回复:“可以把伪元素定义的冒号放在label插槽里的span标签上。”一语惊醒梦中人,找不到label节点我还不能换个能找到的吗?修改后的代码如下:
...
<el-form-item
label-width={labelWidth}
ref={itemRef}
// label={showLabel ? config.label : ''}
style={itemStyle}
v-slots={{
label: () => {
// 根据showLabel判断是否显示伪元素冒号:
return <span class={showLabel ? 'el-form-item__label__span' : ''}>
<el-popover
placement="top"
width="200"
trigger="hover"
v-slots={{
reference: () => {
return config.tips?<el-icon class='tip-style'><Warning /></el-icon>:null
}
}}
>
<span class='tip-style-span'>{config.tips}</span>
</el-popover>
{showLabel ? config.label : ''}
</span>
}
}}
>
...
CSS中注释掉原类名,在span标签的类名上添加伪元素::after,代码如下:
...
/*.el-form-item__label {
&::after {
content: ':';
}
}*/
.el-form-item__label__span {
&::after {
content: ':';
}
}
...
这下可解决问题了,大功告成!
伏笔回收
虽然解决了冒号的bug,可开头的问题还是使我困惑,为啥用插槽替换属性时就会出现冒号?带着这个疑问,我反复对比了label使用属性和插槽渲染时页面的DOM节点,终于让我发现了端倪:
使用label属性渲染:
使用label插槽渲染:
当渲染条件showLabel === false时,使用label属性会使label的DOM节点不会被渲染在DOM树上(v-if也给了提示);而使用label插槽会使label的DOM节点仍被渲染在DOM树上。 这也就间接导致了虽然label的值为空不会被渲染,但定义在label节点上的::after伪元素冒号仍然会被渲染。
到这里我恍然大悟,原来一切都是因为label值为空仍被渲染导致的,所以我又想到label值为空时能不能直接把label节点干掉,这样既能和原来的逻辑保持一致,又能减少DOM节点的渲染提高性能(有一丢丢丢提升吗?)。
想到这实现也就不难了:保持原有的CSS渲染逻辑,根据showLabel的值在插槽中决定要不要渲染label节点(考虑可读性可以将label的渲染结构抽出去)。代码如下:
...
<el-form-item
label-width={labelWidth}
ref={itemRef}
// label={showLabel ? config.label : ''}
style={itemStyle}
v-slots={{
// 根据showLabel判断是否渲染label标签
label: showLabel ? () => {
return <span>
<el-popover
placement="top"
width="200"
trigger="hover"
v-slots={{
reference: () => {
return config.tips?<el-icon class='tip-style'><Warning /></el-icon>:null
}
}}
>
<span class='tip-style-span'>{config.tips}</span>
</el-popover>
{showLabel ? config.label : ''}
</span>
} : null // 这里值为''空字符串时DOM仍会渲染label节点?
}}
>
...
查看页面结构,成功消灭label节点:
总结
Element Plus中表单子项el-form-item的label属性的值为空时不会被渲染在DOM树上,而在v-slots插槽中label的值为空时仍会被渲染在DOM树上。- 在插槽中
label属性的值要赋为null才不会被渲染在DOM树上,属性中传入空字符串''就可以。