vue编写更好用的组件的几点建议!

1,141 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

前言

前几天封装好了一个更好用的表格组件,组件基本上可以满足使用,但是如果要给更多的人使用,必须要有一个良好的使用体验,比如为了满足各种不同人的使用体验,所以需要在代码里边可以通过几种不同的书写方法达到写功能的目的。比如vue中就提供了使用templatejsx两种方式去写视图。那么我们在写vue代码中可以通过什么方式可以提高用户的书写体验呢?

建议组件提供多种编写方式给用户,条条大路通罗马

image.png

表格组件的时候,为了让表格组件可以灵活满足各种需求,给组件抛出了一个自定义插槽来给用户,在组件外部使用插槽的方式自定义内容,有位同学就建议了,在表格组件抛出一个render函数会更方便,当然我是觉得每个人都有每个人的喜爱,有的人喜欢用插槽,有的人喜欢写render函数.所以我就在现有的功能基础上,再抛出来render函数的方式去自定义内容,以下是实现代码:

<el-table-column
  :key="item.prop"
  v-bind="item"
  show-overflow-tooltip
>
  <template v-if="item.__slotName" v-slot="scope">
    <slot :name="item.__slotName" :data="scope"></slot>
  </template>
  <template v-else-if="item.__render" v-slot="scope">
    <slot-ext
      :render="item.__render"
      :index="scope.$index"
      :column="item"
      :row="scope.row"
    ></slot-ext>
  </template>
</el-table-column>

用户在使用的时候可以通过数据定义__slotName,然后再在组件里边写一个同名的插槽

// data
columns:[{ label: '头像', prop: 'avatar', align: 'center', __slotName: 'avatar' }]

//template
<mini-table :columns="columns">
  <template slot="avatar" slot-scope="scope">
    <img slot="avatar" width="40" :src="scope.data.row.avatar" />
  </template>
</mini-table>

也可以使用在render函数写jsx来使用

{ 
  label: '头像', 
  prop: 'avatar', 
  align: 'center', 
  __render: function (h, param) {
      return (
        <img width="40" src={param.row.avatar} />
      )
   } 
}

实现抛出render函数,需要先定义一个slot-ext组件

<script>
export default {
  // 自定义内容的组件
  functional: true,
  props: {
    row: Object,
    render: Function,
    index: Number,
    column: {
      type: Object,
      default: null
    }
  },
  render: (h, data) => {
    const params = {
      row: data.props.row,
      index: data.props.index
    }
    if (data.props.column) params.column = data.props.column
    return data.props.render(h, params)
  }
}
</script>

slot-ext组件是一个函数组件,做的事情很简单,就是作为一个中间层,传入一个props.render函数,然后把该函数里边的h函数和传入该函数组件的数据传出到外部定义的__render函数里. 外部函数有了h函数和需要的行数据后,就可以使用jsx和数据自定义内容了.

没有使用typescript的项目建议使用jsDoc增强vscode编码体验

在我之前写的表格组件里边,需要定义一下查询组件的一个描述数组来描述表格需要有什么查询条件. 大概定义出来长这个样子

const searchArr = 
[
    {
      __type: 'input',
      __key: 'name',
      label: '姓名'
    },
    {
      __type: 'select',
      __key: 'sex',
      __fetchUrl: '/getSelect',
      __method: 'post',
      label: '性别'
    },
    {
      __type: 'input',
      __key: 'age',
      label: '年龄'
    },
    {
      __type: 'input',
      __key: 'education',
      label: '学历',
      data: [
        {
          text: '大专',
          value: '大专'
        },
        {
          text: '二本',
          value: '二本'
        },
        {
          text: '一本',
          value: '一本'
        }
      ]
    },
    {
      __type: 'select',
      __key: 'hobby',
      label: '爱好',
      data: [
        {
          text: '打篮球',
          value: '打篮球'
        },
        {
          text: '踢足球',
          value: '踢足球'
        }
      ]
    },
    {
      __type: 'select',
      __key: 'province',
      __fetchUrl: '/getProvince',
      __method: 'get',
      __nextKey: 'city',
      __nextFetch: '/getCity',
      __nextParam: ['province'],
      __nextMethod: 'get',
      __emptyArr: ['city', 'district'],
      label: '省',
      labelWidth: '40px'
    },
    {
      __type: 'select',
      __key: 'city',
      __method: 'get',
      __nextKey: 'district',
      __nextFetch: '/getDistrict',
      __nextParam: ['province', 'city'],
      __nextMethod: 'get',
      __emptyArr: ['district'],
      label: '市',
      labelWidth: '40px'
    },
    {
      __type: 'select',
      __key: 'district',
      label: '区',
      labelWidth: '40px'
    },
    {
      __type: 'date',
      __key: 'birthday',
      format: 'yyyy-MM-dd',
      label: '生日',
      'value-format': 'yyyy-MM-dd'
    }
  ]

这种定义有一个问题,就是如果一个新人进来,使用这个组件,你在不给他文档的情况下,他根本不知道这个组件有哪一些内部组件自己定义的字段,也就是字段名前面带__的。并且就算知道了有哪些内部字段,也不太清楚这个字段是干什么的. 哪怕在有文档的情况下,你也很难一下记住他们. 这种情况有什么办法解决呢? 我的思路是, 新建一个生成这个json数据的方法. 然后再给这个方法加上jsDoc. 这样创建的表单更具语义,写起来也比较简单,并且字段含义悬浮在字段上就能看到. 不用时刻翻文档了. 哈哈~ 下面是创建的表单的方法.


/**
 * 创建表单
 * @class 
 */
export const createForm = (function() {

  const createForm = function() {
    /** 创建后生成的表单描述列表 */ 
    this.form = []
  }

  /**
   * 创建输入框控件
  * @param { import('element-ui/types/select').ElSelect 
  * | import('../index').BaseParams } params 参数
   */
  createForm.prototype.createInput = function(params) {
    this.form.push({
      ...params,
      __type: 'input',
    })
    return this
  }

  /**
   * 创建单选下拉列表控件
   * @param { import('element-ui/types/select').ElSelect 
   * | import('../index').LocalSelect } params 参数
   */
  createForm.prototype.createSelect = function(params) {
    this.form.push({
      ...params,
      __type: 'select',
    })
    return this
  }

    /**
   * 创建多选下拉列表控件
   * @param { import('element-ui/types/select').ElSelect 
   * | import('../index').LocalSelect } params 参数
   */
  createForm.prototype.createMulSelect = function(params) {
    this.form.push({
      ...params,
      __type: 'mulSelect',
    })
    return this
  }

  /**
   * 创建日期控件
  * @param { import('element-ui/types/select').ElSelect 
  * | import('../index').BaseParams } params 参数
   */
  createForm.prototype.createDate = function(params) {
    this.form.push({
      ...params,
      __type: 'date',
    })
    return this
  }

  return createForm

})()

方法很简单,其实就是初始化对象的时候,先定义内部一个空数组,然后在原型上写上各种创建表单控件的方法. 方法内return this,这样的话,我们就可以链式调用了.

  • 代码提示的话,我们在方法上面写上了@param { ... } params,这样方法里边的参数就是从element-ui定义好的参数和我们内部定义好的参数里边选择了. 然后我们看看我们项目根目录定义的一个index.d.ts代码
export type LocalType = 'select' | 'mulSelect' | 'input' | 'date'

export declare class BaseParams {

  /** 类型 input、select、date */
  // __type: LocalType

  /** 传递给后台的字段名称 */
  __key: string

}

/**
 * 下拉框选择参数
 */
export declare class LocalSelect extends BaseParams {

  /** 下拉数据 */
  __data: string

  /** 请求接口 */
  __fetchUrl: string

  /** 请求方式 post、get */
  __method: string

  /** 选择上级下拉数据的时候,联动下一个请求的字段 */
  __nextKey: string

  /** 选择上级下拉数据的时候,联动下一个请求的接口 */
  __nextFetch: string

  /** 选择上级下拉数据的时候,联动下一个请求需要的参数 */
  __nextParam: []

  /** 选择上级下拉数据的时候,联动下一个请求的请求方式 post、get */
  __nextMethod

  /** 选择上级下拉数据的时候,联动需要清空哪些下拉框 */
  __emptyArr: []

  /** 下拉选择改变事件 */
  __onChange (): void

}

编码效果v

下面我们看一下在vscode上实际的使用情况: GIF.gif

写在最后

ok, 以上就是我在写表格组件的时候,做的一些小优化,目的是让使用组件的用户可以更好的使用这个组件。 如果本文章对你有一些小帮助的话,欢迎点赞,评论! 写文章不易,你们的支持就是我的动力! 那么,谢谢啦~,大家下次见。