Blazor和Vue对比学习(基础-9):表单输入绑定和验证,VeeValidate和EditFrom

这是基础部分的最后一章,内容比较简单,算是为基础部分来个HappyEnding。我们分三个部分来学习:

  • 表单输入绑定
  • Vue的表单验证:VeeValidate
  • Blazor的表单验证:EditForm

 

一、表单输入绑定

表单输入主要涉及前面章节的双向绑定,但不需要我们自定义,直接使用就行。如果已经掌握了“1.6章节双向绑定”,那这部分的知识点简直“洒洒水”。Vue的v-model指令,为所有原生的HTML组件做了一些特殊处理,不仅支持双向绑定,也支持check或radio的分组。而Blazor,对原生HTML并没有做特殊处理,所以实现起来比较麻烦(见下例)。对于Blazor,推荐直接使用内置的表单组件,不仅可以直接双向绑定,还支持验证。以下例子,为原始HTML标签的绑定。

//Vue
//以下代码展示多行文本框、单行文本框、单行文本框-去空格、单行文本框-数字、复选框、多选框、单选框、单选选择器、多选选择器的基本使用

<template>
    //多行文本框:
    <textarea v-model="textareaMsg" placeholder="请输入"></textarea>
    <span>逻辑层值:{{textareaMsg}}</span>

    //单行文本框:
    <input type="text" v-model="inputMsg" placeholder="请输入" />
    <span>逻辑层值:{{inputMsg}}</span>

    //单行文本框-去空格:
    <input type="text" v-model.trim="inputMsgTrim" placeholder="请输入" />
    <span>逻辑层值:{{inputMsgTrim}}</span>

    //单行文本框-数字:
    <input type="number" v-model.number="inputNumber" placeholder="请输入" />
    <span>逻辑层值:{{inputNumber}}</span>

    //复选框:
    <input type="checkbox" id="checkbox" v-model="isCheck" />
    <label for="checkbox">{{isCheck?"已选状态":"未选状态"}} </label>
    <span>逻辑层值:{{isCheck}}</span>

    //多选框:
    <input type="checkbox" id="vuecheck" value="vue" v-model="checkValue" />
    <label for="vuecheck">vue</label>
    <input type="checkbox" id="blazorcheck" value="blazor" v-model="checkValue" />
    <label for="blazorcheck">blazor</label>
    <span>逻辑层值:{{checkValue}}</span>

    //单选框:
    <input type="radio" id="vueradio" value="vue" v-model="radioValue" />
    <label for="vueradio">vue</label>
    <input type="radio" id="blazorradio" value="blazor" v-model="radioValue" />
    <label for="blazorradio">blazor</label>
    <span>逻辑层值:{{radioValue}}</span>

    //单选选择器:
    <select v-model="oneSelected">
        <option disabled value="">请选择</option>
        <option>vue</option>
        <option>blazor</option>
    </select>
    <span>逻辑层值:{{oneSelected}}</span>

    //多选选择器:
    <select v-model="moreSelected" multiple>
        <option>vue</option>
        <option>blazor</option>
    </select>
    <span>逻辑层值:{{moreSelected}}</span>
</template>

<script setup>
import {ref} from 'vue'
const textareaMsg = ref('') //多行文本框,字符串
const inputMsg = ref('') //单行文本框,字符串
const inputMsgTrim = ref('') //单行文本框-去空格,字符串
const inputNumber = ref() //单行文本框-数字,数值
const isCheck = ref(false) //单选框,布尔
const checkValue = ref([]) //多选框,数组
const radioValue = ref('') //单选框,字符串
const oneSelected = ref('') //单选选择器,字符串
const moreSelected = ref([]) //多选选择器,数组
</script>
//Blazor
//Blazor的@bind对check和rado的分组,并没有做出特殊处理

<div class="content">
    多行文本框:
    <textarea @bind="textareaMsg" @bind:event="oninput"></textarea>
    <h6>逻辑层值:@textareaMsg</h6>
</div>

<div class="content">
    单行文本框:
    <input @bind="inputMsg" @bind:event="oninput" />
    <h6>逻辑层值:@inputMsg</h6>
</div>

<div class="content">
    单行文本框-去空格:
    <input @bind="InputTrimMsg" @bind:event="oninput" />
    <h6>逻辑层值:@InputTrimMsg</h6>
</div>

<div class="content">
    单行文本框-数字:
    <input type="number" @bind="inputNumber" @bind:event="oninput" />
    <h6>逻辑层值:@inputNumber</h6>
</div>

<div class="content">
    复选框:
    <input type="checkbox" id="isChecked" @bind="isChecked" />
    <label for="isChecked">选中</label>
    <h6>逻辑层值:@(isChecked?"选中状态":"未选状态")</h6>
</div>

//多选框,无法使用@bind。通过双向绑定的属性和事件来实现【后面补充一种使用@bind的实现方式】
<div class="content">
    多选框:
    <input type="checkbox" id="vuecheck" name="moreLikeCodes" value="@moreLikeCodes" @onchange=Vuecheck />
    <label for="vuecheck">vue</label>
    <input type="checkbox" id="blazorcheck" name="moreLikeCodes" value="@moreLikeCodes" @onchange=Blazorcheck />
    <label for="blazorcheck">blazor</label>
    <h6>逻辑层值:@(String.Join(",", moreLikeCodes.ToArray()))</h6>
</div>

//单选框,无法使用@bind。通过双向绑定的属性和事件来实现【后面补充一种使用@bind的实现方式】
<div class="content">
    单选框:
    <input type="radio" id="vueradio" name="oneLikeCode" value="@oneLikeCode" @onchange=@(()=>{oneLikeCode="vue";}) />
    <label for="vueradio">vue</label>
    <input type="radio" id="blazorradio" name="oneLikeCode" value="@oneLikeCode" @onchange=@(()=>{oneLikeCode="blazor";}) />
    <label for="blazorradio">blazor</label>
    <h6>逻辑层值:@oneLikeCode</h6>
</div>

<div class="content">
    单选选择器:
    <select @bind="singleSelected">
        <option value="">请选择</option>
        <option value="vue">vue</option>
        <option value="blazor">blazor</option>
    </select>
    <h6>逻辑层值:@singleSelected</h6>
</div>

@code {

    private string textareaMsg = "";
    private string inputMsg = "";
    public int inputNumber { get; set; }

    //通过设置set,实现去除空格功能
    private string inputTrimMsg = "";
    public string InputTrimMsg
    {
        get { return inputTrimMsg; }
        set { inputTrimMsg = value.Trim(); }
    }

    private bool isChecked;

    //多选框
    private List<string> moreLikeCodes = new List<string>();
    private void Vuecheck(ChangeEventArgs e)
    {
        if (Convert.ToBoolean(e.Value))
        {
            moreLikeCodes.Add("vue");
        }
        else
        {
            moreLikeCodes.Remove("vue");
        }
    }
    private void Blazorcheck(ChangeEventArgs e)
    {
        if (Convert.ToBoolean(e.Value))
        {
            moreLikeCodes.Add("blazor");
        }
        else
        {
            moreLikeCodes.Remove("blazor");
        }
    }

    //单选框
    private string? oneLikeCode;
    //单选下拉框
    private string? singleSelected;
}
//Blazor
//补充另外一种实现多选框的方式,直接用@bind指令

@if (selectedLikeCodes is not null)
{
    foreach (var item in selectedLikeCodes)
    {
        <input type="checkbox" @bind="@item.IsSelected" />
        <span>@item.Name</span>
    }

    <h5>我喜欢的代码:</h5>
    @foreach (var item in selectedLikeCodes.Where(c => c.IsSelected))
    {
        <div>@item.Name</div>
    }
}


@code {

    private List<LikeCode> selectedLikeCodes = new List<LikeCode>();

    private List<LikeCode> likeCodeItems = new List<LikeCode>
    {
        new LikeCode{Name="vue",IsSelected=false},
        new LikeCode{Name="blazor",IsSelected=false},
        new LikeCode{Name="maui",IsSelected=false}
    };

    protected override void OnInitialized()
    {
        selectedLikeCodes = likeCodeItems.Select(e => new LikeCode { Name = e.Name, IsSelected = e.IsSelected }).ToList();
    }

    private class LikeCode
    {
        public string? Name;
        public bool IsSelected;
    }
}

 

 

二、Vue的表单验证:VeeValidate

Vue没有内置表单验证功能,官方推荐使用第三方库VeeValidate。而Blazor,封装了一套内置表单组件,自带验证功能,我们主要学习这套组件的使用。本章节关于表单验证的知识点,均只涉及基本的使用,关于进一步的深入使用、理解、甚至定制扩展功能,我们将在进阶学习中继续展开。

 

1、VeeValidate的安装、组件式的简单使用

VeeValidate实现表单验证有两种方式,一种是组件式,主要使用<Form><Field>;另外一种是组合式API的方式,可以和其它表单组件更好融合,是更推荐的方式,后面的学习也以此为主。特别说一下VeeValidate的安装,有些坑,如下所示:

1)网络上搜索VeeValidate的安装使用,大多数会提示:①安装,npm install vee-validate;②在main.js中引入,import VeeValidate from vee-validate③在main.js中安装插件,Vue.use( VeeValidate)app.use( VeeValidate)。实际上,在Vue3,只要①就可以了。估计是Vue3引入组合式API之后,VeeValidate也是以组合式API的方式引入。如果是Vue2,可能还是需要三步来引入。

2)完成①后,直接在组件中import就可以使用相关组件或API,以组件式为例:import { Field, Form, ErrorMessage } from 'vee-validate'

3VeeValidate官方推荐结合yup来使用。yup可以理解为验证函数的语法糖,如下例所示(使用组件方式)。yup需要另外安装,npm install yup;然后在组件中引入,import * as yup from yup’。我们后面的案例,均使用更简单的验证方式yup。

//函数验证和yup验证方式的对比
//【特别说明一下】:以往我们使用HTML表单组件,都需要v-model逻辑层的响应式数据,但在VeeValidate里,你找不到了
//<Form>帮我们托管了,数据也可以通过API拿到!
//字段级验证 <template> <Form> <Field name="field" :rules="isRequired" /> <ErrorMessage name="field" /> </Form> </template> <script setup> import { Field, Form, ErrorMessage } from 'vee-validate' import * as yup from 'yup' //函数验证方式,value为字段输入值 function isRequired(value){ if (value && value.trim()){ return ture } return 'This is required' } //yup验证方式 const isRequired = yup.string().required() </script> //表单级验证 <template> <Form @submit="submit" :validation-schema="mySchema"> <Field name="email" /> <ErrorMessage name="email" /> <Field name="password" type="password" /> <ErrorMessage name="password" /> <button>Submit</button> </Form> </template> <script setup> import { Field, Form, ErrorMessage } from 'vee-validate' import * as yup from 'yup' //函数验证方式 const mySchema = { email(value) { //验证规则... }, password(value){ //验证规则... } } //yup验证方式 const mySchema = yup.object({ email: yup.string().required().email(), password: yup.string().required().min(8), }) </script>

 

2、VeeValidate的组合式API使用

VeeValidate起头的API主要有两个,useFielduseForm。从字面就知道,一个是关于字段的,一个是关于表单的。

1)useFieldconst{ value, errorMessage } = useField('fieldName', yup.string().required().min(8))

①接受:字段name、字段级的验证方法、字段级初始值等入参

export:字段value、错误信息errorMessage、字段元信息meta、值变化处理函数handleChange、字段重置resetField等一大堆API。使用对象解构的方式来接收,大多数解构出来的值,也是响应式。

2)useFielduseForm:const{ handleSubmit, isSubmitting } = useForm()

①接受:表单级验证方法对象、表单级初始值对象等入参

export:表单提交handleSubmit、提交状态isSubmitting、表单元信息meta等一大堆API。使用对象解构的方式来接收,大多数解构出来的值,也是响应式。

3)如上所述,VeeValidate的使用套路很简单,直来直去的三步走,入参-解构拿API-使用API

 4)下面来个大例子,大多数API都有涉及和讲解,很全了哦!!!

<template>    
    <form @submit="onSubmit">
        <div class="content50">
          *姓名:
            <input v-model="name" />
            <span>{{nameError}}</span>
        </div>        
        <div class="content50">
          *性别:
            <input type="radio" id="sexmale" value="1" name="sex" v-model="sex" />
            <label for="sexmale">男</label>
            <input type="radio" id="sexfemale" value="0" name="sex"  v-model="sex" />
            <label for="sexfemale">女</label>
            <span>{{sexError}}</span>
        </div>        
        <div class="content50">
          *年龄:
            <input type="number" v-model="age" />
            <span>{{ageError}}</span>
        </div>        
        <div class="content50">
          是否已婚:
            <input type="checkbox" v-model="marriage" />
            <span>{{marriageError}}</span>
        </div>        
        <div class="content50">
          学历:
            <select v-model="graduate">
                <option value="" disabled>请选择</option>
                <option value="小学">小学</option>
                <option value="初中">初中</option>
                <option value="高中">高中</option>
                <option value="本科">本科</option>
                <option value="研究生">研究生</option>
            </select>
            <span>{{graduateError}}</span>
        </div>        
        <div class="content50">
          入职日期:
            <input type="date" v-model="entrydate" />
            <span>{{entrydateError}}</span>
        </div>        
        <div class="content50">
          爱好:
            <input type="checkbox" id="likebasketball" value="basketball" v-model="likes" />
            <label for="likebasketball">basketball</label>
            <input type="checkbox" id="likefootball" value="football" v-model="likes" />
            <label for="likefootball">football</label>
            <input type="checkbox" id="likeswim" value="swim" v-model="likes" />
            <label for="likeswim">swim</label>
            <input type="checkbox" id="likecode" value="code" v-model="likes" />
            <label for="likecode">code</label>
            <span>{{likesError}}</span>
        </div>        
        <div class="content50">
          自我介绍(使用handleChange,失焦时才触发验证,改变验证的敏感性):
            <textarea @change="handleChange" :value="introduce"></textarea>
            <span>{{introduceError}}</span>
        </div>
        <input type="submit" />
    </form>
    
    <div class="content200">
        <h3>表单所有字段值</h3>
        <h6>{{values}}</h6>
        <h3>表单所有错误信息</h3>
        <h6>{{errors}}</h6>
    </div>    
</template>

<script setup>
import {ref} from 'vue'
import { useForm,useField } from 'vee-validate';
import * as yup from 'yup';

// 不需要声明以下响应式变量,这些工作由Vee隐式完成
// const name = ref('')
// const sex = ref()
// .......


//1、表单设置===============================================================================
//1.1初始值,可以省略,但未了控制“多选框”的数组绑定,需要设置
const initialValues = {
    likes:[],
}
//1.2初始错误,首次验证错误时显示,可以省略,错误信息应该在多语言里设置,此篇文章略过,大家可以去查文档
const initialErrors ={
    name:'姓名必填,且不能超过20字',
}
//1.3验证规则,核心
const validationSchema = yup.object({
    name:yup.string().required().max(20),
    sex:yup.number().required().integer().min(0).max(1),
    age:yup.number().required().integer().min(0).max(100),
    marriage:yup.boolean(),
    graduate:yup.string(),
    introduce:yup.string().min(50),
    entrydate:yup.date(),

})
//1.4表单useForm
//入参:validationSchema-验证规则,initialValues-初始值,initialErrors-首次验证错误时显示
//API①:values-表单各字段值,errors-表单验证错误信息
//API②:handleSubmit-表单验证成功后回调,isSubmitting-验证进度,submitCount-验证次数
//API③:resetForm-表单重置,不入参则以初始值重置,可以对象方式以指定值重置
//API④:setValues-以对象方式设置表单各字段值,setFieldValue-单独设置指定字段值,入参为字段名和值
//API⑤:setErrors-以对象方式设置表单各字段错误信息,setFieldError-单独设置指定字段错误信息,入参为字段名和错误信息
const { values,errors,handleSubmit,isSubmitting,submitCount,resetForm,setValues,setFieldValue,setErrors,setFieldError } = useForm({
    validationSchema: validationSchema,
    initialValues: initialValues,
    initialErrors: initialErrors,
})



//2、字段设置===============================================================================
//字段useField
//入参:字段名称【注,入参的第二个参数可以设置验证规则,如useField('name',yup.string().required()),但一般在表单层级上统一设置】
//API①:value-绑定字段(值为ref响应式,用computedRef包装),errorMessage-错误信息
//API②:handleChange-Vee的验证只根据字段值变化响应,和DOM事件无关。handleChange可以将DOM事件与验证响应关联,从而改变验证响应的时机
const {value:name,errorMessage:nameError} = useField('name')
const {value:sex,errorMessage:sexError} = useField('sex')
const {value:age,errorMessage:ageError} = useField('age')
const {value:marriage,errorMessage:marriageError} = useField('marriage')
const {value:graduate,errorMessage:graduateError} = useField('graduate')
const {value:entrydate,errorMessage:entrydateError} = useField('entrydate')
const {value:likes,errorMessage:likesError} = useField('likes')
const {value:introduce,errorMessage:introduceError,handleChange} = useField('introduce')


//3、表单行为===============================================================================
//提交表单:验证成功的处理和验证失败的处理
//第一个参数:验证成功回调,入参为表单数据
//第二个参数:验证失败回调,入参为表单数据、错误信息、验证结果
//验证成功后,调用API,resetForm(),重置表单
const onSubmit = handleSubmit((values)=>{
    console.log("发送表单数据"+values)
    resetForm(); //重置表单
},(values,errors,results)=>{
    console.log("表单数据"+values)
    console.log("错误信息"+errors)
    console.log("验证结果"+results)
})

</script>

 

 

 

三、Blazor的表单验证:EditForm

Blazor自带验证体系,使用起来非常方便,一般的验证功能都能实现,如果想实现更复杂的逻辑,也可以进一步深入。有几点需要说明:

1、需要使用Blazor自带的一整套组件,包括<EditForm><InputText>等,进阶的话,也可以继承InputBase<T>自己开发

2、验证的基本过程:

1)表单组件初始化后,会创建一个EditContext对象,这是表单的上下文,不仅以键值对<FileIdentifier,FieldState>的方式保存着各个字段的元信息,还提供了一系列方法

2)这个EditContext以CascadingValue联级的方式提供给表单组件使用

3)每次有数据更新时,触发验证,并创建一个新的EditContext,验证结果会保存到一个ValidationMessageStore

3、如果表单组件绑定了集合或复杂类型,将无法验证。这个时候有一个实验性的组件可以使用,<ObjectGraphDataAnnotationsValidator />

4、接下来和Vue一样,直接干一个实例!!!

<div class="container">
    //EditForm的Model属性,可以直接绑定employee对象,这里绑定editContext的原因是为了拿到EditContext上下文
    //提交表单的三种情况:OnValidSubmit-验证成功回调、OnInValidSubmit-验证失败回调、OnSubmit-无论成功或失败均回调
    //OnValidSubmit和OnInValidSubmit可以一起使用
    <EditForm Model="@editContext" OnValidSubmit="@MyValidSubmit">

        //启动验证
        <DataAnnotationsValidator/>

        //表单级验证信息的容器
        <ValidationSummary/>

        <div class=content50>
            *姓名:
            <InputText id="name" @bind-Value="employee.Name"></InputText>
            //字段级验证信息容器
            <ValidationMessage For=@(()=>employee.Name) />
        </div>

        <div class="content50">
            *性别:
            <InputRadioGroup @bind-Value="employee.Sex">
                <InputRadio Value="@Sex.male">male</InputRadio>
                <InputRadio Value="@Sex.female">female</InputRadio>
            </InputRadioGroup>
        </div>

        <div class="content50">
            *年龄:
            <InputNumber @bind-Value="employee.Age"></InputNumber>
        </div>

        <div class="content50">
            *学历:
            <InputSelect @bind-Value="employee.Graduate">
                <option value="@Graduate.primary">primary</option>
                <option value="@Graduate.middle">middle</option>
                <option value="@Graduate.high">high</option>
                <option value="@Graduate.university">university</option>
            </InputSelect>
        </div>

        <div class="content50">
            是否已婚:
            <InputCheckbox @bind-Value="employee.IsMarriage"></InputCheckbox>
        </div>

        <div class="content50">
            入职日期:
            <InputDate @bind-Value="employee.EntryDate"></InputDate>
        </div>

        <div class="content100">
            爱好:
            <InputSelect @bind-Value="employee.Likes">
                <option value="@Like.football">football</option>
                <option value="@Like.basketball">basketball</option>
                <option value="@Like.swim">swim</option>
                <option value="@Like.code">code</option>
            </InputSelect>
        </div>

        <div class="content100">
            自我介绍:
            <InputTextArea @bind-Value="employee.Introduce"></InputTextArea>
        </div>
        <br />
        <button type="submit">提交</button>

    </EditForm>
</div>

@code {
    private Employee employee = new Employee();
    private EditContext? editContext;
    protected override void OnInitialized()
    {
        editContext = new EditContext(employee);
    }

    private void MyValidSubmit()
    {
        Console.WriteLine("验证通过,提交表单");
    }

    //表单绑定的对象,直接标注验证规定和错误信息,非常好用
    //这个类还可以前后端共享
    private class Employee
    {
        [Required(ErrorMessage = "姓名必填")]
        [MaxLength(20, ErrorMessage = "最长20字")]
        public string? Name { get; set; } = null; //姓名

        [Required(ErrorMessage = "性别必填")]
        public Sex? Sex { get; set; } = null; //性别

        [Required(ErrorMessage = "年龄必填")]
        [Range(18, 60, ErrorMessage = "只能填18-60岁")]
        public int Age { get; set; } //年龄

        [Required]
        public Graduate Graduate { get; set; } = Graduate.Null;//学历

        public bool IsMarriage { get; set; } //是否已婚

        [DataType(DataType.Date)]
        public DateTime EntryDate { get; set; } //入职日期

        [Required, MinLength(1, ErrorMessage = "最少选1个"), MaxLength(3, ErrorMessage = "最多选3个")]
        public Like[] Likes { get; set; } = new[] { Like.basketball }; //爱好

        [MaxLength(300, ErrorMessage = "最长300字")]
        public string? Introduce { get; set; } //自我介绍

    }
    //Employee对象用到的枚举类型
    private enum Sex{ male,female }
    private enum Like{ football,basketball,swim,code }
    private enum Graduate{ Null,primary,middle,high,university }
}
posted @ 2022-05-19 22:32  functionMC  阅读(900)  评论(0编辑  收藏  举报