返回

Vuetify v-data-table 高级自定义渲染:两种最佳实践

vue.js

Vuetify v-data-table 自定义渲染方案

当需要在 Vuetify v-data-table 中覆盖作用域插槽(scoped slots)的默认渲染行为时,可能会遇到一些挑战,比如需要针对特定的值(例如 null 或空值)进行定制化展示,或对数组进行特殊处理。直接操作插槽,虽然可以实现需求,却可能会带来其他问题,例如,需要手动实现默认的渲染逻辑,使得代码复杂,不易维护。

问题:自定义渲染的痛点

一个常见的场景是,希望当表格数据项的值为 null 时,显示占位符(例如“-”),而不是空内容。 初步尝试可能会通过 v-for 遍历 scopedSlots 来覆盖默认行为,但这会引出以下两个问题:

  1. 依赖插槽名称 :方案过度依赖插槽的命名约定。如果 Vuetify 库未来对插槽命名进行了调整,现有的代码就可能会失效, 造成系统不稳定。
  2. 默认行为丢失 :该方案还必须重新实现某些 v-data-table 的默认渲染行为。例如,原本数组类型的字段在表格中会被恰当地渲染成逗号分隔的列表,但覆盖插槽后,这项行为将会丢失。

解决方案一:基于表头(header)的插槽覆盖

一种相对可行的办法是循环 v-data-tableheaders 属性, 而不是直接处理 scopedSlots。 每个 header 通常会有一个 value 属性。利用这个特性可以生成动态的 slot 名称。具体操作如下:

核心思想

  • 不再直接遍历作用域插槽($scopedSlots)。
  • 根据表头的 value 字段动态生成插槽名称,例如item.fieldName
  • 在每个动态命名的插槽内部进行条件渲染。
  • 将自定义渲染逻辑,例如空值处理和数组渲染添加到插槽内部。

代码示例

<template>
  <v-card outlined>
    <v-data-table
      v-bind="$attrs"
      v-on="$listeners"
      dense
    >
      <template
        v-for="{ value } in $attrs.headers"
        #[`item.${value}`]="slotData"
      >
        <slot
          :name="`item.${value}`"
          v-bind="slotData"
        >
          <template v-if="!slotData.value">
              -
           </template>
          <template v-else-if="Array.isArray(slotData.value)">
            {{ slotData.value.join(', ') }}
           </template>
          <template v-else>
           {{ slotData.value }}
          </template>
        </slot>
      </template>
    </v-data-table>
  </v-card>
</template>

步骤:

  1. 引入组件时,该组件需要透传headers和表格数据(items)等属性。
  2. 组件循环遍历 $attrs.headers 中的 value 字段,并用其构造形如item.${value} 的动态插槽名称。
  3. 在生成的插槽内添加逻辑,如果 slotData.value 为空,就显示“-”,如果为数组,就使用, 连接元素。 如果都不是,直接显示slotData.value

原理分析

这种方案能够实现自定义渲染,是因为它利用 headers 中的 value 属性来动态生成插槽名称, 并有针对性的渲染单元格。相较于直接遍历作用域插槽($scopedSlots) 的方案, 这个方案更加稳定和明确。它并没有完全依赖插槽的命名约定。

安全性提示
需要对从 $attrs.headers 中获取的数据做校验。保证 value 属性的存在性并且符合规范。防止因恶意构造的数据而引发意外的渲染错误。

解决方案二: 利用 headers 的函数型 value 属性 (Vuetify 3+)

从 Vuetify 3 版本开始,每个 header 中的 value 字段,可以接受一个函数。这提供了一种更简洁的自定义渲染方法。

核心思想

  • 不再直接覆盖插槽。
  • 利用 headers 中的函数式 value 属性。
  • 在函数中根据 item 的值进行定制化渲染。
  • 实现高度灵活性和控制权,无需深入操纵作用域插槽。

代码示例

const headers = [
  {
    text: '姓名',
    value: 'name'
  },
  {
    text: '年龄',
    value: 'age',
    //'age' 字段进行自定义处理, 如果年龄为空,则显示 '-'.
    // 如果非空,正常展示
    value: (item) => item.age ?? '-'
  },
    {
    text: '技能',
    value: 'skills',
    // 处理 'skills'  数组字段,将数据合并为字符串
     value: (item) => Array.isArray(item.skills)?item.skills.join(", ") : ""
  },
    {
      text: 'ID',
    value: (item) => item.id ?? '-' // 也可以用于渲染其它字段
  }

];

步骤

  1. 定义 headers 配置,为 value 属性赋予函数。函数接受当前表格行(item)作为参数。
  2. 在函数中,可以灵活判断并处理单元格数据的值。例如,若字段值为空,可以返回一个占位符,若为数组则合并数组元素,或进行其他类型的数据转换。

原理分析

此方案巧妙利用了 Vuetify 的配置机制,直接在 headers 结构中定义了如何显示单元格数据,避免了直接操作 DOM 和作用域插槽,使得代码更加清晰,减少了潜在的问题。并且,它不会有数据格式的问题。数组依旧按原样解析成 , 分隔的列表。它同时实现了控制性和灵活性。它不会出现 header 值与 slot 命名约定的问题。

安全性提示

确保在 value 函数中处理各种可能的边缘情况,如 item 不存在、 item[value] 不存在或类型错误,进行防御性编程,例如:

value: (item) => (item && item.age ? item.age : '-');

总结

针对 Vuetify v-data-table 的自定义渲染需求,应避免直接操作作用域插槽 ($scopedSlots), 因为这种方法虽然可行,但是容易产生依赖命名,且丢失部分默认行为的缺点。 使用循环遍历 headers 并使用动态的 slot 名称,可以达到类似的目的。然而,更建议利用 Vuetify 3 中 headers的函数式 value属性, 在headers结构中定义数据渲染方式。 这个方法无需深入操纵 slot。 这两种方法都提供了自定义渲染能力,但在工程实践中,应当结合实际需求选择合适的方案。对于简单的自定义需求,方案二已经足够优秀;复杂定制可能需要对方案一进行深度扩展。选择方案二更为简洁高效。