在vue文件中使用render函数封装实现动态组件

近期遇到一个vue项目中的table页面,数据和模板强耦合在一个页面中,看起来比较复杂,维护较不方便,而该页面又重复性较高,于是尝试在vue中用循环渲染dom元素,抽离公共部分配置化,很不幸的是v-for并不支持动态的dom元素插入,那么怎么用vue实现动态dom元素节点的动态渲染呢。

联想到react的实现方式,想到vue的render函数,vue中的render号称可以像react一样写页面,有了想法就开始实施。render函数是比较好理解的,但是在render中如何实现 插槽,事件点击,Scope,  以及如何在render中使用 开源库呢?

经过对vue编译之后虚拟dom的调研,这些都是可以实现的。

插槽和scope:可以直接写成 (scope)=> {}的形式

事件点击:nativeOnClick

开源库:事件变为  'on-' + '    '   比如:  on-selection-change

下面是一个DynamicTablePage实例:

<script>
export default {
  name: 'DynamicTablePage',
  props: {
    msg: String,
    tableParams: Array,
    tableData: Array,
    selections: Array,
    layout: String,
    pageSizes: Array,
    handleSizeChange: Function,
    handleCurrentChange: Function,
    total: Number,
    currentPage: Number,
    hasPagination: Boolean,
    handleSelectionChange: {
      type: Function,
      default: () => { }
    },
    paginationFixed: Boolean
  },
  data () {
    return {
    }
  },
  mounted () {
    console.log(this.hasPagination)
  },
  methods: {

  },
  render (h) {
    return <div class="jsx-component">
      <el-table
        data={this.tableData}
        style={{ width: '100%' }}
        class="test"
        on-selection-change={this.handleSelectionChange}
      >
        {
          this.tableParams.map((item, index) => {
            return <el-table-column
              prop={item.param}
              label={item.title}
              width={item.width}
              key={index}
              type={item.type}
              allow-expand={item.allowExpand}
              filters={item.filters}
              align={item.align ? item.align : 'left'}
              selections={this.multipleSelection}
              filter-method={item.filterHandler}
            >
              {
                item.template ? scope => item.template(scope, this, h) : ''
              }
            </el-table-column>
          })
        }
      </el-table>
      {
        this.hasPagination ? <el-pagination
          class={this.paginationFixed ? "fixed" : ''}
          on-size-change={this.handleSizeChange}
          on-current-change={this.handleCurrentChange}
          current-page={this.currentPage}
          page-sizes={this.pageSizes}
          layout={this.layout}
          total={this.total}>
        </el-pagination> : null
      }
    </div >
  }
}
</script>

<style scoped>
.jsx-component {
  background: red;
}
.fixed {
  position: fixed;
  bottom: 0;
}
</style>

对应的配置文件:

let tableParams = [
    {
        title: '',
        param: "",
        width: '30px',
        type: 'selection'
    },
    {
        width: '30px',
        type: 'expand',
        test: {
            name: 1
        },
        allowExpand (_row, index) {
            return index !== 2;
        },
        // eslint-disable-next-line no-unused-vars
        template: (_scope, _self, h) => {
            return <div>测试</div>
        }
    },
    {
        title: '日期',
        param: "date",
        filters: [
            { text: '2018/12/01', value: '2018/12/01' },
            { text: '2018/12/02', value: '2018/12/02' },
            { text: '2018/12/03', value: '2018/12/03' },
            { text: '2018/12/04', value: '2018/12/04' },
            { text: '2018/12/05', value: '2018/12/05' },
            { text: '2018/12/10', value: '2018/12/10' }
        ],
        filterHandler (value, row, column) {
            console.log(value, row, column)
            const property = column['property'];
            return row[property] === value;
        }
    },
    {
        title: '字段2',
        param: "date2",
        template: (scope) => {
            return scope.row.date2.replace(/[1-9]+/g, '000')
        }
    },
    {
        title: '字段3',
        param: "date3",
        // eslint-disable-next-line no-unused-vars
        template: (scope, self, h) => {
            return <div>
                <el-button
                    icon="h-icon-edit"
                    size="mini"
                    nativeOnClick={() => {
                        self.$router.push({ name: 'PageTwo' })
                    }}
                />
            </div>
        }
    }
]

export const tableData = () => {
    return [
        {
            date: '2018/12/01',
            date2: 'dfkjdfjk',
            date3: '测试',
            id: 0
        },
        {
            date: '1221121221',
            date2: 'dfkjdfjk',
            date3: '测试',
            id: 1
        },
        {
            date: '1221121221',
            date2: '1373698562',
            date3: '测试',
            id: 2
        },
        {
            date: '1221121221',
            date2: 'dfkjdfjk',
            date3: '测试',
            id: 3
        }
    ]
}
export default tableParams

使用组件DynamicTablePage

<template>
  <div class="example">
    <h4>页面一</h4>
    <DynamicTablePage
      :tableParams="tableParams"
      :tableData="tableData"
      :total="total"
      layout="total, sizes, prev, pager, next, jumper"
      :currentPage="currentPage"
      :handleSizeChange="handleSizeChange"
      :handleCurrentChange="handleCurrentChange"
      :pageSizes="[100, 200, 300, 400]"
      hasPagination
      :handleSelectionChange="handleSelectionChange"
      paginationFixed
    ></DynamicTablePage>
  </div>
</template>
<script>
import DynamicTablePage from '../../components/DynamicTablePage'
import tableParams, { tableData } from './pageOne.config'
export default {
  name: 'PageOne',
  components: {
    DynamicTablePage
  },
  data () {
    return {
      tableData: tableData(),
      total: 1000,
      selections: [],
      tableParams: tableParams,
      currentPage: 1
    }
  },
  methods: {
    handleSelectionChange (val) {
      console.log(val)
      this.multipleSelection = val;
    },
    handleSizeChange () {

    },
    handleCurrentChange (val) {
      console.log(val)
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.example {
  width: 800px;
  margin: 0 auto;
  height: auto;
}
</style>

 

该组件是对table的抽象,封装过后表格的每一列和数据都可以抽离为配置化,代码结构清晰,且有利于后期维护。如果有相同的页面,可以通过配置快速生成页面。

对于其他的功能,比如表单页面,展示页面等,都可以做类似的封装,从而达到效率和性能及维护的提升。

对于较为复杂组件的封装,还可以结合vuex,插件抽离实现。

 

 

 

 

  • 2
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我可以帮你实现一个Vue3组件动态加载IconPark字体图标库。首先,你需要在项目安装 `@icon-park/vue-next` 和 `@vue/runtime-dom` 两个包,分别用于引入IconPark和Vue3的运行时。 安装命令如下: ```bash npm install @icon-park/vue-next @vue/runtime-dom ``` 接着,你可以创建一个名为 `IconPark.vue` 的组件,代码如下: ```vue <template> <svg :class="['icon', className]" :style="style"> <use :href="`#${iconName}`"></use> </svg> </template> <script> import { defineComponent, h } from '@vue/runtime-dom' import { IconPark } from '@icon-park/vue-next' export default defineComponent({ name: 'IconPark', props: { name: { type: String, required: true }, size: { type: Number, default: 16 }, color: { type: String, default: '#333' }, className: { type: String, default: '' }, style: { type: Object, default: null } }, setup(props) { const iconName = `icon-${props.name}` const icon = IconPark.get(iconName) if (!icon) { console.warn(`[IconPark]: icon '${iconName}' not found`) return null } const width = props.size const height = props.size const color = props.color const style = { width: `${width}px`, height: `${height}px`, fill: color } return { iconName, style } }, render() { return h('div', this.style, [ h('svg', { class: ['icon', this.className], style: this.style }, [ h('use', { href: `#${this.iconName}` }) ]) ]) } }) </script> <style> .icon { display: inline-block; vertical-align: middle; overflow: hidden; } </style> ``` 这里定义了一个 `IconPark` 组件,它接受以下 props: - `name`:要使用的 IconPark 图标的名称,必填。 - `size`:图标的大小,默认为 `16`。 - `color`:图标的颜色,默认为 `#333`。 - `className`:图标的类名。 - `style`:图标的样式。 在 `setup` 函数,我们通过 `IconPark.get` 方法获取了对应的图标对象,并计算出了样式。如果找不到对应的图标,我们会输出一条警告,并返回 `null`。 最后,在 `render` 函数,我们使用了 `h` 函数来创建一个 `svg` 元素,并在其使用 `use` 元素来引用图标。注意,我们这里用了 `h` 函数而不是直接写模板,因为 Vue3 的模板编译器不支持动态引用 SVG 的 `use` 元素。 现在,你就可以在其他组件使用 `IconPark` 了,例如: ```vue <template> <div> <IconPark name="wechat" size="24" color="#7bb32e" /> <IconPark name="github" size="24" color="#000" /> </div> </template> <script> import IconPark from './IconPark.vue' export default { components: { IconPark } } </script> ``` 这样就可以在页面显示出 IconPark 的图标了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值