antd vue form 手动校验_前端 Form 的表单的一个通用解决方案

本文探讨了前端表单的通用解决方案,特别是针对React框架中的AntD Vue Form。作者提出了一种名为Field的工具,用于自动绑定value和onChange,简化数据获取和校验过程。Field与React的表单组件结合,可以实现数据获取、校验、赋值等功能,减少手动编写onChange代码。此外,文章还讨论了自定义事件、表单组件的使用以及Form组件的优化,以提高用户体验和代码简洁性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0ec7c08d7b23a78600ce8246cd548789.png

原文:【第1535期】前端 Form 的表单的一个通用解决方案

这是一篇来自微信公众号的文章,写的还不错,摘抄过来欣赏一下。

文章开始之前,如果使用过AntD框架form表单的同学我觉得阅读应该不成问题,其他同学也还可以(废话)。

文章从下边开始☟☟☟☟☟☟

前端的Form 表单主要用于解决数据获取、数据校验、数据赋值 这三大类问题。这篇文章里面的提供的解决方案能够比较完美的用在 React 框架上,但是解决问题的思路相信应该是可以使用于任何框架语言。

中后台的表单组件已经不仅仅有 input 和 select,可能还扩展到 范围选择器、日期选择器 等,这些组件往往为了实现更优雅的UI和更使用的交互会在原生的组件上面做多层封装,而经过多层叠加后可能已经看不到原生表单元素的影子了。比如经过封装下面这段 DOM 结构经过样式修改也可能成为一个输入组件,虽然完全看不到 input 的影子

<span>
  <span contentEditable></span>
</span>

所以为了便于大家理解我这里从传统的原生 form 说起,好让大家有一个递进的过程。

引子:原生 form 表单

最初始的一份代码如下,代码很简单,看着也很舒服。

<form action="/api/post" method="post">
  username: <input name="username" />
  password: <input name="password" />
  <button type="submit">submit</button>
</form>

但是你开始做数据校验相关,表单就立刻变得复杂多了。如下:代码增多了一倍。

<script>
  function checkname(target) {
    const value = target.value;
    if (value.length < 10) {
      document.getElementById('username_msg').innerHTML = '长度必须>10'
    } else {
      document.getElementById('username_msg').innerHTML = ''
    }
  }
  function checkpassword(target) {
    const value = target.value;
    if (!value.match(/^[w]{6,16}$/)) {
      document.getElementById('password_msg').innerHTML = '密码必须 6-16 位字母数字'
    } else {
      document.getElementById('password_msg').innerHTML = ''
    }
  }
  function getInitData() {
    ajax({
      url:'/api/data', 
      success:function(data) {
        document.getElementById('username') = data.username;
    });
  }
  getInitData();
</script>
<form action="/api/post" method="post">
 username: <input name="username" onchange="checkname(this)"/>
           <span id="username_msg"></span>
 passowrd: <input name="password" onchange="checkpassword(this)"/>
           <span id="password_msg"></span>
 <button type="submit">submit</button>
</form>

如果把DOM的部分也用JS来实现,基本可以做到只修改JS不需要再动DOM结构,但是也让JS的复杂度增高不少。

React 里面所有的DOM结构都是自己通过JS 生成的,JSX也可以方便的实现DOM结构。但这里我拿原生表单举例,只是想说用 React 写出来的原生表单,并不比用原生 JS 的优雅多少!!!

React 中的原生 form 表单

同样一段最简单的功能,套在 react 框架下面是这个样子。

class Demo extends React.Component {
  render() {
    return <form action="/api/post" method="post">
      username: <input name="username" />
      passowrd: <input name="password" />
      <button type="submit">submit</button>
    </form>
  }
}

比如同样想要实现校验输入自动校验 和 赋值,看下面一段代码,想想就是一大堆事情要做。

class Demo extends React.Component {
  state = {
    username: '',
    password: '',
    usernameMsg: '',
    passwordMsg: '',
  };
  checkname = e => {
    // 获取数据
    const value = e.target.value;
    // 受控模式赋值
    this.setState({
      username: value,
    });
    // 校验数据
    if (value.length < 10) {
      this.setState({
        usernameMsg: '长度必须>10',
      });
    } else {
      this.setState({
        usernameMsg: '',
      });
    }
  };
  checkpassword = e => {
    // 获取数据
    const value = e.target.value;
    // 受控模式赋值
    this.setState({
      password: value,
    });
    // 校验数据
    if (!value.match(/^[w]{6,16}$/)) {
      this.setState({
        passwordMsg: '密码必须 6-16 位字母数字',
      });
    } else {
      this.setState({
        passwordMsg: '',
      });
    }
  };
  handleSubmit = () => {
    ajax({
      url: '/api/post',
      data: {
        username: this.state.username,
        password: this.state.password,
      },
      success: () => { 
        // success
      },
    });
  };
  render() {
    // 获取数据和错误信息
    const { username, password, usernameMsg, passwordMsg } = this.state;
    return (
      <form action="/api/post" method="post">
        username: <input value={username} onChange={this.checkname} />
        <span>{usernameMsg}</span>
        passowrd: <input value={password} onChange={this.checkpassword} />
        <span>{passwordMsg}</span>
        <button type="submit" onClick={this.handleSubmit}>
          submit        </button>
      </form>
    );
  }
}

代码有点长,大家大致看一眼干什么的就行,从上面一系列代码中基本可以总结出一个现象,要想实现表单数据获取、校验,基本离不开 onChange 这个方法,而且是有几个表单控件,就要写几个 onChange 。(以上代码可直接运行,可以在 https://codepen.io/frankqian/pen/XOROBw?editors=0010 调试)

其实这里和框架并没有什么关系,因为不管用什么框架要想做到 赋值和校验 这两个功能,基本一定要在 input 上面绑定 onChange。 所以如果有个通用的工具可以自动帮你把这些onChange的绑定都做了,再把校验规则固定下,是不是所有的表单问题都可以解决了呢?是的通用表单解决方案就是按照这种思路设计出来的!

适用于所有 React 表单组件的解决方案

所有的用 React 写成的组件都可以使用该方案。甚至 非 React 体系也可以使用改思路来解决问题。

基于所有表单控件都需要绑定 onChange 做数据获取和校验的原则,所以我设计了一个 Field 工具。这个工具原理很简单,就是可以自动帮你绑定 value + onChange 解决上面一长串代码的问题。

const field = new Field(this);
field.init('username');

field.init 会自动返回 value + onChange ,内容如下:

{
  value: "",
  onChange: ƒ ()
}

下面这张图简单表面 Field 和 React 体系之间的关系。

4ebd23af43b148cf0a121a4a557782ac.png

1. 使用 Field 获取数据

import {Field} from '@alifd/next';
class Demo extends React.Component {
  field = new Field(this);
  handleSubmit = () => {
    console.log(this.field.getValues()); // 获取数据
  }
  render() {
    const {init} = this.field;
    return <form>
      username: <input {...init('username')} />
      passowrd: <input {...init('password')} />
      <button onClick={this.handleSubmit} >submit</button>
    </form>
  }
}

这样一个表单的数据获取问题就解决了,代码简洁了很多。 Demo 在这里 https://codepen.io/frankqian/pen/xMdoxZ?editors=0010 可以自己调试

2. 表单校验

既然能够获取到数据了,那边表单校验是顺手的事情,因为校验只依赖数据。我们只需要对集中固定的交互性形式和校验规则做抽象就好了。

交互形式上大概包含以下三类

  • 输入的时候实时校验,一般 onChange 触发
  • 离开焦点的时候校验,一般 onBlur 触发
  • 通过自定义的操作来触发校验,自己调用 api 触发

常见的校验规则抽象

规则名称描述类型触发条件/数据类型required不能为空Booleanundefined/null/“”/[]pattern校验正则表达式正则

minLength字符串最小长度 / 数组最小个数NumberString/Number/ArraymaxLength字符串最大长度 / 数组最大个数NumberString/Number/Arraylength字符串精确长度 / 数组精确个数NumberString/Number/Arraymin最小值NumberString/Numbermax最大值 / 数组精确个数NumberString/Numberformat对常用 pattern 的总结url/email/tel/numberString

validator自定义校验Function

这里说明下表单是弱类型的数据。比如 input 框里面你希望用户输入的是整数,返回的 value 类型可能有两种

  • “123456”, String 类型的整数校验方式为 :/d+/
  • 123456, Number 类型的整数校验方式为: typeof Value === ‘number’

这个时候要求用户一定要返回 Number 类型才能校验非常不友好,所以在 Field 校验逻辑里面就把类型的问题处理掉了,而不是交给用户去判断。

上面是小插曲,我们继续看如下 Field + 表单的代码,解决了数据获取、表单校验的所有功能。

import { Field } from '@alifd/next';
class Demo extends React.Component {
  field = new Field(this);
  handleSubmit = (e) => {
    e.preventDefault();
    this.field.validate(); // 自定义校验
    console.log(this.field.getValues()); // 获取数据
  }
  render() {
    const {init, getError} = this.field;

    return <form>
      username: <input {...init('username', {rules: { required: true, minLength: 10}})} />
      <span style={{color: 'red'}}>{getError('username')}</span>  {/**错误信息**/}
      passowrd: <input {...init('password', {rules: {
          pattern: /^[w]{6,16}$/,
          message: '密码必须 6-16 位字母数字'
        }})} />
      <span style={{color: 'red'}}>{getError('password')}</span>  {/**错误信息**/}
      <button onClick={this.handleSubmit} >validate</button>
    </form>
  }
}

这样之前可能需要 70 行的代码 24 行就可以解决了,可以让代码清晰不少。调试demo见: https://codepen.io/frankqian/pen/vbZmXE?editors=0010

8ac0deffc132f138549e70e6a22a09e0.png

3. 写自定义的事件

既然 init 会自动返回 onChange,那么如果我希望自己在 onChange 里面加一些逻辑改怎么处理呢。

如果直接写 onChange 会被 init 覆盖掉,不管写前面还是后面总有一个onChange会被覆盖掉

<input 
  onChange={(value) => console.log(value)}
  {...init('username')} 
/>

所以在 init 提供了 props 可以把组件原生的 props 透传进去,Field内部会做好 hook 逻辑处理。

<input {...init('username', {
   props: {onChange:(value) => console.log(value)}
})} />

大家用起来觉得很麻烦,后面会再介绍如果在 Form 层面设计的更加趋向于原生方法的使用

4. 自己写的表单组件怎么用

现在很多React 组件是在原生组件之上又做了封装,还有很多组件可能并没有包裹表单元素(比如 Fusion Select 里面并没有 select 元素,下拉框是自己做的 )。但是只要你自己写的组件也遵循表单的规则就可以使用该方案。

4.1 基本: value + onChange 受控规则

这个规则其实来自原生 html 的组件,我们自己写的组件只要按照标准来都可以使用 Field。

b398df5b3c2605610967783c7ed0c5fc.png

自己写的组件比起原生的表单组件会更加美观,交互更友好。只要遵循规范都能在 field 里面使用,详细demo 见 https://codepen.io/frankqian/pen/gqRWJx?editors=0010这个规则其实来自原生 html 的组件,我们自己写的组件只要按照标准来都可以使用 Field。

4.2 高级功能:满足更加人性化的需求

还有一些其他更加细粒度的规则,是为了让你的组件更加好的适配高级功能,比如:

一键 reset 清空所有数据。因为每个组件的接收数据类型不一样,所以统一为在 willReceiveProps 里面接收 value=undefined

componentWillReceiveProps(nextProps) {
    if ('value' in nextProps ) {
        this.setState({
           value: nextProps.value === undefined? []: nextProps.value   //  设置组件的被清空后的数值
        })
    }
}

一次交互操作只抛一次 onChange

比如 upload 上传,如果一次上传触发上百次 onChange,那么整个页面会跟着一起 Render 几百次,非常影响性能

1abc02fc0a8ee72c3c81ec42161e7feb.png

比如 Slider, 在拖动的时候如果实时触发 onChange,那么在拖动滑块的时候可能会非常卡顿。所以鼠标松开的那个瞬间触发才是比较合理的操作,其他的拖拽事件可以交给 onProgress

1466a30bcc93bc6900df28c89b4c99be.png

Fusion Next 的表单组件基本都已经是按照这套规范标准实现了,详细可以查看这里的文档 https://fusion.design/component/field 拉到最下面

Form 组件让体验持续升级

上面知道了 Field 可以解决校验、获取、赋值等数据方面的问题,但是并不能解决 UI 和 交互的问题,在布局和错误展示的时候需要自己来控制。

常用场景抽象让布局更轻松

场景的布局有水平 inline 布局、垂直的分栏布局,通过 FormItem 的 api 可以非常轻松的做到。

垂直布局

<Form>
  <FormItem label="Username:">
    <Input name="first"  placeholder="first"/>
    <Input name="second" placeholder="second"/>
  </FormItem>
  <FormItem label="Password:" required>
    <Input htmlType="password" name="pass" placeholder="Please enter your password!"/>
  </FormItem>
  <FormItem label=" ">
    <Form.Submit>Submit</Form.Submit>
  </FormItem>
</Form>

129c1da0727f5285ccf4a3f9864bf362.png

水平布局

<Form inline>...</Form>

191b639b8be604f1d165ef4897ccc92c.png

标签内置

<Form labelAligin="inset">...</Form>

3d224ec9c1e11d76594c5c1e3bb821ff.png
  1. 辅助错误展示

出错的时候自动展示错误信息,不需要自己 getError 判断。 每种状态怎么展现由各自的组件自己实现。减少和Form的耦合

3edfd049644913b4b569ec558f830d7a.png

每个组件的加载中、成功、失败,都由组件自己实现,Form 只是在校验的时候传递 state 给各个组件,这样不需要 Form 去关心每个组件应该展现为什么样!

<Input state="error" />  // 错误状态
<Input state="loading" /> // 加载中
<Input state="success" /> // 成功
<DatePicker state="error" /> // 错误状态

进一步优化 Form 让使用更简单

以上我们还是 Field + Form 配合来使用的,代码基本是这个样子。

import { Form, Input, Field, Button } from '@alifd/next';
const FormItem = Form.Item;
class Demo extends React.Component {
  field = new Field(this);
  handleSubmit = () => {
    this.field.validate();
  }
  render() {
    const {init} = this.field;
    return  <Form field={this.field}>
        <FormItem label="Username:">
            <Input {...init('username', {
              rules: {required}
            })} />
        </FormItem>
        <FormItem label="Password:">
            <Input {...init('password', {
              rules: {pattern:/[w]{6,16}/}
            })} htmlType="password" />
        </FormItem>
        <FormItem label=" ">
            <Button onClick={this.handleSubmit} >Submit</Button>
        </FormItem>
    </Form>
  }
}

可能写多了之后就会想,每个组件都要使用 init 、都需要写 rules 规则,而且在 jsx 中写一大串的 JSON 数据。

是否有方法让数据获取和校验变得更简单,让代码再进一步的简化呢?

进一步集成 Field 能力而弱化用法

针对以上问题对 Form 进一步优化,把 Field 的能力整合进了 Form,而把 Field 的用法进一步弱化,让大家不需要再关心 init/取数据 等问题。代码如下:

import { Form, Input, Button } from '@alifd/next';
const FormItem = Form.Item;
class Demo extends React.Component {
  handleSubmit = (values, errors) => {
    if (errors) {
      // 校验出错 
      return;
    }
    console.log(values) // 获取数据
  }
  render() {
    return  <Form>
        <FormItem label="Username:" required>
            <Input name"username" />
        </FormItem>
        <FormItem label="Password:" pattern={/[w]{6,16}/}>
            <Input name="password" htmlType="password" />
        </FormItem>
        <FormItem label=" ">
            <Form.Submit validate onClick={this.handleSubmit} >Submit</Form.Submit>
        </FormItem>
    </Form>
  }
}

上面代码中可以看出几个优化点:

  • 不需要关注 Field 用法,改成 Form API 的方式。用法简单直接不少
  • 通过 name 来进行数据初始化,也更加接近原生 form 的用法,大家更容易理解。
  • 校验功能 API 化,代码更加简洁,可读性增强

后记

Form 的优化一定不会仅仅止于此,因为在实际业务中会遇到更加复杂的功能。

很多业务为了更加方便快捷,会抽象常用的组件布局,通过后端接口吐出JSON schema的方式直接在前端动态展示表单,虽然比较业务化当时确实方便快捷,能够极大的解决效率问题;

又或者把常用的表单类场景做成业务组件、模块模板,在使用的时候直接下载使用。比如:Fusion的表单类模块:https://fusion.design/module?category=表单

方案很多,总有适合自己的一套。

<think>好的,用户的问题是关于Ant Design Vue表单手动赋值时如何保留校验状态,避免清除验证状态。我需要先理清楚Ant Design Vue表单校验机制,然后找到正确的方法来设置值而不影响校验状态。 首先,用户提到手动赋值时会清校验状态。根据引用[2],Antd Vue表单校验通常使用validateFields和validateFieldsAndScroll方法,这些方法可能会在值改变时触发重新校验。但用户需要的是在手动设置值后保留之前的校验状态,比如错误提示。 然后,引用[3]中提到了通过自定义校验规则和Form.Item的rules属性来控制校验。可能涉及到使用validator函数,但用户的问题是关于赋值而非校验规则本身。 通常,在Ant Design Vue中,直接使用setFieldsValue方法会触发表单的重新校验,导致之前的校验状态被清除。用户希望绕过这一点。可能需要使用更底层的方法,比如利用form.getFieldInstance获取字段实例,然后手动更新值而不触发校验。不过Ant Design Vue可能没有直接提供这样的API,可能需要查找相关文档或社区解决方案。 另外,考虑Ant Design的表单数据流,表单值的变化会触发校验。如果用户手动设置值时不希望触发校验,可能需要使用options参数中的特定配置,比如设置validate为false。例如,在调用setFieldsValue时,传递{ validate: false }选项,这样更新值就不会触发校验,从而保留之前的校验状态。 此外,查看引用[3]中的代码示例,使用getValueFromEvent和valuePropName可能影响值的获取和设置,但用户的问题更侧重于赋值后的校验状态保留。可能需要结合这些属性,在设置值时避免触发校验流程。 总结可能的解决方案: 1. 使用setFieldsValue方法,并设置选项参数{ validate: false }来禁止触发校验2. 如果选项参数不支持,可能需要通过其他方法,比如在赋值前保存校验状态,赋值后再恢复,但这种方法可能复杂。 3. 检查Ant Design Vue的文档,是否有相关的API可以静默更新字段值。 根据经验,Ant Design的Form组件在调用setFields时,可以配置validateTrigger来控制是否触发校验。但需要确认Vue版本的Ant Design是否支持此选项。或者,可能需要在更新值时使用form.setFields方法来手动设置值和校验状态,保持状态的保留。 例如,使用form.setFields([{ name: 'fieldName', value: newValue, touched: false, validating: false }]),这样可能不会触发自动校验,同时保留之前的错误状态。但需要验证这种方法的有效性。 最终,正确的做法可能是使用setFieldsValue并传递validate选项为false,或者在更新值时手动管理校验状态,避免自动校验清除之前的错误提示。</think>### 如何在 Ant Design Vue 表单手动赋值时保留校验状态? 在 Ant Design Vue 表单中,直接通过 `setFieldsValue` 方法赋值会触发默认的校验流程,导致校验状态被重置。若需保留校验状态(如错误提示),可通过以下方法实现: --- #### **方法 1:使用 `setFieldsValue` 的静默赋值** 调用 `setFieldsValue` 时,添加 `{ validate: false }` 参数,阻止赋值后自动触发校验。 **代码示例**: ```javascript this.formRef.value.setFieldsValue({ fieldName: newValue }, { validate: false }); // 关键:禁用自动校验 ``` --- #### **方法 2:通过 `setFields` 手动控制状态** 通过 `form.setFields` 直接设置字段值和校验状态,保留 `errors` 和 `validating` 等元数据。 **步骤**: 1. 获取当前字段的校验状态。 2. 更新字段值,同时保留原有校验状态。 **代码示例**: ```javascript const currentField = this.formRef.value.getFieldInstance('fieldName'); this.formRef.value.setFields({ fieldName: { value: newValue, errors: currentField.errors, // 保留原有错误信息 touched: currentField.touched, validating: currentField.validating } }); ``` --- #### **方法 3:结合 `getFieldsValue` 和 `setFieldsValue`** 先获取当前表单所有字段的值和状态,修改目标字段后再整体赋值,避免局部状态丢失。 **代码示例**: ```javascript const allFields = this.formRef.value.getFieldsValue(true); // 获取所有字段(含校验状态) allFields.fieldName = newValue; this.formRef.value.setFieldsValue(allFields); ``` --- #### **注意事项** - **校验触发的场景**:若需在赋值后主动校验,可手动调用 `validateFields` 方法,例如提交时[^2]。 - **动态表单的兼容性**:若表单字段通过 `v-for` 动态生成,需确保 `prop` 绑定正确,否则校验可能失效[^1]。 - **自定义校验规则**:若使用 `validator` 自定义校验逻辑,需确保规则中正确处理字段值的更新[^3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值