高阶组件的基本概念(是什么❓)

  • 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
  • 具体而言,高阶组件是参数为组件,不是函数,返回值为新组件的函数(在此之前可以先了解一下高阶函数)。
  • const EnhancedComponent = higherOrderComponent(WrappedComponent);
    
  • 区别组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。
  • 概念比较简单,实际也好用。HOC 在 React 的第三方库中很常见,例如 Redux 的 connect 和 Relay 的 createFragmentContainer。

    使用高阶组件的原因(为什么❓)

  • 学习新的技术无非要么就是装杯💪💪,要么就是对于项目中带来了实际作用,并且能提升开发效率。(两者兼有)
  • 关于高阶组件能解决的问题可以简单概括成以下三个方面:
  • 抽取重复代码,实现组件复用,常见场景:页面复用。
  • 条件渲染,控制组件的渲染逻辑(渲染劫持),常见场景:权限控制。
  • 捕获/劫持被处理组件的生命周期,常见场景:组件渲染性能追踪、日志打点
  • 下面就来介绍实现方式,从而加深大家对高阶组件作用的理解。
  • 高阶组件的实现(怎么做❓)

  • 通常情况下,实现高阶组件的方式有以下两种:
  • 属性代理(Props Proxy)
  • 返回一个无状态(stateless)的函数组件
  • 返回一个 class 组件
  • 反向继承(Inheritance Inversion)
  • 高阶组件实现方式的差异性决定了它们各自的应用场景:一个 React 组件包含了 props、state、ref、生命周期方法、static方法和React 元素树几个重要部分,所以我将从以下几个方面对比两种高阶组件实现方式的差异性:

  • 原组件是否被继承
  • 能否读取/操作原组件的 props
  • 能否读取/操作原组件的 state
  • 能否通过 ref 访问到原组件的 dom 元素
  • 是否影响原组件某些生命周期等方法
  • 是否取到原组件 static 方法
  • 能否劫持原组件生命周期方法
  • 能否渲染劫持
  • 该方式实现的高阶组件会影响到元组件
  • 操作props

    import React from 'react';
    function MyHoc(WrapperComponent) {
      return class LogoProps extends React.Component {
        render() {
          const newProps = { type: 'logoProps'}
          return (
            <WrapperComponent {...this.props} {...newProps} />
    export default MyHoc;
    
  • 从上面代码可看出我们除了可以拦截到父组件的props,还可以对该props进行操作,例如增加一个type属性
  • 抽象state

  • 本质上来说高阶组件内是不能操作原组件state,但是非要这么干,就通过props回调的方式是可以实现的
  • 来看看吧:
  • //MyHoc.js
    import React from 'react';
    function MyHoc(WrapperComponent) {
      return class LogoProps extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            value: ''
        onChange = (e) => {
          this.setState({
            value: e.target.value
        render() {
          const newProps = {
            name: {
              value: this.state.value,
              onChange: this.onChange
          return (
            <WrapperComponent {...this.props} {...newProps} />
    export default MyHoc;
    
    //FancyComponent.js
    import React from 'react';
    import MyHoc from './MyHoc';
    class FancyComponent extends React.Component{
      render() {
        return (
          <input {...this.props.name} />
    export default MyHoc(FancyComponent);
    

    获取静态方法

    直接看栗子,比较简单:

    MyHoc.js
    import React from 'react';
    function MyHoc(WrapperComponent) {
      return class LogoProps extends React.Component {
        componentDidMount() {
          // 获取父组件静态方法
          WrapperComponent.sayHello()
        render() {
          return (
            <WrapperComponent {...this.props}  />
    export default MyHoc;
    
    //FancyComponent.js
    import React from 'react';
    import MyHoc from './MyHoc';
    import ReverseHoc from './ReverseHoc';
    class FancyComponent extends React.Component{
      constructor(props) {
        super(props);
        this.state = {
      static sayHello() {
        console.log('say hello')
      render() {
        return (
          <input {...this.props.name} />
    export default MyHoc(FancyComponent);
    

    输出:say hello

    反向继承指的是使用一个函数接受一个组件作为参数传入,并返回一个继承了该传入组件的类组件,且在返回组件的 render() 方法中返回 super.render() 方法,最简单的实现如下:

    const HOC = (WrappedComponent) => {
      return class extends WrappedComponent {
        render() {
          return super.render();
    
  • 与属性代理的区别是:使用反向继承方式实现的高阶组件的特点是允许高阶组件通过 this 访问到原组件,所以可以直接读取和操作原组件的 state/ref/生命周期方法。
  • 劫持原组件生命周期

  • 高阶组件会覆盖传入组件的生命周期,示例如下:
  • //ReverseHoc.js
    import React from 'react';
    function ReverseHoc(WrapperComponent) {
      return class LogoProps extends WrapperComponent {
        componentDidMount() {
          console.log('this is ReverseHoc componentDidMount Life');
        render() {
         super.render()
    export default ReverseHoc;
    
    //FancyComponent.js
    import React from 'react';
    import MyHoc from './MyHoc';
    import ReverseHoc from './ReverseHoc';
    class FancyComponent extends React.Component{
      constructor(props) {
        super(props);
        this.state = {
      componentDidMount() {
        console.log('this is FancyComponent componentDidMount Life')
      render() {
        return (
          <input />
    // export default MyHoc(FancyComponent);
    export default ReverseHoc(FancyComponent);
    
    'this is ReverseHoc componentDidMount Life'
    
  • 假如我不想覆盖,当传入组件有该生命周期,高阶组件就不覆盖,无则覆盖传入组件生命周期。 我们改造一下ReverseHoc.js:
  • //ReverseHoc.js
    import React from 'react';
    function ReverseHoc(WrapperComponent) {
      const DidMount = WrapperComponent.prototype.componentDidMount;
      return class LogoProps extends WrapperComponent {
         async componentDidMount() {
          if(DidMount) {
             await DidMount.apply(this)
          console.log('this is ReverseHoc componentDidMount Life');
        render() {
         super.render()
    export default ReverseHoc;
    

    这样做就可以实现上面说的效果啦!

    操作/读取state

  • 因为我们高阶组件继承了传入组件,那么就是能访问到this了,有了this是不是就能操作和读取state,也就不用像属性代理那么复杂还要通过props回调来操作state
  • 示例:
  • //ReverseHoc.js
    import React from 'react';
    function ReverseHoc(WrapperComponent) {
      return class LogoProps extends WrapperComponent {
         async componentDidMount() {
           //读取state
          console.log(this.state)
          //操作state
          this.setState({ type: 1 });
        render() {
         super.render()
    export default ReverseHoc;
    

    注意事项👉(官方文档):

  • 不要在 render 方法中使用 HOC
  • 务必复制静态方法
  • Refs 不会被传递(下面有详细介绍)
  • Refs转发

  • 基本概念:Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。但其对某些组件,尤其是可重用的组件库是很有用的。(应用于非受控组件)
  • 过时API:String类型的Refs(官方不建议使用)

    import React from 'react';
    export default class UnControlledComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          submitVal: '',
      handleSubmit = (e) =>
    
    
    
    
        
     {
        e.preventDefault()
        // this.refs.inputRef.value 得到输入框输入的内容
        this.setState({
           submitVal: this.refs.inputRef.value
      render() {
        const { submitVal } = this.state;
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Name:
              <input type="text" ref="inputRef" />
            </label>
            <input type="submit" value="Submit" />
            <h4>提交内容:{submitVal}</h4>
          </form>
    

    Refs回调

    import React from 'react';
    export default class UnControlledComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          submitVal: '',
        this.inputRef = React.createRef();
      handleSubmit = (e) => {
        e.preventDefault()
        // this.inputRef.value 得到输入框输入的内容
         this.setState({
          submitVal: this.inputRef.value
      render() {
        const { submitVal } = this.state;
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Name:
              <input type="text" ref={(ref) => this.inputRef = ref} />
            </label>
            <input type="submit" value="Submit" />
            <h4>提交内容:{submitVal}</h4>
          </form>
    

    createRef方式

    import React from 'react';
    export default class UnControlledComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          inputVal: '',
          submitVal: '',
        this.inputRef = React.createRef();
      handleSubmit = (e) => {
        e.preventDefault()
        // this.inputRef.current.value 获取输入框输入内容
         this.setState({
           submitVal: this.inputRef.current.value
      render() {
        const { submitVal } = this.state;
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              Name:
              <input type="text" ref={this.inputRef} />
            </label>
            <input type="submit" value="Submit" />
            <h4>提交内容:{submitVal}</h4>
          </form>
    

    在高阶组件中转发 refs

    首先我们看个示例:

    // MyHoc.js
    import React from 'react';
    const MyHoc = (WrapperComponent) => {
      return class LogoProps extends React.Component {
        render() {
          const { forwardRef, ...rest } = this.props;
          return (
            <WrapperComponent {...rest} ref={forwardRef} />
    export default MyHoc;
    
    // FancyInput.js
    import MyHoc from './MyHoc';
    import React from 'react';
     class FancyInput extends React.Component {
       render() {
         return (
           <input />
    export default MyHoc(FancyInput)
    
    //App.js
    import React from 'react';
    import './App.css';
    import FancyInput from './RefsOfHOC/FancyInput';
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.forWardRef = React.createRef()
      componentDidMount() {
        console.log(this.forWardRef)
      render() {
        return (
          <div className="App">
            <FancyInput ref={this.forWardRef} />
          </div>
    export default App;
    

    思考问题:

  • 大家会觉得在App.js中componentDidMount输出的谁呢?

  • 直接看结果,发现输出的高阶组件所包裹的LogoProps 实例
  • 能不能会去到FancyInput.js Input ref呢?

  • 肯定是可以的,上述示例肯定不是我们想要的,继续往下看
  • // MyHoc.js
    import React from 'react';
    const MyHoc = (WrapperComponent) => {
       class LogoProps extends React.Component {
        render() {
          const { forwardRef, ...rest } = this.props;
          return (
            <WrapperComponent {...rest} ref={forwardRef} />
      return React.forwardRef((props, ref) => (
        <LogoProps {...props} forwardRef={ref} />
    export default MyHoc;
    
    // FancyInput.js
    import MyHoc from './MyHoc';
    import React from 'react';
    const FancyInput = React.forwardRef((props, ref) => {
      return (
        <input ref={ref}/>
    export default MyHoc(FancyInput)
    
    //App.js
    import React from 'react';
    import './App.css';
    import FancyInput from './RefsOfHOC/FancyInput';
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.forWardRef = React.createRef()
      componentDidMount() {
        console.log(this.forWardRef)
      render() {
        return (
          <div className="App">
            <FancyInput ref={this.forWardRef} />
          </div>
    export default App;
    

    我们来看看结果:

  • 拿到input Ref,就可以随意操作input组件了,例如:添加class,获取value值...等
  • 在上面的示例中,FancyInput 使用 React.forwardRef 来获取传递给它的 ref,然后转发到它渲染的 DOM input
  • 以上内容如有遗漏错误,欢迎留言 ✍️指出,一起进步💪💪💪

    如果觉得本文对你有帮助,🏀🏀留下你宝贵的 👍

  • 在 React Router 中使用 JWT
  • 基于 ChatGPT 和 React 搭建 JSON 转 TS 的 Web 应用
  •