Javascript React + Redux - 在表单组件中处理 CRUD 的最佳方式是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33237818/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
React + Redux - What's the best way to handle CRUD in a form component?
提问by Mike Boutin
I got one form who is used to Create, Read, Update and Delete. I created 3 components with the same form but I pass them different props. I got CreateForm.js, ViewForm.js (readonly with the delete button) and UpdateForm.js.
我得到了一种用于创建、读取、更新和删除的表单。我创建了 3 个具有相同形式的组件,但我向它们传递了不同的道具。我得到了 CreateForm.js、ViewForm.js(带有删除按钮的只读)和 UpdateForm.js。
I used to work with PHP, so I always did these in one form.
我曾经使用 PHP,所以我总是以一种形式来做这些。
I use React and Redux to manage the store.
我使用 React 和 Redux 来管理商店。
When I'm in the CreateForm component, I pass to my sub-components this props createForm={true}
to not fill the inputs with a value and don't disable them. In my ViewForm component, I pass this props readonly="readonly"
.
当我在 CreateForm 组件中时,我将这个道具传递给我的子组件,createForm={true}
以不使用值填充输入并且不禁用它们。在我的 ViewForm 组件中,我传递了这个 props readonly="readonly"
。
And I got another problem with a textarea who is filled with a value and is not updatable. React textarea with value is readonly but need to be updated
我还遇到了一个 textarea 的问题,它充满了一个值并且不可更新。React textarea with value 是只读的,但需要更新
What's the best structure to have only one component which handles these different states of the form?
只有一个组件来处理表单的这些不同状态的最佳结构是什么?
Do you have any advice, tutorials, videos, demos to share?
你有什么建议、教程、视频、演示要分享吗?
采纳答案by Mike Boutin
I found the Redux Formpackage. It does a really good job!
我找到了Redux Form包。它做得非常好!
So, you can use Reduxwith React-Redux.
因此,您可以将Redux与React-Redux 一起使用。
First you have to create a form component (obviously):
首先,您必须创建一个表单组件(显然):
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;
After this, you connect the component which handles the form:
在此之后,您连接处理表单的组件:
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);
And add the redux-form reducer in your combined reducers:
并在您的组合减速器中添加 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;
And the validator module looks like this:
验证器模块如下所示:
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;
}
After the form is completed, when you want to fill all the fields with some values, you can use the initialize
function:
表单完成后,当您想用某些值填充所有字段时,可以使用该initialize
功能:
componentWillMount() {
this.props.dispatch(initialize('contact', {
name: 'test'
}, ['name', 'address', 'phone']));
}
Another way to populate the forms is to set the 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);
If you got any other way to handle this, just leave a message! Thank you.
如果您有任何其他方法来处理此问题,请留言!谢谢你。
回答by Ashley Coolman
UPDATE: its 2018 and I'll only ever use Formik(or Formik-like libraries)
更新:它是 2018 年,我只会使用Formik(或类似 Formik 的库)
There is also react-redux-form(step-by-step), which seems exchange some of redux-form's javascript (& boilerplate) with markup declaration. It looks good, but I've not used it yet.
还有react-redux-form( step-by-step),它似乎用标记声明交换了一些redux-form的 javascript (& 样板)。看起来不错,但我还没有使用它。
A cut and paste from the readme:
自述文件中的剪切和粘贴:
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
./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);
Edit: Comparison
编辑:比较
The react-redux-form docs provide a comparison vs redux-form:
react-redux-form 文档提供了与 redux-form 的比较:
https://davidkpiano.github.io/react-redux-form/docs/guides/compare-redux-form.html
https://davidkpiano.github.io/react-redux-form/docs/guides/compare-redux-form.html
回答by jasonslyvia
For those who doesn't care about an enormous library for handling form related issues, I would recommend redux-form-utils.
对于那些不关心处理表单相关问题的庞大库的人,我会推荐redux-form-utils。
It can generate value and change handlers for your form controls, generate reducers of the form, handy action creators to clear certain(or all) fields, etc.
它可以为您的表单控件生成值和更改处理程序,生成表单的减速器,方便的动作创建器以清除某些(或所有)字段等。
All you need to do is assemble them in your code.
您需要做的就是将它们组装到您的代码中。
By using redux-form-utils
, you end up with form manipulation like following:
通过使用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>
);
}
}
However, this library only solves problem C
and U
, for R
and D
, maybe a more integrated Table
component is to antipate.
然而,这个库只是解决了问题C
,并U
为R
和D
,也许更多的集成Table
组件是antipate。
回答by hindmost
Just another thing for those who want to create fully controlled form component without using oversized library.
对于那些想要在不使用超大库的情况下创建完全受控的表单组件的人来说,这又是另一回事。
ReduxFormHelper- a small ES6 class, less than 100 lines:
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}
}
}
It doesn't do all the work for you. However it facilitates creation, validation and handling of a controlled form component.
You may just copy & paste the above code into your project or instead, include the respective library - redux-form-helper
(plug!).
它不会为你做所有的工作。然而,它促进了受控表单组件的创建、验证和处理。您可以将上面的代码复制并粘贴到您的项目中,或者包含相应的库 - redux-form-helper
(插件!)。
How to use
如何使用
The first step is add specific data to Redux state which will represent the state of our form. These data will include current field values as well as set of error flags for each field in the form.
第一步是将特定数据添加到 Redux 状态,这将代表我们表单的状态。这些数据将包括当前字段值以及表单中每个字段的错误标志集。
The form state may be added to an existing reducer or defined in a separate reducer.
表单状态可以添加到现有的减速器中,也可以在单独的减速器中定义。
Furthermore it's necessary to define specific action initiating update of the form state as well as respective action creator.
此外,有必要定义启动表单状态更新的特定操作以及相应的操作创建者。
Action example:
动作示例:
export const FORM_UPDATE = 'FORM_UPDATE'
export const doFormUpdate = (data, errors) => {
return { type: FORM_UPDATE, data, errors }
}
...
Reducer example:
减速机示例:
...
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 || {})
}
...
}
}
The second and final step is create a container component for our form and connect it with respective part of Redux state and actions.
第二步也是最后一步是为我们的表单创建一个容器组件,并将其与 Redux 状态和操作的相应部分连接起来。
Also we need to define a form model specifying validation of form fields.
Now we instantiate ReduxFormHelper
object as a member of the component and pass there our form model and a callback dispatching update of the form state.
我们还需要定义一个表单模型,指定表单字段的验证。现在我们将ReduxFormHelper
对象实例化为组件的一个成员,并将我们的表单模型和一个发送表单状态更新的回调传递给那里。
Then in the component's render()
method we have to bind each field's onChange
and the form's onSubmit
events with processField()
and processForm()
methods respectively as well as display error blocks for each field depending on the form error flags in the state.
然后在组件的render()
方法中,我们必须分别使用和方法绑定每个字段onChange
和表单的onSubmit
事件,processField()
并processForm()
根据状态中的表单错误标志显示每个字段的错误块。
The example below uses CSS from Twitter Bootstrap framework.
下面的示例使用来自 Twitter Bootstrap 框架的 CSS。
Container Component example:
容器组件示例:
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)