Element源码之form篇

390 阅读2分钟

本篇文章介绍一下element-ui框架中的form如何在设置label宽度为auto的情况下还能对齐表单项。

  • label宽度设置为auto,那label的宽度就取决于字体本身的宽度,利用这个为出发点,我们可以获取到表单里的所有表单项的label宽度。如果label内容长度不一样,此时表单项是对不齐的,这时候我们可以获取表单项里面最宽的label长度,用这个宽度减去label宽度,设置marginLeft, 并设置类名为el-form-item__content的容器的marginLeft为最宽的label宽度,就能对齐了!
  1. 首先需要获取所有表单项的宽度
// 获取当前label宽度
    getLabelWidth() {
      if (this.$el && this.$el.firstElementChild) {
        // 获取label宽度
        const computedWidth = window.getComputedStyle(this.$el.firstElementChild).width;
        return Math.ceil(parseFloat(computedWidth));
      }
      return 0;
    },
  1. 问题来了,那在什么时候获取表单项宽度呢 我们知道只有dom元素渲染出来了才可以获取到元素宽度。所以在label组件中的mounted生命周期中进行表单项宽度的获取。
  mounted() {
    this.updateLabelWidth('update');
  },
  
  // 更新label宽度
    updateLabelWidth(action = 'update') {
      if (this.$slots.default && this.isAutoWidth && this.$el.firstElementChild) {
        if (action === 'update') {
          this.computedWidth = this.getLabelWidth();
        } else if (action === 'remove') {
          this.elForm.deregisterLabelWidth(this.computedWidth);
        }
      }
    },
  1. 我们注意到获取的label宽度赋值给computedWidth属性,如果我们每次获取label宽度的时候就用一个数组push进去,每次数组发生变化时我们就可以计算出数组中最宽的label宽度。
watch: {
    computedWidth(val, oldVal) {
      if (this.updateAll) {
        // 注册label宽度
        this.elForm.registerLabelWidth(val, oldVal);
        // 更新计算的label宽度
        this.elFormItem.updateComputedLabelWidth(val, oldVal);
      }
    },
  },
  
   // 在form.vue文件中注册label宽度
    registerLabelWidth(val, oldVal) {
      if (val && oldVal) {
        const index = this.getLabelWidthIndex(oldVal);
        this.potentialLabelWidthArr.splice(index, 1, val);
      } else if (val) {
        this.potentialLabelWidthArr.push(val);
      }
    },
    
    // 在 form-item.vue 文件中
     // 更新计算的label宽度
    updateComputedLabelWidth(width) {
      this.computedLabelWidth = width ? `${width}px` : '';
    },
  1. label宽度数组已经获取到了,接下来就需要计算label宽度数组(potentialLabelWidthArr)中的最大值
form.vue
computed: {
    // 自动label宽度
    autoLabelWidth() {
      if (!this.potentialLabelWidthArr.length) return 0;
      const max = Math.max(...this.potentialLabelWidthArr);
      return max ? `${max}px` : '';
    },
  },
  1. 好了,数据齐全了,接下来需要处理label元素和类名为el-form-item__content的容器的样式了。
// 这里渲染label的内容
render() {
    const slots = this.$slots.default;
    if (!slots) return null;
    if (this.isAutoWidth) {
      const autoLabelWidth = this.elForm.autoLabelWidth;
      const style = {};
      if (autoLabelWidth && autoLabelWidth !== 'auto') {
        // 关键在这行,label的marginLeft = 最大宽度 - label宽度
        const marginLeft = parseInt(autoLabelWidth, 10) - this.computedWidth;
        if (marginLeft) {
          style.marginLeft = marginLeft + 'px';
        }
      }
      return (<div class="el-form-item__label-wrap" style={style}>
        { slots }
      </div>);
    } else {
      return slots[0];
    }
  },
  
  // 这是类名为el-form-item__content的容器的样式
  contentStyle() {
      const ret = {};
      const { label } = this;
      if (this.form.labelPosition === 'top' || this.form.inline) return ret;
      if (!label && !this.labelWidth && this.isNested) return ret;
      const labelWidth = this.labelWidth || this.form.labelWidth;
      // 设置marginleft
      if (labelWidth === 'auto') {
        if (this.labelWidth === 'auto') {
          // 如果当前表单项设置了labelWidth
          // 则设置content的marginlft为labelWidth
          ret.marginLeft = this.computedLabelWidth;
        } else if (this.form.labelWidth === 'auto') {
          console.log('计算content marginLeft');
          // 如果form对象的labelWidth 属性设置了auto,则设置它的marginLeft为label宽度数组的最大宽度(是个整数)
          ret.marginLeft = this.elForm.autoLabelWidth;
        }
      } else {
        // 如果没有设置为auto, 则取为当前设置的labelwidth
        ret.marginLeft = labelWidth;
      }
      return ret;
    },

总结:根据组件的单一性原则,label组件中获取到了label宽度,获取label宽度的时候进行监听,并在form组件中注册label长度,以及更新form-item组件中的label宽度(这个用于form-item的labelWidth属性设置为auto),由于label长度数组存储于form组件中,所以可以在这个组件中计算出label数组最大值,并在label组件中使用。