自定义表单组件,让它可以使用 ElementUI 中 el-form 提供的上下文和验证功能

3,723 阅读1分钟

让 el-form 控制自定义表单组件的状态

使用过 ElementUI 的表单组件的都知道,在 el-form 中设置属性 disabled 为 true 时,整个表单都会被 disabled 掉,那么想要自定义的组件也能跟随 el-form 的设置而变化,该怎么做呢?

我们看一下 el-input 组件的源码,发现有这一段代码: 微信图片_20230510143515.png

可以看到除了 el-input 组件自身 props 提供的 disabled 参数以外,还有来自 this.elForm 的 disabled 的值。而它是通过 Vue 的依赖注入分别在 el-form 和 el-form-item 组件中 inject 进来的: 2.png

所以,为了自定义的组件也可以被 el-form 组件的状态所控制,实现的自定义的组件如下所示:

<template>
  <div class="custom-input">
    <input v-model="modelValue" type="text" :disabled="inputDisabled" />
  </div>
</template>

<script>
export default {
  name: 'CustomInput',
  model: { prop: 'value', event: 'change' },
  props: {
    value: { type: String, default: '' },
    disabled: { type: Boolean, default: false }
  },
  inject: {
    elForm: {
      default: ''
    },
    elFormItem: {
      default: ''
    }
  },
  computed: {
    modelValue: {
      get() {
        return this.value
      },
      set(val) {
        this.$emit('change', val)
      }
    },
    inputDisabled() {
      return this.disabled || (this.elForm || {}).disabled
    }
  }
}
</script>

把这个组件与 el-input 组件一起对比来使用:

<template>
  <div class="container">
    <el-form :model="form" :disabled="formDisabled" label-width="auto">
      <el-form-item label="el-input" prop="text1">
        <el-input v-model="form.text1" />
      </el-form-item>
      <el-form-item label="custom-input" prop="text2">
        <custom-input v-model="form.text2" />
      </el-form-item>
    </el-form>
    <el-button type="primary" @click="formDisabled = !formDisabled">{{ formDisabled ? '取消' : '禁用' }}</el-button>
  </div>
</template>

<script>
import CustomInput from './custom-input.vue'

export default {
  name: 'Test',
  components: { CustomInput },
  data() {
    return {
      form: { text1: '', text2: '' },
      formDisabled: false
    }
  }
}
</script>

<style lang="scss" scoped>
.container {
  width: 500px;
  margin: 30px auto;
}
</style>

效果如下所示:

4.gif

实现自定义组件的验证功能

接下来,来看看怎么实现自定义组件的验证功能。在 el-input 组件源码中,有如下实现: 5.png

6.png

在 el-input 组件源码中没有找到 dispatch 函数的定义,但是可以找到如下代码: 7.png

在 emitter 文件中找到了 dispatch 函数的定义,由此可知,它是通过 mixin 混入进来的。作用是为了不断往上查找父组件,直到父组件为指定 componentName 为止,在这里父组件为 el-form-item。找到之后就触发相应的 eventName 事件,并将参数传入执行,该事件触发后,就会被 el-form-item 组件捕获,由此触发相关验证。其代码如下: 8.png

再结合在 el-form-item 的组件源码: 9.png

由此可知,想要使用 ElementUI 中 el-form 提供的验证功能,自定义组件需要首先引入 emitter 文件,然后在组件的 change 和 blur 事件中,使用 emitter 提供的 dispatch 方法,并通过规定的格式传入相应的参数即可。完整实现代码如下:

<template>
  <div class="custom-input">
    <input v-model="modelValue" type="text" :disabled="inputDisabled" @blur="onInputBlur" />
  </div>
</template>

<script>
import emitter from 'element-ui/lib/mixins/emitter'

export default {
  name: 'CustomInput',
  mixins: [emitter],
  model: { prop: 'value', event: 'change' },
  props: {
    value: { type: String, default: '' },
    disabled: { type: Boolean, default: false }
  },
  inject: {
    elForm: {
      default: ''
    },
    elFormItem: {
      default: ''
    }
  },
  computed: {
    modelValue: {
      get() {
        return this.value
      },
      set(val) {
        this.dispatch('ElFormItem', 'el.form.change', [val])
        this.$emit('change', val)
      }
    },
    inputDisabled() {
      return this.disabled || (this.elForm || {}).disabled
    }
  },
  methods: {
    onInputBlur() {
      this.dispatch('ElFormItem', 'el.form.blur', [this.modelValue])
    }
  }
}
</script>

<style lang="scss">
// 设置验证不通过的样式
.el-form-item.is-error {
  .custom-input input {
    outline-color: #f56c6c;
    border-color: #f56c6c;
  }
}
</style>

将这个自定义组件引入,并与 el-input 组件一起对比使用如下:

<template>
  <div class="container">
    <el-form ref="formRef" :model="form" :rules="formRules" :disabled="formDisabled" label-width="auto">
      <el-form-item label="el-input" prop="text1">
        <el-input v-model="form.text1" />
      </el-form-item>
      <el-form-item label="custom-input" prop="text2">
        <custom-input v-model="form.text2" />
      </el-form-item>
      <el-form-item>
        <el-button type="danger" @click="onValidateForm">验 证</el-button>
      </el-form-item>
    </el-form>
    <el-button type="primary" @click="formDisabled = !formDisabled">{{ formDisabled ? '取消' : '禁用' }}</el-button>
  </div>
</template>

<script>
import CustomInput from './custom-input.vue'

export default {
  name: 'Test',
  components: { CustomInput },
  data() {
    const validate = (rule, val, cb) => (val.length < 6 ? cb(new Error('长度不能小于六位!')) : cb())
    return {
      form: { text1: '', text2: '' },
      formDisabled: false,
      formRules: {
        text1: [
          { required: true, message: '请输入', trigger: 'blur' },
          { validator: validate, trigger: 'change' }
        ],
        text2: [
          { required: true, message: '请输入', trigger: 'blur' },
          { validator: validate, trigger: 'change' }
        ]
      }
    }
  },
  methods: {
    onValidateForm() {
      const { formRef } = this.$refs
      if (formRef) formRef.validate()
    }
  }
}
</script>

<style lang="scss" scoped>
.container {
  width: 500px;
  margin: 30px auto;
}
</style>

效果如下所示:

10.gif