首页 文章

React Redux - 在表单组件中处理CRUD的最佳方法是什么?

提问于
浏览
122

我有一个用于创建,读取,更新和删除的表单 . 我创建了3个具有相同形式的组件,但我传递了不同的道具 . 我有CreateForm.js,ViewForm.js(只读取删除按钮)和UpdateForm.js .

我曾经使用PHP,所以我总是以一种形式做这些 .

我使用React和Redux来管理商店 .

当我在CreateForm组件中时,我传递给我的子组件,这使得 createForm={true} 不用输入值填充输入,不要禁用它们 . 在我的ViewForm组件中,我传递了这个道具 readonly="readonly" .

我有一个textarea的另一个问题,它充满了一个值,并且不可更新 . React textarea with value is readonly but need to be updated

只有一个组件处理表单的这些不同状态的最佳结构是什么?

你有任何建议,教程,视频,演示分享?

4 回答

  • 111

    对于那些想要创建完全受控的表单组件而不使用超大库的人来说,这是另一回事 .

    ReduxFormHelper - 一个小的ES6类,少于100行:

    class ReduxFormHelper {
      constructor(props = {}) {
        let {formModel, onUpdateForm} = props
        this.props = typeof formModel === 'object' &&
          typeof onUpdateForm === 'function' && {formModel, onUpdateForm}
      }
    
      resetForm (defaults = {}) {
        if (!this.props) return false
        let {formModel, onUpdateForm} = this.props
        let data = {}, errors = {_flag: false}
        for (let name in formModel) {
          data[name] = name in defaults? defaults[name] :
            ('default' in formModel[name]? formModel[name].default : '')
          errors[name] = false
        }
        onUpdateForm(data, errors)
      }
    
      processField (event) {
        if (!this.props || !event.target) return false
        let {formModel, onUpdateForm} = this.props
        let {name, value, error, within} = this._processField(event.target, formModel)
        let data = {}, errors = {_flag: false}
        if (name) {
          value !== false && within && (data[name] = value)
          errors[name] = error
        }
        onUpdateForm(data, errors)
        return !error && data
      }
    
      processForm (event) {
        if (!this.props || !event.target) return false
        let form = event.target
        if (!form || !form.elements) return false
        let fields = form.elements
        let {formModel, onUpdateForm} = this.props
        let data = {}, errors = {}, ret = {}, flag = false
        for (let n = fields.length, i = 0; i < n; i++) {
          let {name, value, error, within} = this._processField(fields[i], formModel)
          if (name) {
            value !== false && within && (data[name] = value)
            value !== false && !error && (ret[name] = value)
            errors[name] = error
            error && (flag = true)
          }
        }
        errors._flag = flag
        onUpdateForm(data, errors)
        return !flag && ret
      }
    
      _processField (field, formModel) {
        if (!field || !field.name || !('value' in field))
          return {name: false, value: false, error: false, within: false}
        let name = field.name
        let value = field.value
        if (!formModel || !formModel[name])
          return {name, value, error: false, within: false}
        let model = formModel[name]
        if (model.required && value === '')
          return {name, value, error: 'missing', within: true}
        if (model.validate && value !== '') {
          let fn = model.validate
          if (typeof fn === 'function' && !fn(value))
            return {name, value, error: 'invalid', within: true}
        }
        if (model.numeric && isNaN(value = Number(value)))
          return {name, value: 0, error: 'invalid', within: true}
        return {name, value, error: false, within: true}
      }
    }
    

    它并没有为你完成所有的工作 . 但是,它有助于创建,验证和处理受控表单组件 . 您可以将上述代码复制并粘贴到项目中,或者包含相应的库 - redux-form-helper(插件!) .

    如何使用

    第一步是将特定数据添加到Redux状态,它将表示我们表单的状态 . 这些数据将包括当前字段值以及表单中每个字段的错误标记集 .

    表单状态可以添加到现有的reducer中,也可以在单独的reducer中定义 .

    此外,有必要定义启动表单状态更新的特定操作以及相应的操作创建者 .

    动作示例:

    export const FORM_UPDATE = 'FORM_UPDATE' 
    
    export const doFormUpdate = (data, errors) => {
      return { type: FORM_UPDATE, data, errors }
    }
    ...
    

    减速器示例:

    ...
    const initialState = {
      formData: {
        field1: '',
        ...
      },
      formErrors: {
      },
      ...
    }
    
    export default function reducer (state = initialState, action) {
      switch (action.type) {
        case FORM_UPDATE:
          return {
            ...ret,
            formData: Object.assign({}, formData, action.data || {}),
            formErrors: Object.assign({}, formErrors, action.errors || {})
          }
        ...
      }
    }
    

    第二步也是最后一步是为表单创建一个容器组件,并将其与Redux状态和操作的相应部分相连接 .

    我们还需要定义一个表单模型,指定表单字段的验证 . 现在我们将 ReduxFormHelper 对象实例化为组件的成员,并传递我们的表单模型和表单状态的回调调度更新 .

    然后在组件的 render() 方法中,我们必须分别用 processField()processForm() 方法绑定每个字段的 onChange 和表单的 onSubmit 事件,并根据状态中的表单错误标志显示每个字段的错误块 .

    以下示例使用Twitter Bootstrap框架中的CSS .

    容器组件示例:

    import React, {Component} from 'react';
    import {connect} from 'react-redux'
    import ReduxFormHelper from 'redux-form-helper'
    
    class MyForm extends Component {
      constructor(props) {
        super(props);
        this.helper = new ReduxFormHelper(props)
        this.helper.resetForm();
      }
    
      onChange(e) {
        this.helper.processField(e)
      }
    
      onSubmit(e) {
        e.preventDefault()
        let {onSubmitForm} = this.props
        let ret = this.helper.processForm(e)
        ret && onSubmitForm(ret)
      }
    
      render() {
        let {formData, formErrors} = this.props
        return (
      <div>
        {!!formErrors._flag &&
          <div className="alert" role="alert">
            Form has one or more errors.
          </div>
        }
        <form onSubmit={this.onSubmit.bind(this)} >
          <div className={'form-group' + (formErrors['field1']? ' has-error': '')}>
            <label>Field 1 *</label>
            <input type="text" name="field1" value={formData.field1} onChange={this.onChange.bind(this)} className="form-control" />
            {!!formErrors['field1'] &&
            <span className="help-block">
              {formErrors['field1'] === 'invalid'? 'Must be a string of 2-50 characters' : 'Required field'}
            </span>
            }
          </div>
          ...
          <button type="submit" className="btn btn-default">Submit</button>
        </form>
      </div>
        )
      }
    }
    
    const formModel = {
      field1: {
        required: true,
        validate: (value) => value.length >= 2 && value.length <= 50
      },
      ...
    }
    
    function mapStateToProps (state) {
      return {
        formData: state.formData, formErrors: state.formErrors,
        formModel
      }
    }
    
    function mapDispatchToProps (dispatch) {
      return {
        onUpdateForm: (data, errors) => {
          dispatch(doFormUpdate(data, errors))
        },
        onSubmitForm: (data) => {
          // dispatch some action which somehow updates state with form data
        }
      }
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(MyForm)
    

    Demo

  • 1

    我找到了Redux Form包 . 它确实做得很好!

    因此,您可以将ReduxReact-Redux一起使用 .

    首先,您必须创建一个表单组件(显然):

    import React from 'react';
    import { reduxForm } from 'redux-form';
    import validateContact from '../utils/validateContact';
    
    class ContactForm extends React.Component {
      render() {
        const { fields: {name, address, phone}, handleSubmit } = this.props;
        return (
          <form onSubmit={handleSubmit}>
            <label>Name</label>
            <input type="text" {...name}/>
            {name.error && name.touched && <div>{name.error}</div>}
    
            <label>Address</label>
            <input type="text" {...address} />
            {address.error && address.touched && <div>{address.error}</div>}
    
            <label>Phone</label>
            <input type="text" {...phone}/>
            {phone.error && phone.touched && <div>{phone.error}</div>}
    
            <button onClick={handleSubmit}>Submit</button>
          </form>
        );
      }
    }
    
    ContactForm = reduxForm({
      form: 'contact',                      // the name of your form and the key to
                                            // where your form's state will be mounted
      fields: ['name', 'address', 'phone'], // a list of all your fields in your form
      validate: validateContact             // a synchronous validation function
    })(ContactForm);
    
    export default ContactForm;
    

    在此之后,您连接处理表单的组件:

    import React from 'react';
    import { connect } from 'react-redux';
    import { initialize } from 'redux-form';
    import ContactForm from './ContactForm.react';
    
    class App extends React.Component {
    
      handleSubmit(data) {
        console.log('Submission received!', data);
        this.props.dispatch(initialize('contact', {})); // clear form
      }
    
      render() {
        return (
          <div id="app">
            <h1>App</h1>
            <ContactForm onSubmit={this.handleSubmit.bind(this)}/>
          </div>
        );
      }
    
    }
    
    export default connect()(App);
    

    并在您的组合减速器中添加redux-form减速器:

    import { combineReducers } from 'redux';
    import { appReducer } from './app-reducers';
    import { reducer as formReducer } from 'redux-form';
    
    let reducers = combineReducers({
      appReducer, form: formReducer // this is the form reducer
    });
    
    export default reducers;
    

    验证器模块如下所示:

    export default function validateContact(data, props) {
      const errors = {};
      if(!data.name) {
        errors.name = 'Required';
      }
      if(data.address && data.address.length > 50) {
        errors.address = 'Must be fewer than 50 characters';
      }
      if(!data.phone) {
        errors.phone = 'Required';
      } else if(!/\d{3}-\d{3}-\d{4}/.test(data.phone)) {
        errors.phone = 'Phone must match the form "999-999-9999"'
      }
      return errors;
    }
    

    表单完成后,如果要使用某些值填充所有字段,可以使用 initialize 函数:

    componentWillMount() {
      this.props.dispatch(initialize('contact', {
        name: 'test'
      }, ['name', 'address', 'phone']));
    }
    

    填充表单的另一种方法是设置initialValues .

    ContactForm = reduxForm({
      form: 'contact',                      // the name of your form and the key to
      fields: ['name', 'address', 'phone'], // a list of all your fields in your form
      validate: validateContact             // a synchronous validation function
    }, state => ({
      initialValues: {
        name: state.user.name,
        address: state.user.address,
        phone: state.user.phone,
      },
    }))(ContactForm);
    

    如果你有任何其他方法来处理这个,只需留言!谢谢 .

  • 10

    更新:2018年,我只会使用Formik(或类似Formik的库)

    还有react-redux-formstep-by-step),这似乎交换了一些redux-form 's javascript (& boilerplate) with markup declaration. It looks good, but I'尚未使用它 .

    自述文件中的剪切和粘贴:

    import React from 'react';
    import { createStore, combineReducers } from 'redux';
    import { Provider } from 'react-redux';
    import { modelReducer, formReducer } from 'react-redux-form';
    
    import MyForm from './components/my-form-component';
    
    const store = createStore(combineReducers({
      user: modelReducer('user', { name: '' }),
      userForm: formReducer('user')
    }));
    
    class App extends React.Component {
      render() {
        return (
          <Provider store={ store }>
            <MyForm />
          </Provider>
        );
      }
    }
    

    ./components/my-form-component.js

    import React from 'react';
    import { connect } from 'react-redux';
    import { Field, Form } from 'react-redux-form';
    
    class MyForm extends React.Component {
      handleSubmit(val) {
        // Do anything you want with the form value
        console.log(val);
      }
    
      render() {
        let { user } = this.props;
    
        return (
          <Form model="user" onSubmit={(val) => this.handleSubmit(val)}>
            <h1>Hello, { user.name }!</h1>
            <Field model="user.name">
              <input type="text" />
            </Field>
            <button>Submit!</button>
          </Form>
        );
      }
    }
    
    export default connect(state => ({ user: state.user }))(MyForm);
    

    编辑:比较

    react-redux-form文档提供了与redux-form的比较:

    https://davidkpiano.github.io/react-redux-form/docs/guides/compare-redux-form.html

  • 4

    对于那些不关心处理表格相关问题的庞大图书馆的人,我建议redux-form-utils .

    它可以为表单控件生成值并更改处理程序,生成表单的reducers,方便的动作创建者以清除某些(或所有)字段等 .

    您需要做的就是在代码中组装它们 .

    通过使用 redux-form-utils ,您最终会得到如下表单操作:

    import { createForm } from 'redux-form-utils';
    
    @createForm({
      form: 'my-form',
      fields: ['name', 'address', 'gender']
    })
    class Form extends React.Component {
      render() {
        const { name, address, gender } = this.props.fields;
        return (
          <form className="form">
            <input name="name" {...name} />
            <input name="address" {...address} />
            <select {...gender}>
              <option value="male" />
              <option value="female" />
            </select>
          </form>
        );
      }
    }
    

    但是,这个库只解决问题 CU ,对于 RD ,可能更集成的 Table 组件是反对 .

相关问题