如何避免以 React 形式添加字段更改处理方法和其他样板文件?

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

我有一个基于反应的表单,里面有超过 10 个字段,我有 states 用于那十个 form 字段 (受控组件) .

其中大多数 input 字段仅属于 text 类型,但稍后将添加其他类型的字段。

问题是我需要为它们编写 10 个更改处理程序 以正确设置状态,然后在 constructor[=29 中为每个处理程序添加方法绑定=].

I am quite new to react and may be not aware about correct methodologies and techniques.

请指导我如何改进我当前的代码结构,避免编写样板代码和重复容易出错的代码。

我当前的注册组件如下 -

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;
    }
}

还有很多我可能不知道的其他方法。

但我更喜欢在通用方法中对某些类型的通用字段进行更改处理,例如 <input type of text />

这是一个示例输入字段 -

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

我保持状态的字段名称和输入的 "name" 属性相同。 之后,我为所有这些字段编写了一个通用更改处理程序

只需在更改处理程序中写入一行。

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

我正在使用 es6 的动态 属性 字符串设置作为 属性 名称

{ ["propName] : propValue }.

为了避免为 React 组件中的方法编写手动绑定,您可以遵循以下两种方法 -

  1. 使用 es6 箭头函数
  2. 黑客攻击(我不知道这种方法是快还是慢,但它有效:))

在您的组件中创建一个这样的方法。

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

使用 _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.
    );


}

编辑:

添加验证 -

  1. 编写一个可以遍历状态键并检查状态是否为空的方法。
  2. 如果任何输入字段为空,请收集有关该输入的详细信息并标记为必填项。
  3. 设置一个状态来指示表单有错误。具体的错误详情可以在状态的错误对象中找到。

    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
         }
      }
    

我已经习惯了调用isEmptyisEmptyObj的实用方法。他们只是检查对象是否为空或未定义或字段是否为空。

希望对您有所帮助。

您可以创建一个更高阶的组件来为您处理很多这样的事情。 Redux Form 有一个很好的模式,如果你愿意,你可以模仿它。

你基本上会得到类似下面的结果(我根本没有测试过这个,但它应该工作得很好):

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
}

然后您可以根据需要扩充这一组件(添加焦点、模糊、提交处理程序等)。你会像这样使用它:

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)

你可能会发疯并进入 context 这样你就不必手动传递值和函数,但至少在你更熟悉 React 之前我会远离它。

此外,如果您不想手动绑定函数,您可以使用 the class properties transform 如果您使用的是 babel。那么 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}
      />
    )
  }
}

看看NeoForm是怎么做的:

  • 数据状态直接映射到表单字段
  • 一个 onChange 整个表单的处理程序
  • 每字段(例如onBlur)和表单(例如onSubmit)验证
  • 普通对象和不可变状态助手
  • 与 Redux 或任何其他状态管理解决方案轻松集成

它在内部使用 context,非常小且模块化的源代码很容易理解。