首页 > 作文

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

更新时间:2023-04-04 11:48:51 阅读: 评论:0

需求:

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

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

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

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

不好的实现:

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

<formtable :data="lessonpackagearr" :fleldsinfo="lessonpackageinfo" :maxcolumn="3" label-width="120px">  <template #prentedhours="{ data }">    <div class="flex-box between">      <span>        {{ data.prentedhours }}      </span>      <span class="column-btn" @click="editprentedhours(data)">修改</span>    </div>  </template>  <template #gifts="{ data }">    <div class="flex-box between">      <span>        {{ data.gifts }}      </span>      <span class="column-btn" @click="editprentedhours(data)">修改</span>    </div>  </template></formtable>

lessonpackageinfo 对象如下结构:

// 一个对象,用于配置标题列和标题列对应的字段// type 指定值的类型,现在组件内部设置可能显示哪些类型的值了// 对于服务其返回 1 0 需要显示 是否的数,提供一个 map_data 来映射// column 属性设置跨列// 需要自定义显示内容 提供 slotlessonpackageinfo: {    ordertype: { type: 'option', desc: '课时包类别', map_data: { 1: '首单', 2: '续费', 5: '赠课' } },    combo: { type: 'text', desc: '套餐名称' },    prentedhours: { 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" c江苏省的大学lass="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-el-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-el-if="data[key] == 10 || data[key] == 34" :class="field.statuslist[1]">                    {{ field.map_data[data[key]] }}                  </span>                  <span v-el :class="field.statuslist[0]">{{ field.map_data[data[key]] }}</span>                </template>                <span v-el>{{ field.map_data[data[key]] }}</span>              </template>              <slot v-el :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-el 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: fal,        type: niphone8发布会视频umber,        default: 2,      },      labelwidth: {        required: fal,        type: string,        default: '90px',      },    },    data() {      return {}    },    computed: {      tabledata() {        if (!this.data) {          return []        }        if (this.data instanceof array) {          return this.data        } el {          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-el-if="datatype === 'image' || datatype === 'cropper'" :class="classname">  <el-popover placement="right" title="" trigger="hover">    <img :src="metadata" style="max-width: 600px;" /对口支援>    <img slot="reference" :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;'                    src={data.workpic}                    preview-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-el>            {{ 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-el>            {{ ![null, void 0].includes(data[item.prop] && data[item.prop]) || '' }}          </span>        </div>      </li>    </ul>    <div v-el 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 函数封装高扩展组件内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

本文发布于:2023-04-04 11:48:50,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/ab85edeffef4d8e5ee0cdf7f23969bfd.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:使用 render 函数封装高扩展的组件.doc

本文 PDF 下载地址:使用 render 函数封装高扩展的组件.pdf

标签:组件   函数   数据   自定义
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图