使用基本工具在React.js中使用表单

引言


在使用React.js时,我经常不得不处理表单处理。 Redux-FormReact-Redux-Form经过我的双手,但是没有一个库让我完全满意。 我不喜欢表单的状态存储在reducer中 ,并且每个事件都通过动作创建者传递。 同样,根据Dan Abramov的说法, “表单的状态本质上是短暂的和局部的,因此您无需在Redux(或任何Flux库)中对其进行跟踪。”


我注意到在React-Redux-Form中有一个LocalForm组件,它允许您在不使用redux的情况下工作,但是我认为安装21.9kB库并使用少于一半的库是没有意义的。


我并不反对命名库,在特定情况下它们是不可替代的。 例如,与表单无关的第三方组件取决于输入的数据。 但是在我的文章中,我想考虑不需要redux的表单。


我开始使用组件的本地状态 ,并且出现了新的困难:代码量增加,组件失去可读性,出现了大量重复。


解决方案是高阶组件的概念。 简而言之,HOC是一个函数,它接收组件输入,并通过集成其他道具或修改后的道具将其更新后返回。 在React.js官方网站上阅读有关HOC的更多信息。 使用HOC概念的目的是将组件分为两部分,其中一部分负责逻辑,第二部分负责显示。


表格制作


例如,我们将创建一个简单的反馈表单,其中将包含3个字段:姓名,电子邮件,电话。


为简单起见,我们使用Create-React-App 。 全局安装:


npm i -g create-react-app 

然后在纯格式文件夹中创建您的应用程序


 create-react-app pure-form 

另外,安装prop-typesclassname ,它们将来对我们有用:


 npm i prop-types classnames -S 

创建两个文件夹/组件/容器/ components文件夹将包含负责显示的所有组件。 在/ container文件夹中,负责逻辑的组件。


/ components文件夹中,创建一个Input.jsx文件,在其中我们为所有输入声明一个公共组件。 在此阶段,重要的是不要忘记以优质的方式指定ProptTypesdefaultProps ,提供添加自定义类的可能性,并从PureComponent继承它以进行优化。
结果是:


 import React, { PureComponent } from 'react'; import cx from 'classnames'; import PropTypes from 'prop-types'; class Input extends PureComponent { render() { const { name, error, labelClass, inputClass, placeholder, ...props } = this.props; return ( <label className={cx('label', !!labelClass && labelClass)} htmlFor={`id-${name}`} > <span className="span">{placeholder}</span> <input className={cx( 'input', !!inputClass && inputClass, !!error && 'error' )} name={name} id={`id-${name}`} onFocus={this.handleFocus} onBlur={this.handleBlur} {...props} /> {!!error && <span className="errorText">{error}</span>} </label> ); } } Input.defaultProps = { type: 'text', error: '', required: false, autoComplete: 'off', labelClass: '', inputClass: '', }; Input.propTypes = { value: PropTypes.string.isRequired, name: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, placeholder: PropTypes.string.isRequired, error: PropTypes.string, type: PropTypes.string, required: PropTypes.bool, autoComplete: PropTypes.string, labelClass: PropTypes.string, inputClass: PropTypes.string, }; export default Input; 

接下来,在/ components文件夹中,创建文件Form.jsx ,在其中将声明包含表单的组件。 我们将通过道具接收所有使用它的方法以及输入 ,因此这里不需要状态 。 我们得到:


 import React, { Component } from 'react'; import PropTypes from 'prop-types'; import Input from './Input'; import FormWrapper from '../containers/FormWrapper'; class Form extends Component { render() { const { data: { username, email, phone }, errors, handleInput, handleSubmit, } = this.props; return ( <div className="openBill"> <form className="openBillForm" onSubmit={handleSubmit}> <Input key="username" value={username} name="username" onChange={handleInput} placeholder="" error={errors.username} required /> <Input key="phone" value={phone} name="phone" onChange={handleInput} placeholder="" error={errors.phone} required /> <Input key="email" value={email} type="email" name="email" onChange={handleInput} placeholder=" " error={errors.email} required /> <button type="submit" className="submitBtn">   </button> </form> </div> ); } } Form.propTypes = { data: PropTypes.shape({ username: PropTypes.string.isRequired, phone: PropTypes.string.isRequired, email: PropTypes.string.isRequired, }).isRequired, errors: PropTypes.shape({ username: PropTypes.string.isRequired, phone: PropTypes.string.isRequired, email: PropTypes.string.isRequired, }).isRequired, handleInput: PropTypes.func.isRequired, handleSubmit: PropTypes.func.isRequired, }; export default FormWrapper(Form); 

HOC创作


/ container文件夹中,创建FormWrapper.jsx文件。 我们在内部声明该函数,该函数将WrappedComponent组件作为参数并返回WrappedForm类。 此类的render方法返回WrappedComponent,其中集成了prop 。 尝试使用经典的函数声明,这将简化调试过程。


WrappedForm类中创建状态isFetching-用于控制异步请求的标志, 数据 -具有输入值的对象, 错误 -用于存储错误的对象。 声明的状态 传递WrappedComponent 。 因此,实现了将表单状态存储到更高级别的实现,这使得代码更加可读和透明。


 export default function Wrapper(WrappedComponent) { return class FormWrapper extends Component { state = { isFetching: false, data: { username: '', phone: '', email: '', }, errors: { username: '', phone: '', email: '', }, }; render() { return <WrappedComponent {...this.state} />; } }; } 

但是这种实现方式并不通用,因为您必须为每种表单创建自己的包装器。 您可以改进此系统,并将HOC嵌入另一个将形成初始状态值的函数中。


 import React, { Component } from 'react'; export default function getDefaultValues(initialState, requiredFields) { return function Wrapper(WrappedComponent) { return class WrappedForm extends Component { state = { isFetching: false, data: initialState, errors: requiredFields, }; render() { return <WrappedComponent {...this.state} {...this.props} />; } }; }; } 

在此函数中,您不仅可以传递state的初始值,而且还可以传递任何参数。 例如,可以在Form.jsx中创建表单的属性和方法。 这种实现的一个示例将是下一篇文章的主题。


在文件Form.jsx中,我们声明初始状态值并将其传递给HOC:


 const initialState = {    username: '',    phone: '',    email: '', }; export default FormWrapper(initialState, initialState)(Form); 

让我们创建一个handleInput方法来处理输入到输入中的值。 他收到事件 ,我们从中获取名称,然后将其传递给setState 。 由于输入值存储在数据对象中,因此我们setState中调用该函数。 在保存获得的值的同时,我们将变量字段的错误存储归零。 我们得到:


 handleInput = event => { const { value, name } = event.currentTarget; this.setState(({ data, errors }) => ({ data: { ...data, [name]: value, }, errors: { ...errors, [name]: '', }, })); }; 

现在,我们将创建handeSubmit方法来处理表单并在控制台中显示数据,但是在此之前,我们需要通过验证。 我们将仅验证必填字段,即this.state.errors对象的所有键。 我们得到:


 handleSubmit = e => {    e.preventDefault();    const { data } = this.state;    const isValid = Object.keys(data).reduce( (sum, item) => sum && this.validate(item, data[item]),    true    ); if (isValid) { console.log(data); } }; 

使用reduce方法,我们对所有必填字段进行排序。 在每次迭代中,都会调用validate方法,在其中传递一对namevalue 。 在方法内部,将检查输入数据的正确性,其结果将返回布尔类型。 如果至少一对值未通过验证,则isValid变量将变为false ,并且数据将不会输出到控制台,即不会处理该表单。 这里考虑一个简单的情况-检查非空格式。 验证方法:



 validate = (name, value) => { if (!value.trim()) { this.setState( ({ errors }) => ({ errors: { ...errors, [name]: '    ', }, }), () => false ); } else { return true; } }; 

handleSubmit和handleInput方法都必须传递给WrappedComponent


 render() {    return (    <WrappedComponent    {...this.state}       {...this.props}     handleInput={this.handleInput} handleSubmit={this.handleSubmit}    /> ); } 

结果,我们获得了现成的反馈表,其中包含简单的验证和错误输出。 同时,我们从负责显示的组件中删除了逻辑部分。


结论


因此,我们看了一个创建用于处理表单的HOC的基本示例。 创建表单时,仅使用简单的输入,不使用复杂的元素,例如下拉列表,复选框,单选按钮等。 如果可用,您可能必须创建其他事件处理方法。


在文章或我的邮件的评论中写下问题和评论。

一个完整的例子可以在这里找到: 纯反应形式

感谢您的关注!

Source: https://habr.com/ru/post/zh-CN421817/


All Articles