React高阶组件(HOC)模型理论与实践

什么是HOC?

HOC(全称Higher-order component)是一种React的进阶使用方法,主要还是为了便于组件的复用。HOC就是一个方法获取一个组件,返回一个更高级的组件。

什么时候使用HOC?

在React开发过程中,发现有很多情况下,组件需要被"增强",比如说给组件添加或者修改一些特定的props,一些权限的管理,或者一些其他的优化之类的。而如果这个功能是针对多个组件的,同时每一个组件都写一套相同的代码,明显显得不是很明智,所以就可以考虑使用HOC。

栗子:react-redux的connect方法就是一个HOC,他获取wrappedComponent,在connect中给wrappedComponent添加需要的props。

HOC的简单实现

HOC不仅仅是一个方法,确切说应该是一个组件工厂,获取低阶组件,生成高阶组件。

一个最简单的HOC实现是这个样子的:

function HOCFactory(WrappedComponent) {
  return class HOC extends React.Component {
    render(){
      return <WrappedComponent {...this.props} />
    }
  }
}

HOC可以做什么?

  • 代码复用,代码模块化

  • 增删改props

  • 渲染劫持

其实,除了代码复用和模块化,HOC做的其实就是劫持,由于传入的wrappedComponent是作为一个child进行渲染的,上级传入的props都是直接传给HOC的,所以HOC组件拥有很大的权限去修改props和控制渲染。

增删改props

可以通过对传入的props进行修改,或者添加新的props来达到增删改props的效果

比如你想要给wrappedComponent增加一个props,可以这么搞:

function control(wrappedComponent) {
  return class Control extends React.Component {
    render(){
      let props = {
        ...this.props,message: "You are under control"
      };
      return <wrappedComponent {...props} />
    }
  }
}

这样,你就可以在你的组件中使用message这个props:

class MyComponent extends React.Component {
  render(){
    return <div>{this.props.message}</div>
  }
}

export default control(MyComponent);

渲染劫持

这里的渲染劫持并不是你能控制它渲染的细节,而是控制是否去渲染。由于细节属于组件内部的render方法控制,所以你无法控制渲染细节。

比如,组件要在data没有加载完的时候,现实loading...,就可以这么写:

function loading(wrappedComponent) {
  return class Loading extends React.Component {
    render(){
      if(this.props.data) {
        return <div>loading...</div>
      }
      return <wrappedComponent {...props} />
    }
  }
}

这个样子,在父级没有传入data的时候,这一块儿就只会显示loading...,不会显示组件的具体内容

class MyComponent extends React.Component {
  render(){
    return <div>{this.props.data}</div>
  }
}

export default control(MyComponent);

HOC有什么用例?

React Redux

最经典的就是React Redux的connect方法(具体在connectAdvanced中实现)。

通过这个HOC方法,监听redux store,然后把下级组件需要的state(通过mapStatetoProps获取)和action creator(通过mapdispatchToProps获取)绑定到wrappedComponent的props上。

logger和debugger

这个是官网上的一个示例,可以用来监控父级组件传入的props的改变:

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentwillReceiveProps(nextProps) {
      console.log(`WrappedComponent: ${WrappedComponent.displayName},Current props: `,this.props);
      console.log(`WrappedComponent: ${WrappedComponent.displayName},Next props: `,nextProps);
    }
    render() {
      // Wraps the input component in a container,without mutating it. Good!
      return <WrappedComponent {...this.props} />;
    }
  }
}

页面权限管理

可以通过HOC对组件进行包裹,当跳转当前页面的时候,检查用户是否含有对应的权限。如果有的话,渲染页面。如果没有的话,跳转到其他页面(比如无权限页面,或者登陆页面)。

也可以给当前组件提供权限的API,页面内部也可以进行权限的逻辑判断。

本来准备把详细代码当个栗子贴出来的,结果突然想到公司保密协议,所以。。。

使用HOC需要注意什么?

尽量不要随意修改下级组件需要的props

之所以这么说,是因为修改父级传给下级的props是有一定风险的,可能会造成下级组件发生错误。比如,原本需要一个name的props,但是在HOC中给删掉了,那么下级组件或许就无法正常渲染,甚至报错。

Ref无法获取你想要的ref

以前你在父组件中使用<component ref="component"/>的时候,你可以直接通过this.refs.component进行获取。但是因为这里的component经过HOC的封装,已经是HOC里面的那个component了,所以你无法获取你想要的那个ref(wrappedComponent的ref)。

解决这个问题,这里有两个方法

a) 像React Redux的connect方法一样,在里面添加一个参数,比如withRef,组件中检查到这个flag了,就给下级组件添加一个ref,并通过getWrappedInstance方法获取

栗子:

function HOCFactory(wrappedComponent) {
  return class HOC extends React.Component {
    getWrappedInstance = ()=>{
      if(this.props.widthRef) {
        return this.wrappedInstance;
      }
    }

    setWrappedInstance = (ref)=>{
      this.wrappedInstance = ref;
    }

    render(){
      let props = {
        ...this.props
      };

      if(this.props.withRef) {
        props.ref = this.setWrappedInstance;
      }

      return <wrappedComponent {...props} />
    }
  }
}

export default HOCFactory(MyComponent);

这样子你就可以在父组件中这样获取MyComponent的ref值了。

class ParentCompoent extends React.Component {
  doSomethingWithMyComponent(){
    let instance = this.refs.child.getWrappedInstance();
    // ....
  }

  render(){
    return <MyComponent ref="child" withRef />
  }
}

b) 还有一种方法,在官网中有提到过
父级通过传递一个方法,来获取ref,具体看栗子:

先看父级组件:

class ParentCompoent extends React.Component {
  getInstance = (ref)=>{
    this.wrappedInstance = ref;
  }

  render(){
    return <MyComponent getInstance={this.getInstance} />
  }
}

HOC里面把getInstance方法当作ref的方法传入就好

function HOCFactory(wrappedComponent) {
  return class HOC extends React.Component {
    render(){
      let props = {
        ...this.props
      };

      if(typeof this.props.getInstance === "function") {
        props.ref = this.props.getInstance;
      }

      return <wrappedComponent {...props} />
    }
  }
}

export default HOCFactory(MyComponent);

感谢@wmzy的指出,在上面的两个方法getInstancesetWrappedInstance,由于ES6 class的写法并不会自动绑定this,所以需要用bind(this)到两个方法上,确保this的正确性。或者使用箭头函数来写两个方法,ES6的箭头函数自动绑定this

Component上面绑定的Static方法会丢失

比如,你原来在Component上面绑定了一些static方法MyComponent.staticmethod = o=>o。但是由于经过HOC的包裹,父级组件拿到的已经不是原来的组件了,所以当然无法获取到staticmethod方法了。

官网上的示例:

// 定义一个static方法
WrappedComponent.staticmethod = function() {/*...*/}
// 利用HOC包裹
const EnhancedComponent = enhance(WrappedComponent);

// 返回的方法无法获取到staticmethod
typeof EnhancedComponent.staticmethod === 'undefined' // true

这里有一个解决方法,就是hoist-non-react-statics组件,这个组件会自动把所有绑定在对象上的非React方法都绑定到新的对象上:

import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance,WrappedComponent);
  return Enhance;
}

结束语

当你需要做React插件的时候,HOC模型是一个很实用的模型。

希望这篇文章能帮你对HOC有一个大概的了解和启发。

另外,这篇medium上的文章会给你更多的启发,在这文章中,我这里讲的被分为Props Proxy HOC,还有另外一种Inheritance Inversion HOC,强烈推荐看一看。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom