vue3造轮子之自定义form

391 阅读1分钟

大家在使用vue库的时候,有没有想过他们是怎么实现的,有没有想过如何实现一个! 先看效果 image.png

这篇文章我们会介绍一下内容

  • 父子组件
  • 插槽的用法-slot
  • 跨组件传值-provide
  • 跨组件通信--mmit
  • 自定义props
  • 自定义事件

贴代码


<template>
  <div class="container">
    {{ formState }}
    <yue-ma-edu-form :rules="rules" @finish="finish" :model="formState">
      <yue-ma-edu-form-item label="用户名" name="username">
        <yue-ma-edu-input
          v-model:value="formState.username"
          placeholder="跃码教育"
        />
      </yue-ma-edu-form-item>
      <yue-ma-edu-form-item label="描述" name="description">
        <yue-ma-edu-input
          type="textarea"
          v-model:value="formState.description"
          placeholder="跃码教育"
        />
      </yue-ma-edu-form-item>
      <yue-ma-edu-form-item>
        <yue-ma-edu-button> 保存 </yue-ma-edu-button>
      </yue-ma-edu-form-item>
    </yue-ma-edu-form>
  </div>
</template>

戳这里,观看视频教程

首先我们定义一个父级容器存放我们所有的组件

<template>
  <form :class="{ 'was-validated': validated }" @submit.prevent="submit">
    <slot />
  </form>
</template>

定义父级容器的props

···

props: { model: { type: Object, }, rules: { type: Object, }, }

···

定义父级容器的方法

  emits: ["finish"],

定义父级容器的校验方法


setup(props, ctx) {
    const formMapKey = {};
    for (let k of Object.keys(props.rules)) {
      formMapKey[k] = true;
    }
    const formState = reactive(formMapKey);
    // formState.username = false;
    const validated = computed(() => {
      let res = true;
      for (let k in formMapKey) {
        res = res && formMapKey[k];
      }
      return res;
    });

    const validate = (k) => {
      let rules = null;
      let model = null;
      if (k === "") {
        rules = props.rules;
        model = props.model;
      } else {
        rules = { [k]: props.rules[k] };
        model = { [k]: props.model[k] };
      }
      const validator = new Schema(rules);
      const handleErrors = (errors, fields) => {
        VueEvent.emit("validate", fields);
        const keySet = new Set(Object.keys(fields));
        for (let k in formState) {
          if (keySet.has(k)) {
            formState[k] = false;
          } else {
            formState[k] = true;
          }
        }
      };
      validator
        .validate(model)
        .then(() => {
          // 课堂作业,找出我的bug修改

          // validation passed or without error message
          VueEvent.emit("validate", { [k]: [] });
        })
        .catch(({ errors, fields }) => {
          return handleErrors(errors, fields);
        });
    };
    const submit = (e) => {
      validate("");
      ctx.emit("finish");
    };
    onMounted(() => {
      VueEvent.on("check", (k) => {
        validate(k);
      });
    });
    return {
      submit,
      validated,
    };
  },

定义yue-ma-edu-form-item用于承载输入组件


<template>
  <div class="mb-3">
    <label :for="name" class="form-label">{{ label }}</label>
    <slot />
  </div>
</template>
<script lang="ts">
import { defineComponent, provide } from "vue";
export default defineComponent({
  props: {
    label: {
      type: String,
    },
    name: {
      type: String,
    },
  },
  setup(props) {
    provide("name", props.name);
  },
});
</script>


通过provider跨组件传输数据


    provide("name", props.name);


自定义输入组件 yue-ma-edu-input


<template>
  <template v-if="type === 'textarea'">
    <textarea
      :class="{ 'form-control': true, 'is-invalid': invalid }"
      :id="name"
      :name="name"
      :value="value"
      :placeholder="placeholder"
      @input="inputHandler"
    ></textarea>
  </template>
  <template v-if="type === 'text' || type === 'password'">
    <input
      :class="{ 'form-control': true, 'is-invalid': invalid }"
      :type="type"
      :id="name"
      :name="name"
      :value="value"
      :placeholder="placeholder"
      @input="inputHandler"
    />
  </template>

  <div class="invalid-feedback">{{ msg }}</div>
</template>
<script lang="ts">
import { computed, defineComponent, inject, onMounted, ref } from "vue";
import VueEvent from "./VueEvent";

export default defineComponent({
  props: {
    placeholder: {
      type: String,
      default: "跃码教育提示您:请输入内容",
    },
    value: {
      type: String,
    },
    type: {
      type: String,
      default: "text",
    },
  },
  setup(props, ctx) {
    // inject 第二个参数是默认值
    const name = inject("name");
    const msg = ref("");
    // 计算出来一个变量
    const invalid = computed(() => msg.value.length > 0);
    onMounted(() => {
      VueEvent.on("validate", (res) => {
        if (res[name] !== undefined) {
          if (res[name].length > 0) {
            const error = res[name].pop();
            msg.value = error.message;
          } else {
            msg.value = "";
          }
        }
      });
    });
    // TODO 拿到了数据,我就可以在这里进行校验我的数据
    const inputHandler = (e: Event) => {
      ctx.emit("update:value", e.target.value);
      VueEvent.emit("check", name);
    };
    return {
      name,
      inputHandler,
      msg,
      invalid,
    };
  },
});
</script>


通过 const name = inject("name");获取父级组件传输的数据

监听跨组件事件


      VueEvent.on("validate", (res) => {
        if (res[name] !== undefined) {
          if (res[name].length > 0) {
            const error = res[name].pop();
            msg.value = error.message;
          } else {
            msg.value = "";
          }
        }
      });