This article introduces the encapsulation of form components from 0 to 1, using TypeScript + Vue3.0 technology. If you are not familiar with these two blog posts, check them outJuejin. Cn/post / 690313… 和 Juejin. Cn/post / 690818…
The result:
bootstrap
Let’s go straight to the elegant styles of Bootstrap. First install bootstrap in the project installed via vue-CLI. Vue-cli initialization project method juejin.cn/post/690818… There is an introduction, which will not be repeated here.
npm install bootstrap@next --save
Copy the code
vetur
In order to use syntax hints in the template template of the vue file, we need to modify the settings.json configuration of the vetur. New “vetur. Experimental. TemplateInterpolationService” : true
V – model principle
Vue2.0
In Vue2.0, for tags with input events (including <input>, <select>, or <textarea>), we can directly use the v-model for two-way data binding, that is, when the user modifies the content of the form, The data bound by the V-Model can be modified automatically.
<template>
<div>
<input v-model="msg"/>
<p>{{msg}}</p>
<button @click="change">change</button>
</div>
</template>
<script>
export default {
name: "count".data(){
return {
msg: ""}},methods: {
change(){
this.msg = "Hello Vue"; }}}</script>
Copy the code
The V-Model is essentially a syntactical sugar that listens for user input events to update data and does some special processing for extreme scenarios. Internally, the V-Model uses different properties for different input elements and throws different events:
- Text and Textarea elements use value property and input events;
- Checkbox and radio use checked Property and change events;
- The SELECT field takes value as prop and change as an event.
Translation:
<input v-model="msg"/>/ / equivalent to the<input :value="msg" @input="msg=$event.target.value"/>
Copy the code
<input type="checkbox" value="Java" id="Java" v-model="msg">/ / equivalent to the<input type="checkbox" value="Java" id="Java" :checked="msg" @change="changeInput">// The changeInput function is handled differently according to the type of MSGCopy the code
<select v-model="msg">
<option value="dog">dog</option>
<option value="cat">cat</option>
<option value="Hello Vue">Hello Vue</option>
</select>/ / equivalent.<select :value="msg" @change="changeSelect">
<option value="dog">dog</option>
<option value="cat">cat</option>
<option value="Hello Vue">Hello Vue</option>
</select>
Copy the code
Now that we know the nature of the V-Model, we can define the V-Model for our custom components. As follows:
<myComponent v-model="msg"></myComponent>/ / equivalent to the<myComponent :value="msg" @input="msg=arguments[0]"></myComponent>
Copy the code
In myComponent, a variable is usually defined to receive changes in value; And modifying this variable when going to $emit triggers the input event that the parent component subscribles to.
<template>
<div>
{{newVal}}
<button @click="change">Click on the</button>
</div>
</template>
<script>
export default {
name: "my-component".props: {
value: {
type: String.default: "Vue"}},watch: {
value(val){
this.newVal = val; }},data(){
return {
newVal: this.value
}
},
methods: {
change(){
this.newVal = "I am.";
this.$emit("input".this.newVal); }}}</script>
Copy the code
Vue3.0
The initial value is passed in using modelValue and the Update :modelValue event is emitted through emit. See the examples:
//ValidateInput.vue
<template>
<input :value="inputRef.val" @input="updateValue">
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
export default defineComponent({
props: {
modelValue: String
},
setup(props, context){
const inputRef = reactive({
val: props.modelValue || ""
});
/ / to monitor props. ModelValue
watch(() = > props.modelValue, (newVal) = > {
inputRef.val = newVal as string
})
const updateValue = (e: KeyboardEvent) = >{
const targetValue = (e.target as HTMLInputElement).value;
inputRef.val = targetValue;
context.emit('update:modelValue', targetValue);
}
return{
inputRef,
updateValue
}
}
})
</script>
Copy the code
Use it in the parent component:
<ValidateInput v-model="msg"/>
Copy the code
Then the bound MSG will change with the change of the input content, which can realize two-way data binding.
Check mechanism
The verification of the form is to judge whether the input content is legal when the user enters it, and to prompt the error message under the form in real time when the input content is illegal. Add a new attribute rules to the component defined above to pass the user’s rules for the current form element; Then, in blur, the rules are iterated to verify the input. The following uses mailbox input box verification as an example.
<template>
<div class="validate-input-container pb-3">
<input type="text"
class="form-control" :class="{'is-invalid': inputRef.error}"
:value="inputRef.val" @input="updateValue" @blur="validateInput">
<span v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}</span>
</div>
</template>
<script lang="ts">
const emailReg = / ^ [a - zA - Z0-9.! # $% & * + / = '? ^ _ ` {|} ~ -] + @ [a zA - Z0-9 -] + (? :\.[a-zA-Z0-9-]+)*$/
import { defineComponent, reactive, PropType } from 'vue';
interface RuleProp{
type: 'required' | 'email' | 'custom'; message: string; validator? :() = > boolean;
}
export type RulesProp = RuleProp[];
export default defineComponent({
props: {
modelValue: String.rules: Array as PropType<RulesProp>
},
setup(props, context){
const inputRef = reactive({
val: props.modelValue || "".error: false.message: ""
});
const updateValue = (e: KeyboardEvent) = >{
const targetValue = (e.target as HTMLInputElement).value;
inputRef.val = targetValue;
context.emit('update:modelValue', targetValue);
};
const validateInput = () = > {
if(props.rules){
const allPassed = props.rules.every((rule: RuleProp) = > {
let passed = true;
inputRef.message = rule.message;
switch(rule.type){
case 'required': passed = (inputRef.val.trim() ! = =' ')
break;
case 'email':
passed = emailReg.test(inputRef.val)
break;
case 'custom':
passed = rule.validator ? rule.validator() : true
break;
default:
break;
}
returnpassed }) inputRef.error = ! allPassed; }return true
}
return{
inputRef,
updateValue,
validateInput
}
}
})
</script>
Copy the code
So you can bind the placeholder property to the input tag when you use it. Here are two steps: (1) In the InvalidateInput component, set inheritAttrs: false to indicate that you don’t want the component’s root element to inherit attributes. Bind $attrs to the component
export default defineComponent({
/ / new
inheritAttrs: false.setup(props, context){... }Copy the code
<input type="text"
class="form-control" :class="{'is-invalid': inputRef.error}"
:value="inputRef.val" @input="updateValue" @blur="validateInput"
v-bind="$attrs">
Copy the code
More details can be found at cn.vuejs.org/v2/guide/co…
PropType
Add a Vue3.0 knowledge point PropType. We know that the props of a component can be used to define the parameters that the component can pass. Format such as
props: {
rules: Array
}
Copy the code
You need to use a constructor after the rules variable for its type. To take advantage of TypeScript smart hints, we usually define the data type of each item in the array. In this case, we use TypeScript interface and type. To combine the defined type with the constructor, Vue3.0 provides propTypes to combine constructors with specific types. Use method as above:
interface RuleProp{
type: 'required' | 'email' | 'custom'; message: string; validator? :() = > boolean;
}
export type RulesProp = RuleProp[];
export default defineComponent({
props: {
modelValue: String.rules: Array as PropType<RulesProp>
}
})
Copy the code
ValidForm encapsulation
Expose the Submit form submission event in ValidateForm.vue; Slot is also used to customize the content of each item in the Form and the style of the submit button. Finally, we listen for the click event of the outermost div, so that when the user clicks submit, the form will be submitted.
//ValidateForm.vue
<template>
<form class="validate-form-container">
<slot name="default"></slot>
<div class="submit-area" @click.prevent="submitForm">
<slot name="submit">
<button type="submit" class="btn btn-primary">submit</button>
</slot>
</div>
</form>
</template>
<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
emits: ['submit'].setup(props, context){
const submitForm = function(){
console.log("click");
context.emit("submit".false)}return{
submitForm
}
}
})
</script>
Copy the code
Component communication
We know that the parent component can pass data to the child component via props; A child component can pass data to its parent through emit events. Or $parent, $children to get the component information of the response. In Vue2.0, parent-child communication using slots can only be achieved through EventBus, the event stack; In Vue3.0, mitt, a plug-in, is recommended for publishing subscriptions to events. Install mitt first
npm install mitt --save
Copy the code
In order to access the ValidateInput validation methods for each child of the ValidateForm component. We need to subscribe to the Form-validate event in the ValidateForm component, and the callback is used to collect validation methods for each child component. In the child component, the component needs to publish the form-validate event and pass in its own validation function at initialization time. When the user clicks the Submit button, the ValidateForm component triggers the ValidateInput method for each child component. Thus the form verification is realized.
There are two other points that need to be noted :(1) the parent component should subscribe first and then the child component should publish so that the parent component can subscribe. So in ValidateForm we can subscribe to the form-validate event in setup; In ValidateInput, issue the form-validate event when onMounted. (2) The final aftermath should also be done well. We need to unsubscribe events while ValidateInput onUnmounted and empty the array of child component events.
//ValidateForm.vue
<script lang="ts">
import {defineComponent, onUnmounted} from 'vue'
/ / import mitt
import mitt from 'mitt'
export const emitter = mitt();
type ValidateFunc = () = > boolean;
export default defineComponent({
emits: ['submit'].setup(props, context){
// The callback function used to store form validation
let funcArr: ValidateFunc[] = []
const submitForm = function(){
const result = funcArr.map(func= > func()).every(result= > result);
context.emit("submit", result)
}
const callback = function(fn? : ValidateFunc){
if(fn){ funcArr.push(fn); }}/ / subscription form - validate
emitter.on("form-validate", callback);
// Clean up when uninstalling components
onUnmounted(() = >{
// Unsubscribe from form-validate
emitter.off("form-validate", callback);
funcArr = [];
})
return{
submitForm
}
}
})
</script>
Copy the code
<script lang="ts">
/ / import mitt
import {emitter} from './ValidateForm.vue'
export default defineComponent({
setup(props, context){...// Notice the timing of the lifecycle, publish the form-validate event
onMounted(() = >{
emitter.emit("form-validate", validateInput); })... } }) </script>Copy the code
The source code
Github address: github.com/YY88Xu/vali…
Thank you
If this article helps you, please remember to like oh, it will be my motivation to keep writing