面试Vue架构师,封装一个万能表单生成器

8,144 阅读4分钟

需求与分析

日常开发中,我们前端程序员可能需要开发大量类似的表单页面,就好像下面这些

或者这样的

作为初级前端,此时只能反复的通过Ctrl+C、V,俗称CV大法,然后改吧改吧,来完成需求。

但是这样仅仅能应付我们交付的压力,对于后期的维护、新页面的开发仍旧需要大量的时间去完成重复的工作。同时,最难堪的莫过于code review的时候,这种大量CV的代码只能成为团队集体诟病的典型例子。😂

如何解决

因此,在日常开发解决业务需求的同时,考虑如何提高工作的效率是程序员很重要的能力,我们需要设计一套能帮助咱们日常快速产出表单的工具,这样能大大提高工作效率。换言之,原本2天的工作量,仅需要半天完成,那剩下的1.5天不就可以摸[xué]鱼[xí]了吗😍

着手设计

开始设计吧!👍首先,我们先得分析自身的需求:

  1. 要能生成不同类型的表单元素
  2. 通过简单的配置可以定位或者栅格布局元素
  3. 能支持表单提交后的回调函数获取数据
  4. 更易维护核心代码编写方式【因为任何工具都不可能一次设计完美,所以未来可能需要修改】

设计流程图

为了保障我们不做重复事,不返工,最好先滤清思路,这里我用工具制作一张流程图。

😎怎么样,有图思路是不是清晰很多?建议小伙伴也都能养成这样的好习惯,需要这方面工具的可以联系我。

开始coding

数据结构

我们先将配置的数据结构定义成如下:

export const form = Object.freeze({
    title'注册用户',
    items: [
        [
            { label:'用户名',type'input', name'username', col2,value:123 },
        ],
        [
            { label:'性别',type'select', name'gender', x:10,y:100, options: [
                { label'男', value1 },
                { label'女', value2 }
            ] },
        ]
    ]

});

     其中items代表多行的表单元素,其中每个数组都代表一行。col代表当前元素需要跨越的栅格列数,name则为submit之后e元素的key。另外我们对每个表单的配置单独提取出来,作为一个页面的config.js,这样做是将结构、表单、业务逻辑进一步分离哦。另外Object.freeze是可以冻结对象,对于不需要改变的对象使用,可以提升性能哦

传递组件及组件接收的处理
<my-form @submit="onSubmit" :conf="form"  />

我们在需要的页面将配置引入,并声明成data中的form属性,现在将其传递进去。接下来处理接收的万能表单组件啦

export default {
  props: {
    conf: {
      typeObject,
      default() => ({}),
    },
  },
  data() {
    return {
      form: {},
    };
  },
  
  }

说明:

  1. conf对象为外部接收配置
  2. form对象为内部处理数据对象
难点处理

由于外部定义的name属性需要与表单元素绑定并实现响应式,所以这些属性,我们需要给内部form对象动态添加的属性,执行一下$set方法。

created() {
    this.conf.items.forEach((row) => {
      row.forEach((obj) => {
        this.$set(this.form, obj.name, obj.value);
      });
    });
  },

看懂了吗?说白了就是动态给的name作为key给form对象使用

渲染结构

我们先来看看大致结构:

 <div class="form-container">
        <h1>{this.conf.title}</h1>
        <el-form ref="form" props={{ model: this.form }} label-width="80px">
          {this.renderRows()}  {/* 动态渲染行列,内部渲染元素*/}

          <div style={DIRECTIONS[this.direction]}> {/* 动态按钮的方位*/}
            <el-form-item>
              <el-button type="primary" 
                onClick={(e) => this.$emit('submit',this.form)}>
                立即创建				{/* ⬆触发事件传递数据(暂未加验证)*/}
              </el-button>
              <el-button>取消</el-button>
            </el-form-item>
          </div>
        </el-form>
      </div>

渲染元素

我们再来看看使用jsx渲染的行和列,如何处理细节:

renderRows() {
      return this.conf.items.map((row) => {
        return <el-row>{this.renderColumn(row)}</el-row>;
      });
    },
 renderColumn(row) {
      return row.map((element) => {
        let styles = !element.col
          ? [
              { position"absolute" },
              { left: element.x + "1px" },
              { top: element.y + "px" },
            ]
          : null;
        return (
          <div class="row-container" style={styles}>
            <el-form-item label={element.label}>
              <el-col span={element.col}>{this.renderItem(element)}</el-col>
            </el-form-item>
          </div>
        );
      });
    },

这里可以看到,我们用是否跨列来判断,不传递则是绝对定位,去除其x、y,来给元素定义定位的样式,之前传递的label就是在这里使用啦。

渲染表单元素

renderItem(element) {
      switch (element.type) {
        case "input":
          return (
            <el-input
              onInput={(e) => (this.form[element.name] = e)}
              value={this.form[element.name]}
            ></el-input>
          );
        case "select":
          return (
            <el-select
              onChange={(e) => (this.form[element.name] = e)}
              value={this.form[element.name]}
              placeholder="请选择活动区域"
            >
              {element.options.map((e) => {
                return <el-option label={e.label} value={e.value}></el-option>;
              })}
            </el-select>
          );
      }
    },

这里的难点就是大家要对于【对象.[属性名变量]】的动态取值方法有一定的认识,另外如果你对jsx不太熟悉的话,可能也会有困扰。如果你用纯Vue的方式能先写出来,那也是ok的。

无所谓啦,jsx可以认为是一个更利于维护的编码方式,重要理解本课程思想。

最终效果

初始效果:

 

输入值

 

回车查看控制台

总结

本例可以帮助开发者,对于表单页开发,提供更为快速的配置生成方式,但目前只提供了基本的功能,一些其他细节上的功能还需要完善。也欢迎同学们与我交流、探讨功能的完善与技术的优化。