Higher Order Components(HOC) 的本质是Higher Order Functions,只不过返回值不是普通函数,而是React的component。利用HOC我们可以把相似的业务逻辑抽离出来,然后组件只负责渲染视图和组件相关的业务逻辑,而公用的业务逻辑则使用HOC中定义好的公共逻辑,以实现代码复用。本文将用两个简单的Form组件来演示HOC的基本使用方法。
运行效果
创建Form组件
首先我们来创建一个未使用HOC的Form组件:
import React from "react";
class PersonalInfoForm extends React.Component {
state = {
name: "",
age: 0
};
handleInputChange = event => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
};
handleSubmit = event => {
event.preventDefault();
console.log(this.state);
};
render() {
return (
<form>
<div>
<label>
Name:
<input
type="text"
value={this.state.name}
name="name"
onChange={this.handleInputChange}
/>
</label>
</div>
<div>
<label>
Age:
<input
type="number"
value={this.state.age}
name="age"
onChange={this.handleInputChange}
/>
</label>
</div>
<div>
<button onClick={this.handleSubmit}>Submit</button>
</div>
</form>
);
}
}
export default PersonalInfoForm;
复制代码
这里我们定义了姓名和年龄输入框和提交按钮,然后处理输入框值变化的回调函数handleInputChange
以及表单提交处理回调handleSubmit()
。
现在看来这个组件似乎没有什么问题,但是当我们的业务逻辑复杂起来,越来越多的Form组件加入进来之后,我们要为每个Form组件编写onChange和onSubmit的回调函数,以及表单验证处理。那么这时候我们就可以定义一个HOC组件来处理这些冗余的代码,实现一处定义多处使用。
Form HOC
新建一个Form.js
文件,在里边定义form()
函数,代码如下:
import React from "react";
export default function form(fields) {
return WrappedForm => {
return class extends React.Component {
state = { ...fields };
handleInputChange = event => {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
});
};
handleSubmit = (event, onSubmit) => {
event.preventDefault();
// do any default validations and other stuff
onSubmit();
};
render() {
return (
<WrappedForm
onChange={this.handleInputChange}
onSubmit={this.handleSubmit}
fields={{ ...this.state }}
{...this.props}
/>
);
}
};
};
}
复制代码
form()
函数接收一个fields参数,用来保存被包裹的组件的表单项,然后返回了一个React Component,在这个组件中定义了state,初始值为被包裹组件传递进来的fields的值,还定义了handleInputChange和handleSubmit方法。这里handleSubmit中可以定义一些诸如表单验证逻辑,然后它接收一个函数,即被包裹组件的特定的表单处理逻辑,比如发送请求到后台API,或者更新某个组件状态等。
然后在render()中我们直接渲染了被包裹的组件的样式,然后把handleInputChange、handleSubmit、当前的state、还有pass through props(即HOC不需要,但是被包裹的组件需要的props)传递给了被包裹的组件。
使用HOC
让我们新建一个<PersonalInfoFormWithHOC>
组件,代码如下:
import React from "react";
import form from "./Form";
function PersonalInfoFormWithHOC({ onChange, onSubmit, fields }) {
const handleSubmit = event => {
onSubmit(event, () => {
console.log(fields);
});
};
return (
<form>
<div>
<label>
Name:
<input
type="text"
value={fields.name}
name="name"
onChange={onChange}
/>
</label>
</div>
<div>
<label>
Age:
<input
type="number"
value={fields.age}
name="age"
onChange={onChange}
/>
</label>
</div>
<div>
<button onClick={handleSubmit}>Submit</button>
</div>
</form>
);
}
export default form({ name: "", age: 0 })(PersonalInfoFormWithHOC);
复制代码
因为有了HOC,我们不再需要state,所以这里定义成了函数式组件(functional component),然后通过HOC传递进来的props来处理表单数据。我们在最后export的时候调用了form
HOC,把初始的表单项的值传递给了它,而我们能够在HOC传递回来的fields prop来动态的更新input的value。最后我们定义了组件特定的处理表单提交的逻辑,这里只为演示用,就打印了用户填写的表单值。
然后设想又有一个新的表单组件<EmployeeForm>
加入进来,代码如下:
import React from "react";
import form from "./Form";
function EmployeeForm({ onChange, onSubmit, fields }) {
const handleSubmit = event => {
onSubmit(event, () => {
console.log(fields);
});
};
return (
<form>
<div>
<label>
Name:
<input
type="text"
value={fields.name}
name="name"
onChange={onChange}
/>
</label>
</div>
<div>
<label>
Position:
<input
type="text"
value={fields.position}
name="position"
onChange={onChange}
/>
</label>
</div>
<div>
<label>
Marital Status:
<select name="mstatus" value={fields.mstatus} onChange={onChange}>
<option value="">select</option>
<option value="single">Single</option>
<option value="married">Married</option>
</select>
</label>
</div>
<div>
<button onClick={handleSubmit}>Submit</button>
</div>
</form>
);
}
export default form({ name: "", position: "", mstatus: "" })(EmployeeForm);
复制代码
这里面我们只需定义特定的表单处理逻辑即可。而再新建form组件时,我们只关心如何布局表单项和表单处理逻辑即可。最后在index.js中引用这两个组件即可进行测试:
function App() {
return (
<div className="App">
{/*<PersonalInfoForm /> */}
<h1>Personal Info Form</h1>
<PersonalInfoFormWithHOC />
<h1>Employee Form</h1>
<EmployeeForm />
</div>
);
}
复制代码