学会使用react高阶组件(HOC)优化你的代码~

1,399 阅读3分钟

React高阶组件HOC

😊文章略长,做好心里准备哦,建议阅读15分钟。

定义

  • HOC其实是一个函数,接收一个组件作为参数,返回一个包装组件作为返回值
  • 在多个不同的组件中需要用到相同的功能
  • 高阶组件和装饰器就是一个模式,因此高阶组件可以作为装饰器来使用

作用

  • 适用范围广,它不需要es6或者其它需要编译的特性,有函数的地方,就有HOC。
  • Debug友好,它能够被React组件树显示,所以可以很清楚地知道有多少层,每层做了什么。

补充说明装饰器

在下面很多案例中用到了装饰器(@),这里提前说明下,修饰器(Decorator)是一个函数,用来修改类的行为

更多用法可参考简书一篇文章:www.jianshu.com/p/275bf41f4…

基本形式

const EnhancedComponent = higherOrderComponent(WrappedComponent)

function hoc(WrappedComponent) {
    return class HOC extends React.Component {
        componentDidMount() {
            console.log("hoc");
        }

        render() {
            return <WrappedComponent />
        }
    }
}

// 使用高阶组件
class ComponentClass extends React.Component {
    render() {
        return <div></div>
    }
}

export default hoc(ComponentClass);

// 作为装饰器使用
@hoc
export default class ComponentClass extends React.Component {
    //...
}

常见用法

  • 属性代理(Props Proxy): 高阶组件通过ComponentClass的props来进行相关操作
  • 继承反转(Inheritance Inversion)):高阶组件继承自ComponentClass

属性代理(Props Proxy)常见作用

  1. 操作props,可以对原组件的props进行增删改查,需要考虑到不能破坏原组件
// 添加新的props
function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      const newProps = {
        user: currentLoggedInUser
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}
  1. 通过refs访问组件实例,进而调用组件相关方法
// WrappedComponent初始渲染时候会调用ref回调,传入组件实例,在proc方法中,就可以调用组件方法
function refsHOC(WrappedComponent) {
  return class RefsHOC extends React.Component {
    proc(wrappedComponentInstance) {
      wrappedComponentInstance.method()
    }

    render() {
      // Object.asign();将所有可枚举属性的值从一个或多个源对象复制到目标对象
      const props = Object.assign({}, this.props, {ref: this.proc.bind(this)})
      return <WrappedComponent {...props}/>
    }
  }
}
  1. 提取state,可以通过传入 props 和回调函数把 state 提取出来
// 提取了 input 的 value 和 onChange 方法
function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    state = {
        name: ''
    }
    
    onNameChange(event) {
      this.setState({
        name: event.target.value
      })
    }
    
    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange.bind(this)
        }
      }
       return <WrappedComponent {...this.props} {...newProps}/>
    }
  }
}

// 使用方式如下
@ppHOC
class Example extends React.Component {
  render() {
    // 使用ppHOC装饰器之后,组件的props被添加了name属性,input会成为受控组件
    return <input name="name" {...this.props.name}/>
  }
}
  1. 用其他元素包裹WrappedComponent,实现布局,样式等目的
function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return (
        <div style={{display: 'block'}}>
          <WrappedComponent {...this.props}/>
        </div>
      )
    }
  }
}

继承反转(Inheritance Inversion)常见作用

  1. 渲染劫持,HOC 控制着 WrappedComponent 的渲染输出,可以劫持被继承class的render内容,进行修改,过滤后,返回新的显示内容
// 过滤掉原组件中的ul元素
function hoc(ComponentClass) {
    return class HOC extends ComponentClass {
        render() {
            const elementTree = super.render();
            elementTree.props.children = elementTree.props.children.filter((z) => z.type !== "ul")
            
            return React.cloneElement(elementTree);
        }
    }
}

@hoc
export default class ComponentClass extends React.Component {
    render() {
        return (
            <div>
                <p style={{color: 'brown'}}>啦啦啦</p>
                <ul>
                    <li>1</li>
                    <li>2</li>
                </ul>
            </div>
        )
    }
}
  1. 操作state,HOC可以操作WrappedComponent实例的state。但这样会破坏WrappedComponent的state,所以要限制HOC读取或添加state,添加的state应该放在单独的命名空间里
export function IIHOC(WrappedComponent) {
  return class II extends WrappedComponent {
    render() {
      return (
        <div>
          <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre>
          
          <p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre>
          
          {
            super.render()
          }
        </div>
      )
    }
  }
}
  1. 条件渲染
// 假设this.props.loggedIn为真,HOC会完全渲染WrappedComponent 的渲染结果
function iiHOC(WrappedComponent) {
  return class ii extends WrappedComponent {
    render() {
      if (this.props.loggedIn) {
        return super.render()
      } else {
        return null
      }
    }
  }
}
  1. 解决WrappedComponent名字丢失问题
// 用HOC包裹的组件会丢失原先的名字,影响开发和调试,可以在WrappedComponent的名字加前缀来作为HOC的名字
const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';

static displayName = `withModal(${componentName})`;

实际应用

  1. 记录localStorage返回值
//通过多重高阶组件确定key并设定组件
const withStorage = (key) => WrappedComponent => {
  return class extends Component {
    componentWillMount() {
        let data = localStorage.getItem(key);
        this.setState({data});
    }
    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}

@withStorage('data')
class MyComponent2 extends Component {  
    render() {
        return <div>{this.props.data}</div>
    }
}

@withStorage('name')
class MyComponent3 extends Component {  
    render() {
        return <div>{this.props.data}</div>
    }
}
  1. 项目中,每次重新打开modal框,每次销毁modal中数据,防止数据污染
const modalHoc = (options) => WrappedComponent => {
    const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';

    return class ModalComponent extends Component {
        static displayName = `withModal(${componentName})`;

        render() {
            const {visible, onCancel} = this.props;

            let title;
            if (typeof options === 'string') title = options;

            if (typeof options === 'function') title = options;

            if (typeof options === 'object') title = options.title;

            if (typeof title === 'function') title = title(this.props);

            return (
                <Modal
                    destroyOnClose
                    width="60%"
                    bodyStyle={{padding: 0}}
                    footer={null}

                    {...options}
                    title={title}

                    onCancel={onCancel}
                    visible={visible}
                >
                    <WrappedComponent {...this.props}/>
                </Modal>
            );
        }
    }
};

@modalHoc('可以传入不同类型标题')

😊 参考链接:react.html.cn/docs/higher…

😊 刚刚加入掘金社区,欢迎提出宝贵建议,一起进步学习!