我的编程空间,编程开发者的网络收藏夹
学习永远不晚

使用 render 函数封装高扩展的组件

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

使用 render 函数封装高扩展的组件

需求:

后台管理中常常有如下布局的数据展示需求:

像表格又不是表格,像表单又不是表单,实际上样子像表格,呈现的数据是一个对象,和 form 的绑定的值一样,我将其称为表单式表格。

样式深的列是标题,浅的列是标题对应的取值,数据往往是服务器返回的,标题往往是定宽的,取值可能各种各样,比如显示一张图片,值为 01,需要显示是与否,有时候需要添加一个修改按钮,让用户能修改某些值,还需要设置某一列跨越几列。

先来看看一个基于 element ui 的实现

不好的实现:

在接手的项目看到一个实现,先看使用方式


<FormTable :data="lessonPackageArr" :fleldsInfo="lessonPackageInfo" :maxColumn="3" label-width="120px">
  <template #presentedHours="{ data }">
    <div class="flex-box between">
      <span>
        {{ data.presentedHours }}
      </span>
      <span class="column-btn" @click="editPresentedHours(data)">修改</span>
    </div>
  </template>
  <template #gifts="{ data }">
    <div class="flex-box between">
      <span>
        {{ data.gifts }}
      </span>
      <span class="column-btn" @click="editPresentedHours(data)">修改</span>
    </div>
  </template>
</FormTable>


lessonPackageInfo 对象如下结构:


// 一个对象,用于配置标题列和标题列对应的字段
// type 指定值的类型,现在组件内部设置可能显示哪些类型的值了
// 对于服务其返回 1 0 需要显示 是否的数,提供一个 map_data 来映射
// column 属性设置跨列
// 需要自定义显示内容 提供 slot
lessonPackageInfo: {
    orderType: { type: 'option', desc: '课时包类别', map_data: { 1: '首单', 2: '续费', 5: '赠课' } },
    combo: { type: 'text', desc: '套餐名称' },
    presentedHours: { type: 'text', desc: '赠送课时', slot: true },
    price: { type: 'text', desc: '标准价格' },
    gifts: { type: 'text', desc: '赠送礼物', column: 3, slot: true },
  }


  • props 不够直观,配置项多
  • 不是完全数据驱动

为何组件的配置项多不好?

对于这种需求很固定,组件的输入即 props 应该要最小化,组件功能要最大化,尽量给 props 提供默认值,这样才能提高团队的开发效率。

为何不是完全的数据驱动不好?

这个组件不是完全数据驱动的,需要自定义显示列是,需要编写模板。

如果需要自定义的列很多,就要写很多模板代码,想要再提取,只能再次封装组件,不提取,模板代码可能会膨胀,你可能经常看到动辄 500 行一行的 template ?而膨胀的模板代码,让组件维护变得困难,需要 template 和 js 代码之间来回切换。再者,增加一列自定义的数据,起码要修改两个地方。

为何需要完全的数据驱动?

虽然有 slot 来扩展组件,但是我们在写业务组件时候应该少用,而是尽量使用数据驱动模板。因为数据是 js 代码,当组件代码膨胀时,很容易把 js 代码提取成单独的文件, 而想要提取 slot 的代码,只能再封装组件。

三大前端框架的设计理念都是数据驱动模板,这是它们区别于 jQuery 的重要特征,也是我们封装业务组件时优先遵循的原则。

看了组件使用的问题,再看组件的代码:


<template>
  <div v-if="tableData.length" class="form-table">
    <div v-for="(data, _) in tableData" :key="_" class="table-border">
      <el-row v-for="(row, index) in rows" :key="index">
        <el-col v-for="(field, key) in row" :key="key" :span="getSpan(field.column)">
          <div v-if="(field.disabled && data[key]) || !field.disabled" class="column-content flex-box between">
            <div class="label" :style="'width:' + labelWidth">
              <span v-if="field.required" class="required">*</span>
              {{ field.desc }}
            </div>
            <div class="text flex-item" :title="data[key]">
              <template v-if="key === 'minAge'">
                <span>{{ data[key] }}</span>
                -
                <span>{{ data['maxAge'] }}</span>
              </template>
              <template v-else-if="key === 'status'">
                <template v-if="field.statusList">
                  <span v-if="data[key] == 0" :class="field.statusList[2]">{{ field.map_data[data[key]] }}</span>
                  <span v-else-if="data[key] == 10 || data[key] == 34" :class="field.statusList[1]">
                    {{ field.map_data[data[key]] }}
                  </span>
                  <span v-else :class="field.statusList[0]">{{ field.map_data[data[key]] }}</span>
                </template>
                <span v-else>{{ field.map_data[data[key]] }}</span>
              </template>

              <slot v-else :name="key" v-bind:data="data">
                <TableColContent
                  :dataType="field.type"
                  :metaData="data[key]"
                  :mapData="field.map_data"
                  :text="field.text"
                />
              </slot>
            </div>
          </div>
        </el-col>
      </el-row>
    </div>
  </div>
  <div v-else class="form-table empty">暂无数据</div>
</template>

<script>
  import TableColContent from '@/components/TableColContent'
  export default {
    name: 'FormTable',
    components: {
      TableColContent,
    },
    props: {
      // 数据
      data: {
        required: true,
        type: [Object, Array, null],
      },
      // 字段信息
      fleldsInfo: {
        required: true,
        type: Object,
        // className: { type: "text", desc: "班级名称", column: 3 },
      },
      // 最多显示列数
      maxColumn: {
        required: false,
        type: Number,
        default: 2,
      },
      labelWidth: {
        required: false,
        type: String,
        default: '90px',
      },
    },
    data() {
      return {}
    },
    computed: {
      tableData() {
        if (!this.data) {
          return []
        }
        if (this.data instanceof Array) {
          return this.data
        } else {
          return [this.data]
        }
      },
      rows() {
        const returnArray = []
        let total = 0
        let item = {}
        for (const key in this.fleldsInfo) {
          const nextTotal = total + this.fleldsInfo[key].column || 1
          if (nextTotal > this.maxColumn) {
            returnArray.push(item)
            item = {}
            total = 0
          }
          total += this.fleldsInfo[key].column || 1
          item[key] = this.fleldsInfo[key]
          if (total === this.maxColumn) {
            returnArray.push(item)
            item = {}
            total = 0
          }
        }
        if (total) {
          returnArray.push(item)
        }
        return returnArray
      },
    },
    methods: {
      getSpan(column) {
        if (!column) {
          column = 1
        }
        return column * (24 / this.maxColumn)
      },
    },
  }
</script>

有哪些问题?

  • 模板有太多的条件判断,不优雅
  • 自定义显示列,还需要在引入 TableColContent,增加了组件复杂性

TableColContent 内部还是对配置项的 type 进行条件判断

部分代码:


<span v-else-if="dataType === 'image' || dataType === 'cropper'" :class="className">
  <el-popover placement="right" title="" trigger="hover">
    <img :class="lazy" data-src="metaData" style="max-width: 600px;" />
    <img slot="reference" :class="lazy" data-src="metaData" :alt="metaData" width="44" class="column-pic" />
  </el-popover>
</span>


分析完以上实现的问题,看看好的实现:

好的实现:

先看使用方式:


<template>
  <ZmFormTable :titleList="titleList" :data="data" />
</template>
<script>
  export default {
    name: 'Test',
    data() {
      return {
        data: {}, // 从服务器获取
        titleList: [
          { title: '姓名', prop: 'name', span: 3 },
          {
            title: '课堂作品',
            prop: (h, data) => {
              const img =
                (data.workPic && (
                  <ElImage
                    style='width: 100px; height: 100px;'
                    class="lazy" data-src={data.workPic}
                    preview-class="lazy" data-src-list={[data.workPic]}
                  ></ElImage>
                )) ||
                ''
              return img
            },
            span: 3,
          },
          { title: '作品点评', prop: 'workComment', span: 3 },
        ],
      }
    },
  }
</script>


组件说明: titleList是组件的列配置,一个数组,元素 title 属性是标题,prop 指定从 data 里取值的字段,span 指定这列值跨越的行数。

prop 支持 string ,还支持函数,这是实现自定义显示的方式,当这个函数很大时,可提取到独立的 js 文件中,也可以把整个 titleList 提取单独的 js 文件中。

参数 h 和 data 是如何传递进来的?或者 这函数在哪调用呢?

h 是 createElement 函数,data 是从组件内部的 data,和父组件传入的 data 是同一个值。

当普通函数的第一个参数是 h 是,它就是一个 render 函数。

这种方式使用起来简单多了。

看看内部实现:

<template>
  <div class="form-table">
    <ul v-if="titleList.length">
      <!-- titleInfo 是经过转化的titleList-->
      <li
        v-for="(item, index) in titleInfo"
        :key="index"
        :style="{ width: ((item.span || 1) / titleNumPreRow) * 100 + '%' }"
      >
        <div class="form-table-title" :style="`width: ${titleWidth}px;`">
          <Container v-if="typeof item.title === 'function'" :renderContainer="item.title" :data="data" />
          <span v-else>
            {{ item.title }}
          </span>
        </div>
        <div class="form-table-key" :style="`width:calc(100% - ${titleWidth}px);`">
          <Container v-if="typeof item.prop === 'function'" :renderContainer="item.prop" :data="data" />
          <span v-else>
            {{ ![null, void 0].includes(data[item.prop] && data[item.prop]) || '' }}
          </span>
        </div>
      </li>
    </ul>
    <div v-else class="form-table-no-data">暂无数据</div>
  </div>
</template>

<script>
  import Container from './container.js'
  export default {
    name: 'FormTable',
    components: {
      Container,
    },
    props: {
      titleWidth: {
        type: Number,
        default: 120,
      },
      titleNumPreRow: {
        type: Number,
        default: 3,
        validator: value => {
          const validate = [1, 2, 3, 4, 5, 6].includes(value)
          if (!validate) {
            console.error('titleNumPreRow 表示一行有标题字段对,只能时 1 -- 6 的偶数,默认 3')
          }
          return validate
        },
      },
      titleList: {
        type: Array,
        default: () => {
          return []
        },
        validator: value => {
          const validate = value.every(item => {
            const { title, prop } = item
            return title && prop
          })
          if (!validate) {
            console.log('传入的 titleList 属性的元素必须包含 title  和 prop 属性')
          }
          return validate
        },
      },
      data: {
        type: Object,
        default: () => {
          return {}
        },
      },
    },
  }
</script>
<!-- 样式不是关键,省略 -->

实现自定义显示的方式,没有使用动态插槽,而是用一个函数组件Container,该组件接收一个 render 函数作为 prop


export default {
  name: 'Container',
  functional: true,
  render(h, { props }) {
    return props.renderContainer(h, props.data)
  },
}

Container 内部调用 titleList 传入的函数。

总结:

  • 封装组件时优先考虑数据驱动
  • 普通函数的第一个参数是 h,就是渲染函数
  • 可能有一些人不习惯写 JSX, 可兼容两种写法

到此这篇关于使用 render 函数封装高扩展的组件的文章就介绍到这了,更多相关 render 函数封装高扩展组件内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

使用 render 函数封装高扩展的组件

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Vue3 封装扩展并简化Vuex在组件中的调用问题

这篇文章主要介绍了Vue3 封装扩展并简化Vuex在组件中的调用,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2023-01-28

ECMAscrip函数的扩展怎么使用

本篇内容主要讲解“ECMAscrip函数的扩展怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“ECMAscrip函数的扩展怎么使用”吧!1.函数参数的默认值1.1函数参数指定默认值在ECM
2023-06-21

如何使用vue组件封装共用的组件

这篇文章主要介绍了如何使用vue组件封装共用的组件的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇如何使用vue组件封装共用的组件文章都会有所收获,下面我们一起来看看吧。这里提供两种vue封装共用组件的方法方法一
2023-06-30

使用 PHP 函数的最佳实践:高并发和可扩展性?

遵循 php 函数最佳实践可提升高并发和可扩展性。具体做法包括:1. 优先使用内置函数;2. 缓存函数结果;3. 限制递归深度;4. 使用惰性求值;5. 并行处理大型数据集。PHP 函数的最佳实践:高并发和可扩展性在构建高并发和可扩展的
使用 PHP 函数的最佳实践:高并发和可扩展性?
2024-05-05

使用PHP自定义函数扩展数组交集和并集的功能

使用 php 自定义函数可扩展数组交集和并集功能,自定义交集函数允许按键或值查找交集,而自定义并集函数按键或值查找并集。这使您能够基于特定需求灵活操作数组。使用 PHP 自定义函数扩展数组交集和并集在 PHP 中,交集和并集是两个经常使用
使用PHP自定义函数扩展数组交集和并集的功能
2024-05-01

如何使用PHP7的匿名函数和闭包实现更加灵活和可扩展的逻辑封装?

如何使用PHP7的匿名函数和闭包实现更加灵活和可扩展的逻辑封装?在PHP编程中,逻辑封装是一种常见的编程技巧,它可以把一段特定的代码逻辑进行封装,便于复用和维护。PHP7引入了匿名函数和闭包的特性,使得逻辑封装更加灵活和可扩展。本文将介绍如
2023-10-25

vue 封装一个高质量的表单通用组件

这篇文章主要为大家介绍了vue如何封装一个高质量的表单通用组件方法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-03-10

微信小程序使用扩展组件库WeUI的方法

本篇内容介绍了“微信小程序使用扩展组件库WeUI的方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. 学习参考2.NodeJs初始化这里
2023-06-30

如何使用PHP7的匿名函数和闭包提高代码的可扩展性?

如何使用PHP7的匿名函数和闭包提高代码的可扩展性?引言:在软件开发过程中,代码的可扩展性是一个非常重要的考量因素。当项目的规模逐渐增大,需求不断变化时,我们需要一种灵活的方法来扩展和调整代码,以应对不断变化的需求。PHP7引入的匿名函数和
2023-10-22

Ant Design Vue Pagination分页组件的封装与使用

这篇文章主要介绍了Ant Design Vue Pagination分页组件的封装与使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-05-17

编程热搜

目录