Element Plus表单子项label属性与插槽用法的小坑

685 阅读5分钟

问题描述

最近,测试给提了一个bug,在表单组件的某个子项前出现了一个冒号:

image.png

问题分析

作为一个刚进公司的菜鸟,第一反应可能是公司根据Element Plus二次封装的组件库出问题了,直接打开组件库一头扎进表单组件。好家伙,近两千行的.vue文件差点给我干蒙了。

image.png

好一阵寻找才发现,表单组件的布局是编写在另一个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属性来渲染,这时奇迹出现了:

image.png

冒号居然消失了,看来是使用插槽带来的问题,但为什么会这样呢?(暂且埋个伏笔~~~)

问题解决

虽然问题出在插槽上,可也不能不顾需求不使用插槽来渲染气泡框啊,这要咋办呢?转念一想既然问题的根源出在冒号上,那就去找找这个冒号是在哪里定义的。通过定位页面上的冒号,找到了冒号定义的逻辑:

image.png

原来是通过一个伪元素::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: ':';
    }
  }
...

这下可解决问题了,大功告成!

image.png

伏笔回收

虽然解决了冒号的bug,可开头的问题还是使我困惑,为啥用插槽替换属性时就会出现冒号?带着这个疑问,我反复对比了label使用属性和插槽渲染时页面的DOM节点,终于让我发现了端倪:

使用label属性渲染:

image.png

使用label插槽渲染:

image.png

当渲染条件showLabel === false时,使用label属性会使labelDOM节点不会被渲染在DOM树上(v-if也给了提示);而使用label插槽会使labelDOM节点仍被渲染在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节点:

image.png

总结

  1. Element Plus中表单子项el-form-itemlabel属性的值为空时不会被渲染在DOM树上,而在v-slots插槽中label的值为空时仍会被渲染在DOM树上。
  2. 在插槽中label属性的值要赋为null才不会被渲染在DOM树上,属性中传入空字符串''就可以。