Skip to content

高阶组件

高阶组件是 React 中一个重要而复杂的概念。主要用来实现组件逻辑的抽象和复用,在很多第三方库被使用到。合理使用高阶组件也能显著提高代码质量。

基本概念

高阶函数是函数作为参数,并且返回值也是函数的函数。类似地,高阶组件简称(HOC)接受 React 组件作为参数,并且返回一个新的 React 组件。高阶组件本质上也是一个函数,并不是一个组件,高阶组件的函数形式如下:

const EnhancedComponent = higherOrderComponent(WrappedComponent)

从例子来看。MyComponent 组件需要从 LocalStorage 中读取数据然后渲染到界面,一般来说:

jsx
class MyComponent extends Component {
  render() {
    return <div>{this.state.data}</div>;
  }
  componentWillMount() {
    let data = localStorage.getItem('data');
    this.setState({ data });
  }
}

但当其他组件也需要从 LocalStorage 中获取同样的数据展示时,每个组件都需要重写一次 componentWillMount 中的代码,这是很冗余的。于是用高阶组件来改写这部分:

jsx
function withPersistentData(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem('data');
      this.setState({ data });
    }
    render() {
      // 通过 {...this.props}把传递给当前组件的属性传递给被包装的组件
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

class MyComponent extends Component {
  render() {
    return <div>{this.props.data}</div>;
  }
}
const MyComponentWithPersistenData = withPersistentData(MyComponent);

withPersistenData 就是一个高阶组件,它返回一个新的组件,在新组件的 componentWillMount 中统一处理从 localStorage 中获取数据的逻辑,然后将获取到的数据通过 props 传递给被包装的租金啊 WrappedComponent。这样在 WrappedComponent 中就可以直接使用 this.props.data 获取需要展示的数据。

高阶组件的主要功能是封装被分离组件的通用逻辑,让通用逻辑在组件间更好地被复用。高阶组件的这种实现方式的本质其实就是装饰者设计模式。


使用场景

  1. 操作 props
  2. 通过 ref 访问组件实例
  3. 组件状态提升
  4. 从其他元素包装组件

操作 props

在被包装组件接受 props 前,高阶组件可以先拦截 props,对 props 执行增、删、改等操作,然后将处理后的 props 再传递给被包装组件。

通过 ref 访问组件实例

高阶组件通过 ref 获取被包装组件实例的引用,然后高阶组件就具备了直接操作被包装组件的属性或方法的能力。

jsx
function withRef(WrappedComponent) {
  return class extends Component {
    someMehod = () => {
      this.wrappedInstance = this.someMethodInWrappedComponent();
    };
    render() {
      // 为被包装组件添加ref , 从而获取该组件实例并赋值给this.wrappedInstance
      return (
        <WrappedComponent ref={instance => (this.wrappedInstance = instance)} {...this.props} />
      );
    }
  };
}

当 WrappedComponent 被渲染时,执行 ref 的回调函数,高阶组件通过 this.wrappedInstance 保存 WrappedComponent 实例的引用,在 someMethod 方法中,通过 this.wrappedInstance 调用 WrappedComponent 实例中的方法。

组件状态提升

无状态组件更容易被复用。高阶组件可以通过将被包装组件的状态及相应的状态处理方法提升到高阶组件自身内部实现被包装组件的无状态化。一个典型场景是,利用高阶组件将原本受控组件需要自己维护的状态统一提升到高阶组件中:

jsx
function withControlledState(WrappedComponent) {
  return class extends Component {
    constructor() {
      super();
      this.state = {
        value: '',
      };
    }
    handleValueChange = e => {
      this.setState({
        value: e.target.value,
      });
    };
    render() {
      // newProps保存受控组件需要使用的属性和事件处理函数
      const newProps = {
        controlledProps: {
          value: this.state.value,
          onChange: this.handleValueChange,
        },
      };
      return <WrappedComponent {...this.props} {...newProps} />;
    }
  };
}

这个例子把受控组件的 value 属性用到的状态和处理 value 变化的回调都提升到了高阶组件中,当我们再使用受控组件时,可以这么使用:

jsx
class SimpleControlledComponent extends Component {
  render() {
    // 此时的SimpleControlledComponent 为无状态组件,状态由高阶组件维护
    return <input nmae="simple" {...this.props.controlledProps} />;
  }
}
const ComponentWithControlledState = withControlledState(SimpleControlledComponent);

用其他元素包装组件

我们还可以在高阶组件渲染 WrappedComponent 时添加额外的元素,这种情况通常用于为 WrappedCompoent 增加布局或修改样式

jsx
function withRedBackground(WrappedComponent) {
  return class extends Component {
    render() {
      return (
        <div style={{ backgroundColor: 'red' }}>
          <WrappedComponent {...this.props} />
        </div>
      );
    }
  };
}

共 20 个模块,1301 篇 Markdown 文档。