简体   繁体   English

如何避免以反应形式添加现场更换处理方法和其他样板?

[英]How to avoid adding field change handling methods and other boilerplates in react form?

I have a react based form with more than 10 fields in it, I have states for those ten form fields (controlled component) . 我有在它超过10个字段一个反应基的形式,我对这些十个 form字段(控制部件)。

Most of these input fields are of type text only but other types fields will be added later. 这些input字段中的大多数仅为text类型,但稍后将添加其他类型字段。

Problem is that I need to write 10 change handlers for them to set state correctly and then add method bindings for each of them in constructor . 问题是我需要为它们编写10个更改处理程序以正确设置状态,然后在构造函数中为每个处理程序添加方法绑定。

I am quite new to react and may be not aware about correct methodologies and techniques. 我很反应,可能不了解正确的方法和技术。

Please guide me to how to improve my current code structure and avoid writing boiler plates and repetitive error prone code. 请指导我如何改进我当前的代码结构,避免编写样板和重复的错误代码。

My current Registration component is like below - 我目前的注册组件如下 -

export default class Register extends Component {

    constructor(props){
        super(props);
        this.state = {
            regName              : '',
            regAdd1              : '',
            regAdd2              : '',
            regState             : '',
            regZipCode           : '',
            regCity              : '',
            regPhone             : ''
        };
        // add bindings .... ugh..
        this.changeRegAdd1 = this.changeRegAdd1.bind(this);
        this.changeRegAdd2 = this.changeRegAdd2.bind(this);
        //Similary binding for other handlers...
    }

    // add individual field change handlers ... ugh...
    changeRegName(e) {
        this.setState({regName:e.target.value});
    }

    changeRegAdd1(e) {
        this.setState({regAdd1:e.target.value});
    }

    changeRegAdd2(e) {
        this.setState({regAdd2:e.target.value});
    }

    changeRegState(e) {
        this.setState({regState:e.target.value});
    }


    // Similary for other change handler ....

    handleSubmit(e) {
        e.preventDefault();
        // validate then do other stuff
    }

    render(){

        let registrationComp = (
                <div className="row">
                    <div className="col-md-12">
                        <h3>Registration Form</h3>
                        <fieldset>
                              <div className="form-group">
                                <div className="col-xs-12">
                                    <label htmlFor="regName">Name</label>
                                    <input type="text" placeholder="Name"
                                        onChange={this.changeregName} value = {this.state.regName} className="form-control" required autofocus/>
                                </div>
                              </div>

                              <div className="form-group">
                                <div className="col-xs-12">
                                    <label htmlFor="regAdd1">Address Line1</label>

                                    <input
                                        type        = "text"
                                        placeholder = "Address Line1"
                                        onChange    = {this.changeregAdd1}
                                        value       = {this.state.regAdd1}
                                        className   = "form-control"
                                        required
                                        autofocus
                                    />

                                    <input
                                        type        = "text"
                                        placeholder = "Address Line2"
                                        onChange    = {this.changeregAdd2}
                                        value       = {this.state.regAdd2}
                                        className   = "form-control"
                                        required
                                        autofocus
                                    />
                                </div>
                              </div>

                              <div className="form-group">
                                <div className="col-xs-6">
                                    <label htmlFor="regState">State</label>
                                    <input
                                        type        = "text"
                                        placeholder = "State"
                                        onChange    = {this.changeregState}
                                        value       = {this.state.regState}
                                        className   = "form-control"
                                        required
                                        autofocus
                                    />
                                </div>
                                <div className="col-xs-6">
                                    <label htmlFor="regZipCode">Zip Code</label>
                                    <input
                                        type        = "text"
                                        placeholder = "Zip Code"
                                        onChange    = {this.changeregZipCode}
                                        value       = {this.state.regZipCode}
                                        className   = "form-control"
                                        required
                                        autofocus
                                    />
                                </div>
                              </div>

                              <div className="form-group">
                                <div className="col-xs-12">
                                    <label htmlFor="regCity">City</label>
                                    <input
                                        type        = "text"
                                        placeholder = "City"
                                        title       = "City"
                                        onChange    = {this.changeregCity}
                                        value       = {this.state.regCity}
                                        className   = "form-control"
                                        required
                                        autofocus
                                    />
                                </div>
                              </div>
                              {/* other form fields */}
                          </fieldset>
                      </div>
                    </div>
            );

            return  registrationComp;
    }
}

There can be other lots of ways to do it which I may not be aware. 可能有其他很多方法可以做到,我可能不知道。

But I prefer to do change handling in a common method for certain type of common fields such as <input type of text /> 但我更喜欢在某种常见字段的常用方法中进行变更处理,例如<input type of text />

This is a sample input field - 这是一个示例输入字段 -

  <input
      onChange    = {this.onChange}
      value       = {this.state.firstName}
      type        = "text"
      name        = {"firstName"}
  />

I keep both state's field name and input's "name" attribute same. 我保持状态的字段名称和输入的“名称”属性相同。 After that I write a common change handler for all such fields. 之后,我为所有这些字段编写了一个通用的更改处理程序

Need to write just one line in change handler. 需要在变更处理程序中只写一行。

onChange(e) {
    this.setState({[e.target.name]: e.target.value});
}

I am using es6's dynamic property setting from string as property name 我使用es6的动态属性设置从字符串作为属性名称

{ ["propName] : propValue }.

To avoid writing manual binding for methods in react component you can follow below two approaches - 为了避免为反应组件中的方法编写手动绑定,您可以遵循以下两种方法 -

  1. Use es6 arrow functions 使用es6箭头功能
  2. A hack ( I don't know whether this approach is fast or slow but It works :) ) 一个黑客(我不知道这种方法是快还是慢但它有效:))

create a method like this in your component. 在组件中创建这样的方法。

_bind(...methods) {
  methods.forEach( (method) => this[method] = this[method].bind(this) );
 }

use _bind to bind your methods. 使用_bind绑定您的方法。

constructor(props){
    super(props);
    this.state = {
        regName              : '',
        regAdd1              : '',
        regAdd2              : '',
        regState             : '',
        regZipCode           : '',
        regCity              : '',
        regPhone             : ''
    };
    // add bindings .... ugh..
    //this.changeRegAdd1 = this.changeRegAdd1.bind(this);
    //this.changeRegAdd2 = this.changeRegAdd2.bind(this);
    //Similary binding for other handlers...

    this._bind(
        'changeRegName',
        'changeReg1'    , 'changeRegAdd2'
        // and so on.
    );


}

EDIT : 编辑:

Adding validation - 添加验证 -

  1. Write a method which can iterate over state keys and check if state is empty. 编写一个可以迭代状态键并检查状态是否为空的方法。
  2. If any of the input field is empty collect the detail about that input and mark that required. 如果任何输入字段为空,请收集有关该输入的详细信息并标记所需。
  3. set a state to indicate that form has errors. 设置一个状态以指示表单有错误。 Specific error details can be found in state's error object. 可以在状态的错误对象中找到特定的错误详细信息。

     validateInput() { let errors = {}; Object.keys(this.state) .forEach((stateKey) => { isEmpty(this.state[stateKey]) ? (errors[stateKey] = `*required` ) : null; }); return { errors, isValid : isEmptyObj(errors) }; } isFormValid() { const { errors, isValid } = this.validateInput(); if (!isValid) { this.setState({ errors}); } return isValid; } onSubmit(e) { e.preventDefault(); this.setState({errors : {}}); if (this.isFormValid()) { // Perform form submission } } 

I have used to utility method calld isEmpty , isEmptyObj . 我习惯了实用方法calld isEmptyisEmptyObj They just check if object is null or undefined or field is empty. 它们只检查对象是null还是未定义或field是否为空。

I hope this helps. 我希望这有帮助。

You could create a higher order component to handle a lot of that for you. 您可以创建一个更高阶的组件来处理很多这样的组件。 Redux Form has a pretty good pattern you could model it after if you wanted. Redux Form有一个非常好的模式,你可以根据需要建模。

You would essentially end up with something like the following (I have not tested this at all, but it should work pretty well): 你基本上会得到类似下面的内容(我根本没有测试过,但它应该可以正常工作):

export class Field extends Component {
  handleChange = (event) => this.props.onChange(this.props.name, event.target.value)

  render() {
    const InputComponent = this.props.component
    const value = this.props.value || ''

    return (
      <InputComponent
        {...this.props}
        onChange={this.handleChange}
        value={value}
      />
  }
}

export default function createForm(WrappedComponent) {
  class Form extends Component {
    constructor() {
      super()

      this.state = this.props.initialValues || {}
      this.handleChange = this.handleChange.bind(this)
    }

    handleChange(name, value) {
      this.setState({
        [name]: value,
      })
    }

    render() {
      return (
        <WrappedComponent
          {...this.state}
          {...this.props}
          // pass anything you want to add here
          onChange={this.handleChange}
          values={this.state}
        />
      )
    }
  }

  return Form
}

Then you can augment this one component as necessary (add focus, blur, submit handlers, etc). 然后,您可以根据需要增加此组件(添加焦点,模糊,提交处理程序等)。 You would use it something like this: 你可以使用这样的东西:

import createForm, { Field } from './createForm'

// simplified Registration component
export class Registration extends Component {
  render() {
    return (
      <form>
        <Field
          component="input"
          name="name"
          onChange={this.props.onChange}
          type="text"
          value={this.props.values.name}
        />
      </form> 
    )
  }
}

export default createForm(Registration)

You could go crazy and get into context so you don't have to manually pass values and function around, but I would stay away at least until you're more familiar with React. 你可能会疯狂并进入上下文,所以你不必手动传递值和函数,但我会保持至少,直到你更熟悉React。

Also, if you don't want to manually bind functions, you could use the class properties transform if you're using babel. 此外,如果您不想手动绑定函数,则可以使用类属性转换(如果您使用的是babel)。 Then the Form component would just look like the following: 然后Form组件将如下所示:

class Form extends Component {
  state = this.props.initialValues || {}

  handleChange = (name, value) => this.setState({
    [name]: value,
  })

  render() {
    return (
      <WrappedComponent
        {...this.state}
        {...this.props}
        onChange={this.handleChange}
        values={this.state}
      />
    )
  }
}

Take a look at how it's done in NeoForm : 看看它在NeoForm中是如何完成的:

  • data state is directly mapped to form fields 数据状态直接映射到表单字段
  • one onChange handler for the entire form 一个onChange处理程序,用于整个表单
  • per-field (for example onBlur ) and form (for example onSubmit ) validation per-field(例如onBlur )和form(例如onSubmit )验证
  • plain object and Immutable state helpers 普通对象和不可变状态助手
  • easy integration with Redux or any other state management solution 与Redux或任何其他状态管理解决方案轻松集成

It uses context internally and really small and modular source code is easy to follow. 它在内部使用context ,非常小,模块化的源代码很容易遵循。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM