如何快速提高前端开发效率?

247 阅读4分钟

当下正值五月,上海的天气还挺凉爽,老程序员的内心很焦虑,焦虑可能会失业。想想做点什么事儿吧,于是干点小活,写这篇文章。写点什么呢,就写前端组件标准化。这个课题是我三年前,在某虎任职的时候,我们的领导提出来的一个课题。

当时某虎大概有100+前端开发人员,这么大的队伍,归属不同的业务部门,不同的团队,但有一个统一的组织——大前端,网罗了web前端,移动端,小程序,部分nodejs。当然,web端的人数最多,提效就显得尤为重要。

组件标准化,核心是为了解决开发效率问题。我记得当时有说要搞一个拖拽式低代码平台,当时是不被大领导看好的,他说咱们先不说项目的可行性,我们最低的要求新人进入公司,要能够保证他学到知识和技能,走出公司能让人有一技之长。这其实是比较婉转的否决了拖拽式的项目,但是又不太好直截了当的说明。

我当时在某虎任职前端开发组长,团队有10个人左右。在上面提出标准化的同时,我们其实自己内部也在做这件事情,也在尝试解决效率问题。团队人数不算少,但当时每个人依旧很忙,几乎每天都要加班。作为组长,我有责任和义务帮助大家脱离苦海,虽然这只是一个美好愿景,但我确实付出了很多努力。也就是说我现在讲的这个项目,得益于那时候的理论积累和可行性的沉淀。

我们正式开始聊项目设计,他的模样是长的下面这个样子:

db44d5f94ad5cad60d3c0d79b1f212cb.png

项目的代码结构,是以JSON的形式表达,内置了很多常用的组件,特别是表单组件足以覆盖绝大部分场景。代码最终是保存在数据库中。在这个系统里管理着【菜单,环境变量,业务代码,发布回滚】等操作。

一个简单的增删改查页面代码例子

{
  baseInfo: {
    systemName: 'CRUD页面'
  },
  data: {
    list: [],
    VIPOptions: [
      { label: '普通会员', value: 1 },
      { label: '高级会员', value: 2 }
    ]
  },
  page: [
    {
      type: 'FormSearch',
      attrs: {
        labelWidth: '80px'
      },
      formList: [
        {
          type: 'input',
          label: '手机号',
          field: 'phone'
        },
        {
          type: 'input',
          label: '用户昵称',
          field: 'nickname'
        },
        {
          type: 'select',
          label: 'VIP',
          field: 'vipLevel',
          options: this.data.VIPOptions
        },
        {
          type: 'datetimerange',
          label: '注册时间',
          field: 'datetimerange',
          attrs: {
            startField: 'startTime',
            endField: 'endTime'
          }
        }
      ]
    },
    {
      type: 'button',
      label: ' + 新增',
      click: async () => {
        await this.$$dialog.open(this.$$renderModal, {
          title: '新增',
          refId: 'modal-form',
          className: 'render-modal',
          size: 'medium',
          props: {
            value: {}
          }
        });
      }
    },
    {
      type: 'button',
      label: '导出',
      click: async () => {
        // 前端导出-CSV
        let datalist = [];
        this.data.list.forEach(item => {
          let arr = [];
          arr.push(item.id);
          arr.push(item.nickname);
          arr.push(item.phone);
          datalist.push(arr);
        });
        this.$$utils.downloadCsv({
          dataList: datalist,
          headerArr: ['用户ID', '用户昵称', '手机号'],
          fileName: '用户列表' + this.$$moment().getDate().toString()
        });
      }
    },
    {
      type: 'Table',
      attrs: {
        isInitLoad: true,
        pageParamNames: ['pageNum', 'pageSize'],
        className: ['p-t-10'],
        // true和false返回的数据结构不一样
        showPagination: true,
        loadData: async params => {
          let url = SETTINGS.apiServer + '/setting/demo/user/page';
          let { data } = await this.$$axios.post(url, params);
          // 如果showPagination=false,只需要返回数组即可
          // return data.list || []
          this.data.list = data.list || [];
          return {
            list: data.list || [],
            total: data.rowCount
          };
        }
      },
      tableList: [
        {
          prop: 'id',
          label: '用户ID'
        },
        {
          prop: 'nickname',
          label: '用户昵称'
        },
        {
          prop: 'avatar',
          label: '用户头像',
          renderType: 'img',
          width: '120px'
        },
        {
          prop: 'phone',
          label: '手机号',
          class: 'link',
          click(v, data) {
            this.$router.push(`/xx/xx?id=${data.id}`);
          }
        },
        {
          prop: 'price',
          label: '价格',
          renderType: 'htlm',
          display(v) {
            return `<strong>¥${v}</strong>`;
          }
        },
        {
          prop: 'addTime',
          label: '注册时间',
          renderType: 'datetime'
        },
        {
          prop: 'vipLevel',
          label: 'VIP',
          mapList: this.data.VIPOptions
        },
        {
          prop: 'opt',
          label: '操作',
          renderType: 'opt',
          width: '100px',
          list: [
            {
              type: 'button',
              label: '编辑',
              attrs: {
                size: 'small',
                type: 'text'
              },
              click: async data => {
                this.$$dialog.open(this.$$renderModal, {
                  title: '编辑',
                  refId: 'modal-form',
                  className: 'render-modal',
                  size: 'medium',
                  vm: this,
                  props: {
                    value: JSON.parse(JSON.stringify(data))
                  }
                });
              }
            },
            {
              type: 'button',
              label: '删除',
              attrs: {
                size: 'small',
                type: 'text'
              },
              click: data => {
                this.$confirm('确定删除该记录吗?', '提示', {
                  type: 'warning'
                }).then(async () => {
                  let url = SETTINGS.apiServer + '/setting/demo/user/remove';
                  await this.$$axios.post(url, { id: data.id });
                  this.$message({ message: '操作成功', type: 'success' });
                  this.reloadTable();
                });
              },
              vif: rowData => rowData.vipLevel === 1
            }
          ]
        }
      ]
    },
    {
      id: 'modal-form',
      type: 'modal',
      attrs: {
        inline: false,
        labelWidth: '90px'
      },
      onSave: async formData => {
        let url = SETTINGS.apiServer + '/setting/demo/user/save';
        await this.$$axios.post(url, formData);
        this.$message({ message: '操作成功', type: 'success' });
        // 刷新表格数据
        this.reloadTable();
      },
      formList: [
        {
          type: 'input',
          label: '用户昵称',
          field: 'nickname',
          attrs: { maxlength: 20 },
          rules: { required: true }
        },
        {
          type: 'imageUpload',
          label: '图片上传',
          field: 'avatar',
          rules: { required: true },
          clasName: ['full-width'],
          attrs: {
            tips: '支持图片格式png/jpg/jpeg,最大500KB',
            format: ['.png', '.jpg', '.jpeg'],
            maxSize: 500
          }
        },
        {
          type: 'select',
          label: 'VIP',
          field: 'vipLevel',
          options: this.data.VIPOptions,
          rules: { required: true }
        },
        {
          type: 'input',
          label: '手机号',
          field: 'phone',
          attrs: { maxlength: 11 },
          rules: { required: true, mobile: true }
        }
      ]
    }
  ]
}

JSON表达的优点

  1. 代码规范,速度快效率高
  2. bug少
  3. 纯组件化开发,解决重复劳动

JSON表达的缺点 不灵活,交互方式单一,标准化之后就会必然产生这个结果

有人问,JSON配置如何实现复杂的交互控制?这是一个核心要解决的问题,很多人都会考虑的重点问题。我的解决方式很简单,把复杂的交互的东西都装进一个组件内完事,其实就是没有解决,我们要学会换位思考,向上思考尤为重要。在我们程序设计领域要学会一个最重要的思想:没有什么是加一层解决不了的事情。你比如设计模式,xx架构设计,都不知道加多少层了。

因此我们需要将组件简单的分类

  1. 通用组件(通用)
  2. 业务组件(特定通用)
  3. 不通用业务组件(不通用)

针对各种业务组件,JSON要保证有良好的注入机制。这样就能解决自定义的问题,真正的拥抱变化。

目前该项目还未开源,但是有一个简化版本针对表单配置的项目已经开源,这个vue-json-form是开源项目,json化配置表单,有兴趣可以看看。