将 React HOC 与表单一起使用。使用 HOC 传递通用 JSX 和状态
Using react HOC with a form. Passing common JSX and state down using a HOC
我刚刚完成了一个相当大的项目,该项目在 React 应用程序的不同部分使用了一个表单。整个表格是相同的,但功能会有所不同,具体取决于表格的使用位置。
所以现在我有多个具有重复表单的组件。所有这些表单都是受控制的,即(使用 this.state... 作为值)唯一的区别是渲染方法以及某些按钮事件中表单数据发生的情况。
我意识到这是可怕的编码,真的想使用 HOC 使这些组件更加灵活和简洁。
这是其中一种形式,还有2种类似。
sampleForm = (
<form action="" name="sample">
<div className="group">
<label htmlFor="">Descriptive Name:</label>
<input type="text" name="name" value={this.state.name}
onChange={this.handleChange} placeholder="Descriptive Name" />
</div>
<div className="group">
<label>Sample Codename:</label>
<input type="text" name="codeName" value={this.state.codeName}
onChange={this.handleChange} placeholder="Ex: MM_MG_01" />
</div>
<div className="group">
<label htmlFor="">GPS Coordinates:</label>
<input type="text" name="coords" value={this.state.coords}
onChange={this.handleChange} placeholder="GPS Coordinates" />
</div>
<div className="group">
<label htmlFor="">Metagenomic Data:</label>
<textarea type="text" name="METAdesc" value=
{this.state.METAdesc}
onChange={this.handleChange} placeholder="Image Description" rows={7} />
<input type="file" name="METAimage"
onChange={this.handleChange} />
</div>
{notes}
</form>
)
我目前在渲染方法中复制了这三个(四次:/)
如何将这三个传递给组件?
高阶组件是一种 React 模式,其中:
a function takes a Component and returns a new Component.
https://reactjs.org/docs/higher-order-components.html
HOC 可以为表单做的一件事是管理状态,处理 onChange
和 onSubmit
等事件。因此,考虑 Form
组件作为作为参数传递给您的 FormHandler
HOC 的功能组件。
例如,
在FormHandler.js
const withFormHandling = FormComponent => class extends Component {/* Component Logic */}
export default withFormHandling
在Form.js
import withFormHandling from './path/to/Components/FormHandler'
const Form = props => {/* Component Logic */}
export default withFormHanlding(Form);
那么我们如何处理多个不同表单的表单细节、道具和状态?
- 确定每个表单应具有的状态或道具
对于您的情况,可能是以下情况:
formAction
formName
handleChange
handleSubmit
inputNames
notes
errors
我会考虑传入 inputNames
和 errors
作为道具(它们应该在结构上匹配)。您可以在此处添加各种复杂性或保持简单。就个人而言,我在我的状态中保留了一个 fields
对象和一个 errors
对象,它们都具有匹配的键。一个用于维护用户输入的值,另一个用于存储字段验证的结果。
然后让我们填写我们的 HOC
const withFormHandling = FormComponent => class extends Component {
constructor(props) {
super(props)
this.state = {
fields: {...props.inputNames},
errors: {...props.errors},
selectedFile: null
}
this.handleChange = this.handleChange.bind(this);
this.handleFile = this.handleFile.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// https://codeburst.io/save-the-zombies-how-to-add-state-and-lifecycle-methods-to-stateless-react-components-1a996513866d
static get name() {
return Component.name;
}
handleChange(e) {
const target = e.target;
let value = target.type === 'checkbox' ? target.checked : target.value;
let name = target.name;
const fields = {...this.state.fields}, errors = {...this.state.errors};
const error = //create a validation function that returns an error based on field name and value
fields[name] = value;
errors[name] = error;
this.setState({ fields, errors })
}
handleFile(e) {
this.setState({selectedFile: event.target.files[0]})
}
handleSubmit(e) {
//validate form
//flatten fields into structure of data for form submission
//handle submission of data and also file data
}
render() {
return <FormComponent
{...this.props} //to access form specific props not handled by state
fields={this.state.fields}
errors={this.state.errors}
handleChange={this.handleChange}
handleFile={this.handleFile}
handleSubmit={this.handleSubmit}
/>
}
}
export default withFormHandling
此模式之所以有效,是因为返回组件的渲染函数渲染了作为参数传递给 HOC 函数的表单组件。
因此,您可以使用此 HOC 作为处理程序创建任意数量的表单。您可以考虑将表示表单输入结构的树传递到 HOC 中,以使其更加模块化和可重用。
现在,让我们为您提供的示例填写 Form.js
:
import withFormHandling from './path/to/Components/FormHandler'
const Form = ({formAction, formName, handleChange, handleFile, handleSubmit, fields, errors, notes}) => {
return (
<form action={formAction} name={formName} onSubmit={handleSubmit}>
<div className="group">
<label htmlFor="name">Descriptive Name:</label>
<input type="text" name="name" value={fields.name}
onChange={handleChange} placeholder="Descriptive Name" />
</div>
<div className="group">
<label htmlFor="codeName">Sample Codename:</label>
<input type="text" name="codeName" value={fields.codeName}
onChange={handleChange} placeholder="Ex: MM_MG_01" />
</div>
<div className="group">
<label htmlFor="coords">GPS Coordinates:</label>
<input type="text" name="coords" value={fields.coords}
onChange={handleChange} placeholder="GPS Coordinates" />
</div>
<div className="group">
<label htmlFor="METAdesc">Metagenomic Data:</label>
<textarea type="text" name="METAdesc" value=
{fields.METAdesc}
onChange={handleChange} placeholder="Image Description" rows={7} />
<input type="file" name="METAimage"
onChange={handleFile} />
</div>
{notes}
</form>
)
}
export default withFormHanlding(Form);
最后,在其他一些组件中,您可以随意调用 Form
组件,并传入独特的道具。
//...some other Component Render Method
// form one, with its own internal state managed by HOC
<Form formAction={'https://someendpoint1'} formName={"some form 1"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 1"}/>
// form two, with its own internal state managed by HOC
<Form formAction={'https://someendpoint2'} formName={"some form 2"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 2"}/>
// form three, with its own internal state managed by HOC
<Form formAction={'https://someendpoint3'} formName={"some form 3"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 3"}/>
这就是我处理具有许多具有相似结构的不同形式的应用程序的方式。
要考虑的另一种模式是 render props
,但我会把它留给另一个问题和另一个受访者。
我刚刚完成了一个相当大的项目,该项目在 React 应用程序的不同部分使用了一个表单。整个表格是相同的,但功能会有所不同,具体取决于表格的使用位置。
所以现在我有多个具有重复表单的组件。所有这些表单都是受控制的,即(使用 this.state... 作为值)唯一的区别是渲染方法以及某些按钮事件中表单数据发生的情况。
我意识到这是可怕的编码,真的想使用 HOC 使这些组件更加灵活和简洁。
这是其中一种形式,还有2种类似。
sampleForm = (
<form action="" name="sample">
<div className="group">
<label htmlFor="">Descriptive Name:</label>
<input type="text" name="name" value={this.state.name}
onChange={this.handleChange} placeholder="Descriptive Name" />
</div>
<div className="group">
<label>Sample Codename:</label>
<input type="text" name="codeName" value={this.state.codeName}
onChange={this.handleChange} placeholder="Ex: MM_MG_01" />
</div>
<div className="group">
<label htmlFor="">GPS Coordinates:</label>
<input type="text" name="coords" value={this.state.coords}
onChange={this.handleChange} placeholder="GPS Coordinates" />
</div>
<div className="group">
<label htmlFor="">Metagenomic Data:</label>
<textarea type="text" name="METAdesc" value=
{this.state.METAdesc}
onChange={this.handleChange} placeholder="Image Description" rows={7} />
<input type="file" name="METAimage"
onChange={this.handleChange} />
</div>
{notes}
</form>
)
我目前在渲染方法中复制了这三个(四次:/)
如何将这三个传递给组件?
高阶组件是一种 React 模式,其中:
a function takes a Component and returns a new Component. https://reactjs.org/docs/higher-order-components.html
HOC 可以为表单做的一件事是管理状态,处理 onChange
和 onSubmit
等事件。因此,考虑 Form
组件作为作为参数传递给您的 FormHandler
HOC 的功能组件。
例如,
在FormHandler.js
const withFormHandling = FormComponent => class extends Component {/* Component Logic */}
export default withFormHandling
在Form.js
import withFormHandling from './path/to/Components/FormHandler'
const Form = props => {/* Component Logic */}
export default withFormHanlding(Form);
那么我们如何处理多个不同表单的表单细节、道具和状态?
- 确定每个表单应具有的状态或道具
对于您的情况,可能是以下情况:
formAction
formName
handleChange
handleSubmit
inputNames
notes
errors
我会考虑传入 inputNames
和 errors
作为道具(它们应该在结构上匹配)。您可以在此处添加各种复杂性或保持简单。就个人而言,我在我的状态中保留了一个 fields
对象和一个 errors
对象,它们都具有匹配的键。一个用于维护用户输入的值,另一个用于存储字段验证的结果。
然后让我们填写我们的 HOC
const withFormHandling = FormComponent => class extends Component {
constructor(props) {
super(props)
this.state = {
fields: {...props.inputNames},
errors: {...props.errors},
selectedFile: null
}
this.handleChange = this.handleChange.bind(this);
this.handleFile = this.handleFile.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
// https://codeburst.io/save-the-zombies-how-to-add-state-and-lifecycle-methods-to-stateless-react-components-1a996513866d
static get name() {
return Component.name;
}
handleChange(e) {
const target = e.target;
let value = target.type === 'checkbox' ? target.checked : target.value;
let name = target.name;
const fields = {...this.state.fields}, errors = {...this.state.errors};
const error = //create a validation function that returns an error based on field name and value
fields[name] = value;
errors[name] = error;
this.setState({ fields, errors })
}
handleFile(e) {
this.setState({selectedFile: event.target.files[0]})
}
handleSubmit(e) {
//validate form
//flatten fields into structure of data for form submission
//handle submission of data and also file data
}
render() {
return <FormComponent
{...this.props} //to access form specific props not handled by state
fields={this.state.fields}
errors={this.state.errors}
handleChange={this.handleChange}
handleFile={this.handleFile}
handleSubmit={this.handleSubmit}
/>
}
}
export default withFormHandling
此模式之所以有效,是因为返回组件的渲染函数渲染了作为参数传递给 HOC 函数的表单组件。
因此,您可以使用此 HOC 作为处理程序创建任意数量的表单。您可以考虑将表示表单输入结构的树传递到 HOC 中,以使其更加模块化和可重用。
现在,让我们为您提供的示例填写 Form.js
:
import withFormHandling from './path/to/Components/FormHandler'
const Form = ({formAction, formName, handleChange, handleFile, handleSubmit, fields, errors, notes}) => {
return (
<form action={formAction} name={formName} onSubmit={handleSubmit}>
<div className="group">
<label htmlFor="name">Descriptive Name:</label>
<input type="text" name="name" value={fields.name}
onChange={handleChange} placeholder="Descriptive Name" />
</div>
<div className="group">
<label htmlFor="codeName">Sample Codename:</label>
<input type="text" name="codeName" value={fields.codeName}
onChange={handleChange} placeholder="Ex: MM_MG_01" />
</div>
<div className="group">
<label htmlFor="coords">GPS Coordinates:</label>
<input type="text" name="coords" value={fields.coords}
onChange={handleChange} placeholder="GPS Coordinates" />
</div>
<div className="group">
<label htmlFor="METAdesc">Metagenomic Data:</label>
<textarea type="text" name="METAdesc" value=
{fields.METAdesc}
onChange={handleChange} placeholder="Image Description" rows={7} />
<input type="file" name="METAimage"
onChange={handleFile} />
</div>
{notes}
</form>
)
}
export default withFormHanlding(Form);
最后,在其他一些组件中,您可以随意调用 Form
组件,并传入独特的道具。
//...some other Component Render Method
// form one, with its own internal state managed by HOC
<Form formAction={'https://someendpoint1'} formName={"some form 1"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 1"}/>
// form two, with its own internal state managed by HOC
<Form formAction={'https://someendpoint2'} formName={"some form 2"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 2"}/>
// form three, with its own internal state managed by HOC
<Form formAction={'https://someendpoint3'} formName={"some form 3"} inputNames={{name:'', codename:''}} errors={{name:'', codename:''}} notes={"some notes 3"}/>
这就是我处理具有许多具有相似结构的不同形式的应用程序的方式。
要考虑的另一种模式是 render props
,但我会把它留给另一个问题和另一个受访者。