高阶组件(简称HOC)接收React组件作为参数,并且返回一个新的React组件。高阶组件本质上也是一个函数,并不是一个组件,主要用来实现组件逻辑的抽象和复用。
import React, {Component} from 'react';
// 定义高阶组件(函数)
function withPersistentData(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
info: {
age: 18
}
};
}
componentWillMount() {
let data = {name: "aaayang"};
this.setState({data});
}
render() {
// 使用组件,并传递内容
return <WrappedComponent data={this.state.data} {...this.props} {...this.state.info}/>
}
}
}
// 定义自己的组件
class MyComponent extends Component {
render() {
// 可以直接拿到 data 啦,无需自己获取
return <div>{this.props.data.name}</div>
}
}
// 利用高阶组件和自己的组件生成一个新的带公用功能的组件
const MyComponentWithPersistentData = withPersistentData(MyComponent);
export default MyComponentWithPersistentData;
高阶组件的使用场景主要有 4 种
- 操纵 props
在被包装组件接收props前,高阶组件可以先拦截到props,对props执行增加、删除或修改的操作,然后将处理后的props再传递给被包装组件
- 通过 ref 访问组件实例
高阶组件通过ref获取被包装组件实例的引用,然后高阶组件就具备了直接操作被包装组件的属性或方法的能力
import React, {Component} from 'react';
function withRef(WrappedComponent) {
return class extends Component {
constructor(props) {
super(props);
this.someMethod = this.someMethod.bind(this);
}
someMethod() {
this.wrappedInstance.changeState();
}
render() {
//为被包装组件添加ref属性,从而获取该组件实例并赋值给this.wrappedInstance
return <div>
<WrappedComponent ref={instance => this.wrappedInstance = instance} {...this.props}/>
<button onClick={this.someMethod}>btn</button>
</div>
}
}
}
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
num: 0
};
}
changeState() {
this.setState({num: 666});
}
render() {
return <div>
{this.state.num}
</div>
}
}
const MyComponentwithRef = withRef(MyComponent);
export default MyComponentwithRef;
- 组件状态提升
高阶组件可以通过将被包装组件的状态及相应的状态处理方法提升到高阶组件自身内部实现被包装组件的无状态化。一个典型的场景是,利用高阶组件将原本受控组件需要自己维护的状态统一提升到高阶组件中
import React, {Component} from 'react';
function withControlledState(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ""
};
this.handleValueChange = this.handleValueChange.bind(this);
}
handleValueChange(event) {
this.setState({
value: event.target.value
});
}
render() {
// 保存受控组件需要使用的属性和事件处理函数
const newProps = {
controlledProps: {
value: this.state.value,
onChange: this.handleValueChange
}
};
return <WrappedComponent {...this.props} {...newProps}/>;
}
}
}
class MyComponent extends Component {
render() {
let {value, onChange} = this.props.controlledProps;
// 此时的MyComponent为无状态组件,状态由高阶组件维护
return <input value={value} onChange={onChange} />
}
}
const MyComponentWithControlledState = withControlledState(MyComponent);
export default MyComponentWithControlledState;
- 用其他元素包装组件
我们还可以在高阶组件渲染 WrappedComponent 时添加额外的元素,这种情况通常用于为 WrappedComponent 增加布局或修改样式
import React, {Component} from 'react';
function withRedBackground(WrappedComponent) {
return class extends Component {
render() {
return (
<div style={{backgroundColor: 'red'}}>
<WrappedComponent {...this.props}/>
</div>
);
}
}
}
// 定义组件
class MyComponent extends Component {
render() {
return <p>hello world</p>;
}
}
const MyComponentWithRedBackground = withRedBackground(MyComponent);
export default MyComponentWithRedBackground;
参数传递
高阶组件的参数并非只能是一个组件,它还可以接收其他参数
import React, {Component} from 'react';
function withPersistentData(WrappedComponent, name) {
return class extends Component {
componentWillMount() {
let data = {name};
this.setState({data});
}
render() {
return <WrappedComponent data={this.state.data} {...this.props}/>
}
}
}
// 定义组件
class MyComponent extends Component {
render() {
return <p>{this.props.data.name}</p>;
}
}
export const MyComponentWithPersistentData1 = withPersistentData(MyComponent, 'aaayang');
export const MyComponentWithPersistentData2 = withPersistentData(MyComponent, 'sherry');
但实际情况中,我们很少使用上面方式传递参数,而是采用更加灵活、更具通用性的函数形式,比较牛逼
import React, {Component} from 'react';
const withPersistentData = name => WrappedComponent => {
return class extends Component {
componentWillMount() {
let data = {name};
this.setState({data});
}
render() {
return <WrappedComponent data={this.state.data} {...this.props}/>
}
}
}
// 定义组件
class MyComponent extends Component {
render() {
return <p>{this.props.data.name}</p>;
}
}
export const MyComponentWithPersistentData1 = withPersistentData('aaayang')(MyComponent);
export const MyComponentWithPersistentData2 = withPersistentData('sherry')(MyComponent);
继承方式实现高阶组件
前面介绍的高阶组件的实现方式都是由高阶组件处理通用逻辑,然后将相关属性传递给被包装组件,我们称这种实现方式为属性代理。除了属性代理外,还可以通过继承方式实现高阶组件:通过继承被包装组件实现逻辑的复用。继承方式实现的高阶组件常用于渲染劫持。例如,当用户处于登录状态时,允许组件渲染;否则渲染一个空组件
import React, {Component} from 'react';
function withAuth(WrappedComponent) {
// 继承方式实现的高阶组件对被包装组件具有侵入性
// 当组合多个高阶组件使用时
// 很容易因为子类组件忘记通过super调用父类组件方法而导致逻辑丢失
// 因此,在使用高阶组件时,应尽量通过代理方式实现高阶组件
return class extends WrappedComponent {
render() {
if (this.props.loggedIn) {
return super.render();
} else {
return null;
}
}
}
}
高阶函数:以函数作为参数的函数,return一个函数。
高阶组件:以组件作为参数的组件,return一个组件。
高阶函数是一个接收函数作为参数或者将函数作为返回输出的函数
比如map、reduce、filter