React + Redux - 在表单组件中处理 CRUD 的最佳方式是什么?
React + Redux - What's the best way to handle CRUD in a form component?
我得到了一个用于创建、读取、更新和删除的表单。我创建了 3 个具有相同形式的组件,但我向它们传递了不同的道具。我得到 CreateForm.js、ViewForm.js(使用删除按钮只读)和 UpdateForm.js。
我以前和 PHP 一起工作,所以我总是以一种形式做这些。
我使用 React 和 Redux 来管理商店。
当我在 CreateForm 组件中时,我将此道具 createForm={true}
传递给我的子组件,以不使用值填充输入并且不禁用它们。在我的 ViewForm 组件中,我传递了这个道具 readonly="readonly"
。
我遇到了另一个问题,文本区域填充了一个值并且不可更新。 React textarea with value is readonly but need to be updated
只有一个组件来处理表单的这些不同状态的最佳结构是什么?
您有任何建议、教程、视频、演示要分享吗?
我找到了 Redux Form 包。它做得非常好!
因此,您可以使用 Redux with React-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 形式的减速器:
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']));
}
填充表单的另一种方法是设置初始值。
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);
如果您还有其他处理方法,请留言!谢谢。
更新:现在是 2018 年,我只会使用 Formik(或类似 Formik 的库)
还有 react-redux-form (step-by-step), which seems exchange some of redux-form 的 javascript(和样板文件)带有标记声明。看着不错,还没用过
自述文件中的剪切和粘贴:
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
对于那些不关心处理表单相关问题的庞大库的人,我会推荐 redux-form-utils。
它可以为您的表单控件生成值和更改处理程序、生成表单的缩减器、方便的动作创建器以清除某些(或所有)字段等。
您需要做的就是 assemble 在您的代码中添加它们。
通过使用 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>
);
}
}
但是,这个库只解决了问题C
和U
,对于R
和D
,也许一个更集成的Table
组件是antipate .
对于那些想要在不使用超大库的情况下创建完全受控的表单组件的人来说,这是另一回事。
ReduxFormHelper - 一个小的 ES6 class,不到 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 状态,这将代表我们表单的状态。
这些数据将包括当前字段值以及表单中每个字段的一组错误标志。
表单状态可以添加到现有的缩减器或在单独的缩减器中定义。
此外,有必要定义启动表单状态更新的特定操作以及相应的操作创建者。
动作示例:
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()
方法中我们必须分别使用 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)
我得到了一个用于创建、读取、更新和删除的表单。我创建了 3 个具有相同形式的组件,但我向它们传递了不同的道具。我得到 CreateForm.js、ViewForm.js(使用删除按钮只读)和 UpdateForm.js。
我以前和 PHP 一起工作,所以我总是以一种形式做这些。
我使用 React 和 Redux 来管理商店。
当我在 CreateForm 组件中时,我将此道具 createForm={true}
传递给我的子组件,以不使用值填充输入并且不禁用它们。在我的 ViewForm 组件中,我传递了这个道具 readonly="readonly"
。
我遇到了另一个问题,文本区域填充了一个值并且不可更新。 React textarea with value is readonly but need to be updated
只有一个组件来处理表单的这些不同状态的最佳结构是什么?
您有任何建议、教程、视频、演示要分享吗?
我找到了 Redux Form 包。它做得非常好!
因此,您可以使用 Redux with React-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 形式的减速器:
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']));
}
填充表单的另一种方法是设置初始值。
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);
如果您还有其他处理方法,请留言!谢谢。
更新:现在是 2018 年,我只会使用 Formik(或类似 Formik 的库)
还有 react-redux-form (step-by-step), which seems exchange some of redux-form 的 javascript(和样板文件)带有标记声明。看着不错,还没用过
自述文件中的剪切和粘贴:
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
对于那些不关心处理表单相关问题的庞大库的人,我会推荐 redux-form-utils。
它可以为您的表单控件生成值和更改处理程序、生成表单的缩减器、方便的动作创建器以清除某些(或所有)字段等。
您需要做的就是 assemble 在您的代码中添加它们。
通过使用 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>
);
}
}
但是,这个库只解决了问题C
和U
,对于R
和D
,也许一个更集成的Table
组件是antipate .
对于那些想要在不使用超大库的情况下创建完全受控的表单组件的人来说,这是另一回事。
ReduxFormHelper - 一个小的 ES6 class,不到 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 状态,这将代表我们表单的状态。 这些数据将包括当前字段值以及表单中每个字段的一组错误标志。
表单状态可以添加到现有的缩减器或在单独的缩减器中定义。
此外,有必要定义启动表单状态更新的特定操作以及相应的操作创建者。
动作示例:
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()
方法中我们必须分别使用 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)