手写Vue表单组件

test-form.vue

<template>
  <el-form
    :model="ruleForm"
    :rules="rules"
    ref="ruleForm"
  >
    <el-form-item label="用户名" prop="username">
      <el-input type="username" v-model="ruleForm.username" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="密码" prop="pass">
      <el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>
    </el-form-item>

    <el-form-item>
      <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
      <el-button @click="resetForm('ruleForm')">重置</el-button>
    </el-form-item>
  </el-form>
</template>
<script>
import elForm from "../components/el-form.vue";
import elFormItem from "../components/el-form-item.vue";
import elInput from "../components/el-input.vue";

export default {
  components: {
    "el-form": elForm,
    "el-input": elInput,
    "el-form-item": elFormItem
  },
  data() {
    return {
      ruleForm: {
        pass: "",
        username: ""
      },
      rules: {
        username: [
          { required: true, message: "用户名不能为空", trigger: "blur" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" }
        ],
        pass: [
          { required: true, message: "密码不能为空", trigger: "blur" },
          { min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" }
        ]
      }
    };
  },
  methods: {
    submitForm(formName) {
      this.$refs[formName].validate(valid => {
        if (valid) {
          alert("submit!");
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    },
    resetForm(formName) {
      this.$refs[formName].resetFields();
    }
  }
};
</script>

el-form.vue

  • slot 中插入组件
  • provide 出自己,供子孙组件获取当前组件
  • 接收 校验规则rules,表单数据model
  • 提供表单校验函数。对所有子组件进行校验
<template>
  <form @submit.prevent>
    <slot></slot>
  </form>
</template>

<script>
export default {
  name: "elForm",
  provide() {
    return { elForm: this };
  },
  props: {
    rules: {
      type: Object,
      default: () => ({}) // 保证数据不被共享和data一样
    },
    model: {
      type: Object,
      default: () => ({}) // 保证数据不被共享和data一样
    }
  },
  methods: {
    resetFields() {
      // .async v-model  找到输入框清理
    },
    async validate(cb) {
      let children = this.$broadcast("elFormItem"); // 获取所有子元素
      try {
        await Promise.all(children.map(child => child.validate()));
        cb(true);
      } catch {
        cb(false);
      }
    }
  },
  mounted() {
    console.log(this);
  }
};
</script>

 

el-form-item.vue

  • 注入 elForm,拿到当前el-form-item组件传入的v-model, this.elForm.model[this.prop] , 校验规则 this.elForm.rules[this.prop] 
  • $on 监听当子组件 el-input 值变化时进行值校验
<template>
  <div>
    <label v-if="label">{{label}}</label>
    <slot></slot>
    <span v-if="errorMessage">{{errorMessage}}</span>
  </div>
</template>

<script>
import Schema from "async-validator";
export default {
  name: "elFormItem",
  inject: ["elForm"], 
  data() {
    return { errorMessage: null };
  },
  props: {
    label: {
      type: String
    },
    prop: {
      type: String
    }
  },
  methods: {
    validate() {
      if (this.prop) {
        let value = this.elForm.model[this.prop];
        let ruleValue = this.elForm.rules[this.prop];
        let schema = new Schema({
          [this.prop]: ruleValue // username:rule
        });

        return schema.validate({ [this.prop]: value }, (err) => {
          if (err) {
            this.errorMessage = err[0].message;
          } else {
            this.errorMessage = null;
          }
        });
      }
    }
  },
  mounted() {
    this.$on("validate", () => {
      this.validate();
    });
  }
};
</script>

el-input.vue

  • 实时监听当前input框的值,并 emit 当前值给父组件,v-model 语法糖
  • 实时监听值变化,进行校验
<template>
  <input type="text" :value="value" @input="handleInput" />
</template>

<script>
export default {
  name: "elInput",
  props: {
    value: {
      type: String
    }
  },
  methods: {
    handleInput(e) {
      this.$emit("input", e.target.value);
      this.$dispatch("elFormItem", "validate");
    }
  }
};
</script>

 

  • $broadcast 函数:向下寻找子组件,存储componentName的子组件,或者让子组件 emit事件
  • $dispatch 函数:向上寻找指定父组件,并向父组件抛出事件
Vue.prototype.$broadcast = function (componentName, eventName) {
  let children = this.$children;
  let arr = [];
  function findFormItem(children) {
    children.forEach(child => {
      if (child.$options.name === componentName) {
        if (eventName) {
          arr.push(child.$emit('eventName'))
        } else {
          arr.push(child)
        }
      }
      if (child.$children) {
        findFormItem(child.$children);
      }
    });
  }
  findFormItem(children);
  return arr;
}

Vue.prototype.$dispatch = function (componentName, eventName) {
  let parent = this.$parent;
  while (parent) {
    let name = parent.$options.name;
    if (name == componentName) {
      break;
    } else {
      parent = parent.$parent;
    }
  }
  if (parent) {
    if (eventName) {
      return parent.$emit(eventName)
    }
    return parent
  }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值