likes
comments
collection
share

思想:封装表格渲染动态组件🥰(万num)

作者站长头像
站长
· 阅读数 23

前言


学会了用旅游的心态看待自己生活的城市! 果然是,心态好,哪里都是巴厘岛啊。(喜欢这段话)

思想:封装表格渲染动态组件🥰(万num)

你说你看过日照金山,而我珍惜眼前的棉花糖也很不错啊!

一、想要的效果

请允许我用最简单朴素的话描述。

在我们封装的表格组件中,能根据JSON配置可渲染出switch开关(基于elementui),并且能够交互。

思想:封装表格渲染动态组件🥰(万num)

为什么我要用配置传入JSON对象的形式封装表格

ElementUI 列数据必须使用插槽的问题

思想:封装表格渲染动态组件🥰(万num)

如果我有很多个字段,那el-table-column就要写很多个。

有点小累。

我想要的组件是:

思想:封装表格渲染动态组件🥰(万num)

仅需要做简单的 columns 配置并传入数据就能够出现一个完整的表格,在 template 模板中根本不需要写过多的列数据代码

所以才想要封装一个表格组件,基本的功能都支持了:自定义组件、分页等等。

我觉得这是我们在开发中,遇到不顺心的组件应该要去思考的,沉淀出属于自己的东西。

每个页面都写着差不多的模板,是不是可以不再做重复性工作?

如何去封装组件,怎么样才能够到达可观性、复用性,降低团队的开发成本。

你说是吧,差不多的先生~

好了,接下来正文开始。

二、回忆原本功能

前篇相关可移步:

2.1 原生

我们知道,在之前我们已经实现了可显示元素渲染。

如i标签,icon,这个关闭的×

思想:封装表格渲染动态组件🥰(万num)

代入如下:

{
    prop: "aaa",
    label: "使用时间",
    align: "center",
    "header-align": "center",
    fixed: "right",
    width: 150,
    event: () => {
      return "";
    },
    // 配置表格内容
    tempBody: [
      // 勾
      {
        show: true,
        position: "right",
        name: "div",
        ele: '<i class="el-icon-check" style="color: green; font-size: 22px;" ></i>',
        // 显示
        interface: val => {
          return val.row.aaa;
        },
        attribute: {}
      },
      // ×
      {
        show: true,
        position: "right",
        name: "div",
        ele: '<i class="el-icon-close" style="color: red;font-size: 22px;" ></i>',
        // 隐藏
        interface: val => {
          return !val.row.aaa;
        },
        attribute: {}
      }
    ]
  }

2.2 tooltip

这种效果我们也实现成功

思想:封装表格渲染动态组件🥰(万num)

代码如下:

{
    prop: "aaa",
    label: "使用时间",
    align: "center",
    "header-align": "center",
    fixed: "right",
    width: 150,
    event: () => {
      return "";
    },
    // 配置表格内容
    tempBody: [
      {
        show: true,
        position: "left",
        name: "el-tooltip",
        ele: '<i class="el-icon-warning" style="cursor: pointer;margin-right: 5px;color: #E6A23C;"></i>',
      
        attribute: {
          placement: "top",
          effect: "dark",
          contentSlot: (scope, coloumnHeader) => {
            return  `提示: 该于 ${scope.row.aaa} 被体情况请点击【
      <span class="detailed-link" style="cursor: pointer;color: #409EFF;">详情</span>】`
          },
        },
      },
    ]
  }

三、分析

3.1 现有功能无法交互

那么,既然能渲染元素,那现在的问题是什么?

现在的问题是:原生元素可渲染,el-switch第三方组件也能渲染。

思想:封装表格渲染动态组件🥰(万num)

但无法进行交互,如开关能触发change事件,做一些业务的事情。

可能还是有点难理解,不着急,我们先实现下代码的配置。

我们可以很轻松的写出这段配置。

页面中el-switch也能显示。

{
    prop: "aaa",
    label: "启用状态",
    align: "center",
    "header-align": "center",
    fixed: "right",
    width: 150,
    event: () => {
      return "";  // 为什么这里要设置value值为空,因为他是布尔类型,会显示true/false;但此处只需要展示开关
    },
    // 配置表格内容
    tempBody: [
      {
        show: true,
        position: "left",
        name: "el-switch",
        // 是否显示,条件判断
        interface:(val) =>{
          return val.row.aaa
        },
        attribute: {
          value: true,  // true则开,因为el-switch是用v-model绑定的,底层就是:value和@change
        },

      },
    ]
  },

看下table中的子组件DynamicComponents源码:

思想:封装表格渲染动态组件🥰(万num)

tips:相信大家看到新写的v-on,原本没有的,先忽略。

看完源码后,发现el-switch想要触发开/关change事件,根本没有实现,因为之前需求对表格的定位基本只是展示。

3.2 扩展v-on绑定事件

那么,我们现在需要扩展功能🙃。

相信大家已经看到答案了,用v-on绑定事件。

补充一点知识:

绑定多个事件:

<button 
  v-on="{ 
    mouseover: onMouseOverHandler, 
    mouseout: onMouseOutHandler 
  }"
>
  Hover me
</button>

于是配置,传入对象事件:

{
  show: true,
  position: "left",
  name: "el-switch",
  interface:(val) =>{
    return val.row.aaa
  },
  attribute: {
    value: true,
  },
  attrEvent:{
    change: (e) =>{
      this.$emit('attrEvent', e, 'aaa'); // 暴露给父组件
    }
  }

},

我们重点看这段配置

attrEvent:{
    change: (e) =>{
      this.$emit('attrEvent', e, 'aaa'); // 暴露给父组件
    }
  }

这里的this.emit是不可以的,我们这是写在js文件中,无vue实例的emit是不可以的,我们这是写在js文件中,无vue实例的emit是不可以的,我们这是写在js文件中,无vue实例的emit,尽管我可以import导入vue。

但由于内嵌了很多层,(封装得比较细),这里用事件总线bus解决。

import bus from '@/util/bus.js';

attrEvent:{
    change: (e) =>{
      bus.$emit('attrEvent', e, 'aaa'); // 暴露给父组件
    }
  }

bus.js

import Vue from 'vue';
export default new Vue();

在使用组件可监听事件

use.vue

bus.$on("attrEvent", (scope, coloumnHeader) => {
  
}); // 监听事件

当我们页面触发switch开关时,use组件确实可以监听到事件。

假如现在是开的状态下,点击之后,变成关闭状态。

我们需要手动在use组件中去改变他的布尔值。

思想:封装表格渲染动态组件🥰(万num)

但我们发现,写不下去了,因为只接收到

我们回顾一下子组件的配置传什么给父组件use

e:true/false(开关状态)

第二个参数:当前的表头字段名

bus.$emit('attrEvent', e, 'aaa'); // 暴露给父组件

只有这两个字段,我们无法判断当前点击的是哪一行数据。

但问题是JSON配置页拿不到是哪一行数据。

3.3 表格DynamicComponents需传参给外面的use使用

继续看,你将能明白讲述的意思。

所以,我们table的DynamicComponents组件要传参给配置,配置才能$emit暴露给父组件。

<template>
  <span>
    <!-- v-bind绑定属性(传入对象) -->
    <component :is="item.name" v-bind="item.attribute" v-on="item.attrEvent">
      <template slot="content">
        <span v-if="item.attribute.contentSlot" v-html="item.attribute.contentSlot(scope, coloumnHeader)" @click="$emit('componentClick')"></span>
      </template>
      <span v-html="item.ele"></span>
    </component>
  </span>
</template>

这里的v-on="item.attrEvent",我们做下解构

v-on="{...item.attrEvent}"

好像也不行。没办法传。

tips: 我会把当时思考的想法都一五一十的记录下来,所以会看到很多过程。

v-on要写成方法,我们在方法中来传递。

v-on="attrEvent()"

attrEvent() {
  if (!this.item.attrEvent) return {};

  let obj = {};
  Object.keys(this.item.attrEvent).forEach((property) => {
    obj[property] = this.item.attrEvent[property](this.scope, this.coloumnHeader, this.item); // 在表格我们可以拿到哪一行的数据
  });
  return {};
},

但是,方法一旦加上括号(),就会立即执行,这里又没返回值,就变成

v-on = "{
  change: undefined
}"

v-on即会报错。

那我们想,配置就不要去处理change里面到底执行什么,我们放在table组件中写。

即:

配置:写事件名就好

attrEvent: ['change']

DynamicComponents组件

v-on="attrEvent()"

attrEvent() {
  if (!this.item.attrEvent) return {};

  return this.item.attrEvent.reduce((pre, next) =>{
    pre[next] = bus.$emit('attrEvent', this.scope, this.coloumnHeader, this.item); // 暴露给父组件
    return pre
  }, {})
},

那么,我们在use使用组件

bus.$on("attrEvent", (scope, coloumnHeader) => {
  scope.row[coloumnHeader.prop] = !scope.row[coloumnHeader.prop]
}); // 监听事件

拿到哪一行数据,状态给他取反即可。

发现页面的switch状态没变。

不要忘记了,在配置那里需要写两个状态,一个开,一个关的配置。

{
  prop: "aaa",
  label: "启用状态",
  align: "center",
  "header-align": "center",
  fixed: "right",
  width: 150,
  event: () => {
    return "";  // 为什么这里要设置value值为空,因为他是布尔类型,会显示true/false;但此处只需要展示开关
  },
  // 配置表格内容
  tempBody: [
    // 开
    {
      show: true,
      position: "left",
      name: "el-switch",
      interface:(val) =>{
        return val.row.aaa
      },
      attribute: {
        value: true,
      },
      attrEvent: ['change']
    },
    // 关
    {
      show: true,
      position: "left",
      name: "el-switch",
      interface:(val) =>{
        return !val.row.aaa
      },
      attribute: {
        value: false,
      },
      attrEvent: ['change']
    },
  ]
},

分析一下:

页面一开始进入,假设当前状态true,进入配置tempBody数组的interface为true的条件,渲染el-switch组件,开的状态。

绑定了change事件。

当点击switch从开到关,状态变化,触发change,在·DynamicComponents组件的v-on事件,走进attrEvent方法,触发bus.$emit,在use组件监听attrEvent 取反了数据的状态,会进入配置的tempBody数组的interface为false的条件,状态改为false渲染成功。

状态变了,又触发change事件,上面的流程又不断触发。

3.4 防抖方多次触发

经验丰富的我们,很快就想到了防抖。

改造一下DynamicComponents组件。

v-on="attrEvent()"

attrEvent() {
  if (!this.item.attrEvent) return {};

  return this.item.attrEvent.reduce((pre, next) =>{
    pre[next] = debounce(this.emitAttrEvent, 300);
    return pre
  }, {})
},

emitAttrEvent() {
  bus.$emit("attrEvent", this.scope, this.coloumnHeader, this.item); // 暴露给父组件
},

/**
 * 防抖
 * @param {Function} func 
 * @param {Number} delay 
 * @returns 
 */
export const debounce = (func, delay) =>{
  let timer;

  return function (...args) {
    clearTimeout(timer);

    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

这样子就可以了。尽管会多次触发change,但我们防抖300毫秒内有触发,即取消,重新等待300毫秒,这样子可避免不断触发。

至此撒花~

后记

这种类似的场景一般用于可视化低代码平台中。

会根据配置来动态渲染。

做好扩展性,遵守开闭原则。

当然,我们还要考虑安全性防XSS注入,防止外部嵌入,导致安全问题。

如果有其他更好的方法也欢迎评论区见,这里提供的只是诸多方法之一。

最后,祝君能拿下满意的offer。

👍 如果对您有帮助,您的点赞是我前进的润滑剂。

以往推荐

原文链接

转载自:https://juejin.cn/post/7386501850042662946
评论
请登录