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

时间:2015-10-20 13:21:00

标签: javascript reactjs crud redux

我有一个用于创建,读取,更新和删除的表单。我创建了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 个答案:

答案 0 :(得分:114)

我找到了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 reducer:

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

如果您有其他方法可以处理此问题,请留言!谢谢。

答案 1 :(得分:10)

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

还有react-redux-formstep-by-step),它似乎与redux-form的javascript(&amp;样板文件)交换了一些标记声明。它看起来不错,但我还没有使用它。

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

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 docs提供了与redux-form的比较:

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

答案 2 :(得分: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组件是反对。< / p>

答案 3 :(得分:1)

对于那些想要在不使用超大库的情况下创建完全控制的表单组件的人来说,这是另一回事。

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

它不会为你完成所有的工作。然而,它有助于创建,验证和处理受控形式组件。 你可以复制&amp;将上述代码粘贴到您的项目中,或者包含相应的库 - redux-form-helper(插件!)。

如何使用

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

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

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

动作示例

export const FORM_UPDATE = 'FORM_UPDATE' 

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

Reducer示例

...
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()方法中,我们必须使用{{1}将每个字段onChange和表单的onSubmit事件绑定在一起}和processField()方法分别显示每个字段的错误块,具体取决于状态中的表单错误标志。

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

容器组件示例

processForm()

Demo