【译】使用React和HTML5表单验证API处理表单元素
作者: Dmitry Sheiko | 译:甄玉磊
原文地址:[http://dsheiko.com/weblog/handling-forms-with-react-and-html5-form-validation-api]
【译者注:链接序号对应下面扩展阅读,另外可以点击阅读原文查看详细的链接文章】
简介:React 没有内置的表单验证逻辑,但是我们可以使用第三方解决方案。这种方法可能是开发包、表单生成器,还可能是 HOC 或者是包含校验逻辑的任意表单容器组件。那么选择哪种方法呢?我们将在本文中一 一介绍。
每当我们提及应用程序中的用户输入框时,首先映入脑海的就是 HTML 的表单元素。最早的 HTML 版本就已经支持 Web 表单。众所周知,这一特性于 1991 年提出,且在 1995 年作为 RFC(征求评议文件) 1866[1] 号协议标准化。与此同时表单元素也得到了广泛应用,几乎每一个代码库和框架中都在使用。那么在 React 中如何使用呢? Facebook 在如何处理表单上提供了受控的输入框[2]。该输入框指的是受控表单,主要是通过交互事件和通过 value 属性传递 state 值实现对输入框的控制。因此,你可以决定表单的校验和提交逻辑。拥有好的用户体验的 UI ,意味着你应该考虑到诸如“提交”、“校验”的逻辑以及内联的错误提示信息,根据有效性、原始状态和提交状态来切换元素。难道我们不能提取这种逻辑,简单的插入到表单元素中吗?当然可以,唯一要考虑的问题是我们要采用何种方法和解决方案。
组件库中的表单
如果你习惯使用诸如 ReactBootstrap
[3] 或者 AntDesign
[4] 等 React
组件库,很可能已经对其中的表单组件感到满意。这两个组件库所提供的表单组件满足了多种需求。例如在 AntDesign
组件库中我们定义了一个表单元素 Form
以及带有表单域的 FormItem
,即包裹了任意输入控制的容器。你可以在 FormItem
设置校验规则:
{getFieldDecorator('select', { rules: [ { required: true, message: 'Please select your country!' }, ], })( <select> <option value="china">China</option> <option value="use">U.S.A</option> </select> )}
举例来说,在处理表单提交时,你可以执行 this.props.form.validateFields()
来验证。这样看起来似乎所有事情都考虑到了。然而该解决方案是针对框架的。如果你没有使用这些组件库,则无法使用该方法。
基于schema构建表单
或者我们可以基于 JSON 细则构建单独的表单组件,例如我们可以引入 Winterfell
组件[5],构建如下所示的表单组件
<br />
然而这个方案是相当复杂的[6]。除此之外,我们还需要满足特定的语法。类似的另一种解决方案 react-jsonschema-form
[7],依赖于 JSON schema[8]。JSON schema 是一种与项目无关的数据文档,用来注释和校验 JSON 文档。但是,它可以将我们定义的特有属性应用在项目中,且在文档中定义。
Formsy(译者注:一种 React 表单验证组件)
我更倾向于使用一种具有逻辑校验,适用于任意 HTML 表单的组件。其中最常用的方法是—— Formsy[9]。是什么样的结构呢?我构建了自己的表单组件,并用HOC(Higher-Order Components,高阶组件)
Formsy 包裹起来:
import { withFormsy } from "formsy-react"; import React from "react"; class MyInput extends React.Component { changeValue = ( event ) => { this.props.setValue( event.currentTarget.value ); } render() { return ( <div><input type="text" value="{" /> { this.props.getErrorMessage() }</div> ); } } export default withFormsy( MyInput );
如上所示,组件的 props 属性中接收了 getErrorMessage() 函数,该函数可以用来生成行内错误提示信息。
这样我们开发了一个输入域组件,将其放入表单中如下所示:
import Formsy from "formsy-react"; import React from "react"; import MyInput from "./MyInput"; export default class App extends React.Component { onValid = () => { this.setState({ valid: true }); } onInvalid = () => { this.setState({ valid: false }); } submit( model ) { //... } render() { return ( <button disabled="disabled" type="submit">Submit</button> ); } }
我们指定所有需要验证的区域都会添加 validations
属性(详见 validations
列表属性[10])。使用 validationError
属性来设置所期望的校验信息,通过 onValid
和 onInvalid
接收表单校验状态。
这样看上去很简单、干净、灵活。但是我想知道的是为什么我们不依赖 HTML5 的表单验证[11],而是使用繁多的自定义实现呢?
HTML5表单验证
这项技术在很早之前就出现了,首次实现是在 2008 年的 Opera 9.5 中。现在所有的现代浏览器都支持它。表单(数据)验证[12]引入额外的 HTML 属性和 input 类型,这些可以用来设置表单的校验规则。这些校验还可以使用特有的 API [13]来控制和自定义表单JavaScript
。
让我们看下面的代码:
<br /><form><label for="answer">What do you know, Jon Snow?</label> <input id="answer" name="answer" required="" type="text" /> <button>Ask</button></form>``` 这是一个简单的表单,期望的功能—— `&lt;input&gt;` 元素有必填的属性。因此,如果我们快速按下提交按钮,表单内容不会提交到服务器。相反我们会看到在输入框旁出现提示信息,提示 `value` 值没有满足给出的限制条件(不能为空)。 <a href="http://img12.360buyimg.com/uba/jfs/t20722/99/1200275996/4305/bec1c2a/5b21df0aN9a9e5424.png"><img src="http://img12.360buyimg.com/uba/jfs/t20722/99/1200275996/4305/bec1c2a/5b21df0aN9a9e5424.png" /></a> 现在我们给输入框增加约束条件What do you know, Jon Snow?
Ask
这样 value 值不仅仅是必填值,还必须完全符合由 `pattern` 属性设置的规范。 <img src="http://img14.360buyimg.com/uba/jfs/t21367/238/1163573212/5237/b8bf8393/5b21df59N5b2c16b1.png" /> 错误提示信息并没有给出我们期望的信息,不是吗?我们可以自定义它(例如为了解释所期望的用户输入值)或者仅仅对输入值进行转换。What do you know, Jon Snow?
Ask
`
const answer = document.querySelector( "[name=answer]" ); answer.addEventListener( "input", ( event ) => { if ( answer.validity.patternMismatch ) { answer.setCustomValidity("Oh, it's not a right answer!"); } else { answer.setCustomValidity( "" ); } });
以上代码只是检查 input
框的输入事件,其校验状态 patternMismatch
的变化。当输入的值与定义的 pattern 不匹配时,就会出现错误提示信息。如果我们设置了其他的限制条件[14],也会被我们所定义的事件处理函数所覆盖。
你对这个工具提示不太满意?是的,它在不同的浏览器中表现形式是不一样的。让我们给表单元素增加novalidate
属性,并且自定义错误提示:
<br /><br /><br /><form><label for="answer">What do you know, Jon Snow?</label> <div></div> <button>Ask</button> </form>
const answer = document.querySelector( "[name=answer]" ), answerError = document.querySelector( "[name=answer] + [data-bind=message]" ); answer.addEventListener( "input", ( event ) => { answerError.innerHTML = answer.validationMessage; });
尽管这个介绍比较简短,你也能体会到技术背后的力量和灵活性。最重要的是,它是原生的表单验证。所以我们为什么要依赖繁多的自定义库。而不去使用原生的验证呢?
满足表单校验的 React API
react-html5-form 将 React(还可以选择Redux) 和 HTML5 表单校验 API 联系起来了。它提供 ‘From’ 组件和 ‘InputGroup’ 组件(类似于 Formsy 组件库中的 input,或者 AntDesign 组件库中的 FormItem )。这样,Form
组件定义了表单及其作用区域。InputGroup
组件可以包含一个或多个输入框。我们简单地用这些组件包裹一个任意的表单内容(只是普通的 HTML 或 React 组件)。在用户事件上,我们可以请求表单验证,并根据有效的输入值,获得 Form 和 InputGroup 组件的更新状态。
好了,让我们实践一下,首先我们定义表单区域:
import React from "react"; import { render } from "react-dom"; import { Form, InputGroup } from "Form"; const MyForm = props => ( <form>{({ error, valid, pristine, submitting, form }) => ( Form content <button disabled="disabled" type="submit">Submit</button> )}</form> ); render( , document.getElementById( "app" ) );
作用域接收到的状态对象具有以下属性:
– error- 表单错误信息(通常指的是服务端的校验信息),可以通过 form.setError()
进行设置;
– valid- 布尔值,表示所有的输入是否全部满足规定的约束;
– pristine- 布尔值,表示用户是否和表单进行过交互;
– submitting- 布尔值,表示是否正在提交表单(当用户按下提交按钮时该状态转化为 true,一旦用户定义的异步提交逻辑处理结束,该值变为 false)
– form- 用于访问表单组件 API 的表单实例;
在这里我们使用 pristine 和 submitting 属性将提交按钮切换为禁用状态。
为了在提交表单的同时校验表单输入信息,我们使用 InputGroup
包裹这些input
表单。
<br />{({ error, valid }) => ( <div><label>Email address</label> { error && ( <div>{error}</div> ) } </div> )}
使用 validate 属性我们可以指定该组内应该使用什么样的 input。[“email”] 意味着我们只有一个名称为“email”的input。
在这个作用区域内我们接收到的状态对象具有下面的属性:
– errors – 所有注册 input 的错误信息数组;
– error – 最后显示的错误信息;
– valid – 布尔值,表示是否所有的输入数据全部满足规定的约束条件;
– inputGroup – 访问表单组件 API 的实例;
渲染之后我们可以得到一个 email
类型的输入框。如果该值为空,或在提交时包含无效的电子邮件地址,则在输入框旁边显示相应的验证消息。
还记得我们在使用原生表单校验 API 自定义错误提示信息时的焦头烂额吗?使用 InputGroup
这一情况将变得好转:
<br />...
我们可以将每个 input 定义为 key-value 的哈希结构,其中 keys 表示有效性属性[15],values 表示自定义消息。
自定义消息很简单。那么自定义验证该如何实现呢?我们可以使用校验的 prop 来做到:
{ if ( !EMAIL_WHITELIST.includes( input.current.value ) ) { input.setCustomValidity( "Only whitelisted email allowed" ); return false; } return true; } }}> ...
在这个例子中,我们提供的是包含输入框名字的哈希而不是数组,其中 key 是输入框的名字, value 是验证处理函数。处理函数校验输入值(可以异步完成)并返回一个布尔类型的有效状态。使用 input.setCustomValidity,我们可以自定义验证消息。
输入框并不总是提交时才进行校验。为了达到实时验证,首先我们需要给输入事件定义事件处理函数:
const onInput = ( e, inputGroup ) => { inputGroup.checkValidityAndUpdate(); };
事实上,我们可以在用户每次输入时重新验证。控制逻辑如下所示:
<input name="email" required="" type="email" /> onInput( e, inputGroup, form ) } id="emailInput" />
这样,每当输入值改变时都会进行验证。如果输入值是无效的,我们就会马上收到错误的提示信息。
你可以通过这里找到以上示例的源代码[16]。
顺便说一句,你是否考虑过将组件派生的表单状态树和 Redux 存储值联系起来?我们也可以这样做。
Redux 提供了包含所有注册表单状态树的 reducer:html5form。我们可以按照如下所示将 html5form 和 store 结合起来:
import React from "react"; import { render } from "react-dom"; import { createStore, combineReducers } from "redux"; import { Provider } from "react-redux"; import { App } from "./Containers/App.jsx"; import { html5form } from "react-html5-form"; const appReducer = combineReducers({ html5form }); // Store creation const store = createStore( appReducer ); render( , document.getElementById( "app" ) );
当我们运行该应用程序时,就能在 store 中找到所有与表单相关的状态。
点击这里查看上述 demo 的源代码[17]。
总结
React 没有内置的表单验证逻辑,但是我们可以使用第三方解决方案。这种方法可能是开发包、表单生成器,还可能是 HOC 或者是包含校验逻辑的任意表单容器组件。我个人倾向于使用容器组件,该组件依赖于 HTML 内置表单验证 API ,并在表单和表单域的范围中显示有效性状态。
– react-html5-form 源代码[18]
– 演示例子链接[19]
扩展阅读:
[1] https://tools.ietf.org/html/rfc1866
[2] https://reactjs.org/docs/forms.html
[3] https://react-bootstrap.github.io/components/forms/
[4] https://ant.design/components/form/
[5] https://github.com/andrewhathaway/Winterfell
[6] https://github.com/andrewhathaway/Winterfell/blob/master/examples/schema.js
[7] https://github.com/mozilla-services/react-jsonschema-form
[8] http://json-schema.org/
[9] https://github.com/formsy/formsy-react
[10] https://github.com/formsy/formsy-react/blob/master/API.md#validators
[11] https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation
[12] https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation
[13] https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation
[14] https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation
[15] https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
[16] https://github.com/dsheiko/react-html5-form/blob/master/demo/bootstrap/src/index.jsx
[17] https://github.com/dsheiko/react-html5-form/blob/master/demo/bootstrap-redux/src/index.jsx
[18] https://github.com/dsheiko/react-html5-form
[19] https://dsheiko.github.io/react-html5-form
更多内容请关注我们团队的公众账号“全栈探索”
文章来源:
Author:开元
link:https://jdc.jd.com/archives/212629