Introduction: Common form component (form form) is almost very common in each major project, so how do we not rely on the UI framework to implement one? Collect data, verify data and submit data. This article records every step of implementation and optimization in detail.

Technical support required

  • Dispatch (the componentName)
  • provide/inject
  • async-validator
  • $attrs
  • mixins

Usage and Analysis

Form is used in element

Examples of our custom usage:

<SwForm :model='userInfo' :rules="rules" ref=' SwForm '> <SwFormItem label=" "Prop =' ACC '> <SwFormInput V-model =" userinfo.acc" placeholder=" Please input user name "></SwFormInput> </SwFormItem> <SwFormItem Label = "password: "Prop =' PWD '> <SwFormInput type="password" V-model =" userinfo.pwd" placeholder=" please input username "> </button> </SwFormItem> </SwForm>Copy the code
Data () {return {userInfo: {acc: "", PWD: "",}, rules: {acc: [{required: true, message: "User name must not be less than 5 digits ", min: 5}], PWD: [{required: true, message: "Password no less than 5 digits but no more than 10 digits ", min: 5, Max: 10,},],},}; },Copy the code

Demand analysis

  • First of all, there is the input box input, which is just for input information, and there are properties like Type and placeholder, and v-Model two-way binding where it is used.
  • There should be formItem, label, prop properties, checksum, error messages
  • And then there’s the form, which is used for global validation

SwFormInput

Create a components/form folder and create swForMinput. vue and index.vue files.

Index. Vue file:

<template> <div> <SwFormInput V-model =" userinfo.acc "placeholder=" please input user name" ></SwFormInput> <! Acc}}</p> </div> </template> <script> import SwFormInput from "./SwFormInput"; export default { components: { SwFormInput }, data() { return { userInfo: { acc: "", }, rules: { acc: [ { required: True, message: "User name not less than 5 digits but not more than 10 digits ", min: 5, Max: 10,},],},}; }}; </script>Copy the code

SwFormInput. Vue file:

<template> <div> <template> <! Since different inputs may pass different attributes, <input type="text" @input="input" V-bind ="$attrs" /> </template> </div> </template> <script> export Default {methods: {input() {// Implement bidirectional binding: 1.:value 2. $emit("input", event.target.value); ,}}}; </script>Copy the code

And now we see that the divs in the SwFormInput file also have placeholder properties, and that’s called inheritance of properties. Add an attribute to the script that inheritAttrs is set to false to turn off attribute inheritance and avoid being set to the root element.

SwFormItem

Create swFormItem. vue file, SwFormItem file should receive label, prop properties, and check, error messages.

<template> <div> <! - the label tag - > < label v - if = "label" > {{label}} < / label > <! -- Slot: position of SwFormInput --> <slot></slot> <! --> <p v-if="error">{{error}}</p> </div> </template> <script> export default {data() {return {error: ", // the validation failure message should be the component's own state, so let the component maintain it; }, props: { label: { type: String, default: "", }, prop: { type: String, default: "", }, }, }; </script>Copy the code

Introduce SwFormItem in index.vue and use it.

<SwFormItem label=' username 'prop='acc'> <SwFormInput V-model ="userInfo. Acc "placeholder=" please input username" > </SwFormItem> import SwFormItem from "./SwFormItem"; components: { SwFormInput, SwFormItem },Copy the code

At this point, can we run a test? Think about the values of the input field, and the validation rules, are they available now in the SwFormItem component? I don’t think so. In index.vue, we pass all the values and rules, but the syntax is different, and we take all the values and rules from each SwFormItem. We passed a prop property for what? With these doubts, let’s do SwForm again.

SwForm

Create swForm. vue and accept the model and rules attributes in SwForm.

<template> <div> <! <slot></slot> </div> </template> <script> export default {props: {model: Type: Object, require: true,}, rules: {type: Object, require: true,},}; </script>Copy the code

The same is introduced and used in index.vue.

<SwForm :model="userInfo" :rules="rules"> <SwFormItem label=" user name "prop="acc"> <SwFormInput V-model =" userinfo.acc" Placeholder =" please input username "></SwFormInput> </SwFormItem> </SwForm> import SwForm from "./SwForm"; components: { SwFormInput, SwFormItem, SwForm },Copy the code

What else do we do at this point? We use the component instance as the provider, and the child components can easily get the data and validation rules. Provide /inject values between ancestors and descendants. When we do not use Vuex, VUE provides us with this native interface to realize value transmission across generations.

Data return {form: this, // provide the component instance itself to descendant components}; },Copy the code

SwForm provides itself to descendant components as a provider, and we inject and retrieve it in SwFormItem.

New to swformItem. vue:

Inject: ['form'], // Inject the required attributesCopy the code

validation

In SwFormInput, listen for the change event

<! Notify validation every time a value changes and loses focus. <input type="text" @input="input" v-bind="$attrs" @change="change" /> <input type="text" @input="input" v-bind="$attrs" @change="change" />Copy the code

Element’s official practice address

Function Broadcast (componentName, eventName, Params) {// componentName: componentName // eventName: componentName Event name // params: argument, which needs to be an array // Traverses all the sub-components: $children. ForEach (Child => {var name = child.new ()) ponentName; ComponentName (componentName, componentName, componentName); If (name === = componentName) {child.$emit. Apply (child, $emit. [eventName].concat(params)); } else { broadcast.apply(child, [componentName, eventName].concat([params])); }}); } export default { methods: {/ / from bottom to top distributing events (bubble) similar to dispatch (the componentName, eventName, params) {var parent = this. $parent | | this. $root; var name = parent.$options.componentName; // Look up until you find a component with the same name as the passed componentName while (parent && (! name || name ! == componentName)) { parent = parent.$parent; if (parent) { name = parent.$options.componentName; If (parent) {parent.$emit. Apply (parent, [eventName].concat(params)); } }, broadcast(componentName, eventName, params) { broadcast.call(this, componentName, eventName, params); }}};Copy the code

SRC create a new mixins file, add emitters. Js, and copy the above code into it.

SwFormInput:

<script> import emitter from "@/mixins/emitter.js"; Export default {mixins: [Emitter], inheritAttrs: false, methods: {input() {// $emit("input", event.target.value); }, change() {// this.dispatch: parameter 1: componentName parameter 2: // Write componentName in the component to be triggered, ComponentName :'SwFormItem', this.dispatch('SwFormItem', 'validate'); }}}; </script>Copy the code

Add to the script of SwFormItem:

mounted() { this.$on("validate", () => { this.validate(); }); }, methods: { validate() { console.log("validate"); }},Copy the code

The test is successful. Once that’s ok, we start to verify.

In the validate method, we need to verify: first, how to get values and rules; Second: how to verify with rules and values.

So let’s look at the first step, how to get the values and the rules. We pass and receive the form instance via provide/inject, where we can use the value.

validate() {
    const rule = this.form.rules[this.prop];
    const value = this.form.model[this.prop];
},
Copy the code

With the values and rules in hand, we can proceed to the next step: validation.

Use the plugin Async-Validator.

Installation:

npm i async-validator -D
Copy the code

Use:

import Schema from "async-validator";
Copy the code
// If ($on("validate",) {// If ($on("validate",)) { Console.log () => {this.validate().catch() => {console.log(); }); }); }, methods: {validate() {const value = this.form.model[this.prop];}, methods: {validate() {const value = this.form.model[this.prop]; Const rule = this.form.rules[this.prop]; // Const rule = this.form.rules[this.prop]; Const validator = new Schema({[this.prop]: rule}); const validator = new Schema({[this.prop]: rule}); Return new Promise((resolve, reject) => {// validate: Validator. Validate ({[this.prop]: validator ({[this.prop]); Value}, (err) => {if (err) {this.error = err[0]. Message; reject(err[0].message); } else {this.error = ""; resolve(); }}); }); }},Copy the code

Global validation

Add a button to index.vue:

<SwForm :model="userInfo" :rules="rules" ref="swform">
  <SwFormItem label="用户名" prop="acc">
    <SwFormInput
      v-model="userInfo.acc"
      placeholder="请输入用户名"
    ></SwFormInput>
  </SwFormItem>
  <SwFormItem>
    <button @click="login">登录</button>
  </SwFormItem>
</SwForm>
Copy the code
methods: {login() {// Find the SwForm instance and call its validate method, This.$refs.swform.validate((result) => {if (result) {alert("suc"); } else { alert("err"); }}); }},Copy the code

SwForm. Vue of:

methods: {validate(callback) {// Run through all sons with prop attributes, Then execute their validate method -> the method returns a promise -> into an array of validates const validates = this.$children.filter ((item) => item.prop) .map((item) => item.validate()); Then (() => callback(true)). Catch (() => callback(false)); then(() => callback(true)). }},Copy the code

At this point, we have completed the custom implementation of Element’s form form.

To optimize the

In the previous swform. vue: validate method, this.$children obtains and iterates through the prop property, and then executes their validate method to retrieve all the child components recursively, which can affect performance. The official approach is: for each SwFormItem instance that needs to be checked, pass itself to SwForm, and then all SwFormItem instances that need to be checked can be fetched from SwForm.

Swformitem. vue:

import emitter from '@/mixins/emitter'; mixins: [Emitter], mounted() {this.$on("validate", Console.log () => {this.validate().catch() => {console.log(); }); }); If (this.prop) {// If (this.prop) {// If (prop) { Dispatch ('SwForm', 'formItenField', [this])}},Copy the code

SwForm:

ComponentName: 'SwForm', data() {return {field: [], // to store all SwFormItem components}; }, created() { this.$on("formItenField", (item) => { this.field.push(item); }); }, validate(callback) { const validates = this.field.map((item) => item.validate()); Promise.all(validates) .then(() => callback(true)) .catch(() => callback(false)); },Copy the code

style

The style will fine tune itself

The code address