react高阶组件

一个高阶组件就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为结果,而且,返回的新组件拥有了输入组件所不具有的功能。我们可以这么打比方,每个组件最后都返回了一个jsx,而jsx实质上一个对象,相当于我们传入一个对象,最后返回了一个新对象,它具有参数对象不具有的功能

// 删除user这个props
  function removeUserProp(WrapperComponent){
    return class WrappingComponent extends React.Component{
      render(){
        const {user,...otherProps} = this.props
        return <WrapperComponent {...otherProps}>
      }
    }  
  }

定义高阶组件的意义何在呢?

首先,重用代码 有时候很多 React 组件都需要公用同样一个逻辑,比如说 react-redux中容器组件的部分,没有必要让每个组件都实现一遍 shouldComponentUpdate 这些生命周期函数,把这部分逻辑提取出来,利用高阶组件的方式应用出去,就可以减少很多组件的重复代码

其次,修改现有 React 组件的行为 有些现成 React 组件并不是开者自己开发的,来自于第3方,或者,即使是我们自己开发的,但是我们不想去触碰这些组件的内部逻辑,这时候高阶组件有了用武之地 通过一个独立于原有组件的函数,可以产生新的组件,对原有组件没有任何侵害。

根据返回的新组件和传人组件参数的关系,高阶组件的实现方式可以分为两大类:

  • 代理方式的高阶组件

  • 继承方式的高阶组件

代理方式的高阶组件

上面的 removeUserProp 例子就是一个代理方式的高阶组件,特点是返回的新组件类直接继承自 React. Component 新组件扮演的角色是传入参数组件的一个“代理”,在新组建的 render 函数中,把被包裹组件渲染出来,除了高阶组件自己要做的工作,其余功能全都转手给了被包裹的组件。

代理方式的高阶组件,可以应用在下列场景中:

  • 操纵 prop

  • 访问 ref

  • 抽取状态

  • 包装组件

  1. 操纵 prop
  // 添加新props
  const addNewProp = (WrapperComponent,newProps) => {
    return class WrappingComponent extends React.Component{
      render(){
        return <WrapperComponent {...this.props} {...newProps}>
      }
    }  
  }
  1. 访问 ref
  // 获取refs
  const refsHOC = (WrapperComponent) => {
    return class HOCComponent extends React.Component{
      constructor(){
        super(...arguments)
        this.linkRef = this.linkRef.bind(this)
      }
      linkRef(wrappedInstance){
        this._root = wrappedInstance
      }
      render(){
        const props = {...this.props,ref:this.linkRef}
        return <WrapperComponent {...props}>
      }
    }  
  }
  1. 抽取状态
  const doNothing = () => ({})
  function connect(mapStateToProps=doNothing,mapDispatchToProps=doNothing){
    return function(WrapperComponent){
      class HOCComponent extends React.Component{
        //定义声明周期函数
        constructor(){
          super(...arguments)
          this.onChange = this.onChange.bind(this)
          this.store = {}
        }
        componentDidMount(){
          this.context.store.subscribe(this.onChange)
        }
        componentWillUnMount(){
          this.context.store.unsubscribe(this.onChange)
        }
        onChange(){
          this.setState({})
        }

        render(){
          const store = this.context.store
          const newProps = {
            ...this.props,
            ...mapStateToProps(store.getState()),
            ...mapDispatchToProps(store.dispatch())
          }
          return <WrapperComponent {...newProps}>
        }
      }
      HOCComponent.contextTypes = {
        store:React.PropTypes.object
      }
      return HOCComponent
    }
  }

  function getDisplayName(WrappedComponent){
    return WrappedComponent.displayName || WrappedComponent.name || Component
  }

  1. 包装组件
  const styleHOC = (WrappedComponent,style) => {
    return class HOCComponent extends React.Component{
      render(){
        return(){
          <div style={style}>
            <WrappedComponent {...this.props} />
          </div>
        }
      }
    }
  }

继承方式的高阶组件

继承方式的高阶组件采用继承关系关联作为参数的组件和返回的组件,假如传入的组件参数是 WrComponeappednt ,那么返回的组件就直接继承自 WrappedComponent

function removeUserProp(WrapperComponent){
    //继承于参数组件
    return class NewComponent extends WrapperComponent{
      render(){
        const {user,...otherProps} = this.props
        this.props = otherProps
        //调用WrapperComponent的render方法
        // 只是一个render函数,不是一整个生命周期
        return super.render()
      }
    }  
  }

继承方式的高阶组件可以应用于下列场景:

  • 操纵 prop

  • 操纵生命周期函数

  1. 操纵 prop
  const modifyPropsHOC = (WrappedComponent) => {
    return class NewComponent extends WrappedComponent{
      render(){
        const elements = super.render()
        const newStyle = {
          color:(elements && elements.type === 'div') ? 'red' : 'green'
        }
        const newProps = {...this.props,style:newStyle}
        return React.cloneElement(elements,newProps,elements.props.children)
      }
    }
  }
  1. 操纵生命周期函数:修改参数组件的生命周期
const onlyForLoggedHOC = (WrappedComponent) => {
  return class NewComponent extends WrappedComponent{
    render(){
      if(this.props.loggedIn){
        return super.render()
      }else{
        return null
      }
    }
  }
}
const cacheHOC = (WrappedComponent) => {
  return class NewComponent extends WrappedComponent{
    shouldComponentUpdate(nextProps,nextState){
      return !nextProps.userCache
    }
  }
}

以函数为子组件

高阶组件并不是唯一可用于提高 React 组件代码重用的方法 在上 节的介绍中可以体会到,高阶组件扩展现有组件功能的方式主要是通过 props ,增加 props 或者减少props ,或者修改原有的 props 以代理方式的高阶组件为例,新产生的组件和原有的组件说到底是两个组件,是父子关系,而两个 React 组件之间通信的方式自然是 props 因为每个组件都应该通过 propTypes 声明自己所支持的 props 高阶组件利用原组件的 props扩充功能,在静态代码检查上也占优势>

但是,高阶组件也有缺点,那就是对原组件的 props 有了固化的要求 也就是说,能不能把一个高阶组件作用于某个组件 ,要先看一下这个组件 是不是能够接受高阶组件传过来的 props ,如果组件 并不支持这些 props ,或者对这些 props 的命名有不同,或者使用方式不是预期的方式,那也就没有办法应用这个高阶组件。

“以函数为子组件”的模式就是为了克服高阶组件的这种局限而生的 在这种模式下,实现代码重用的不是一个函数,而是一个真正的 React 组件,这样的 React 组件有个特点,要求必须有子组件的存在,而且这个子组件必须是一个函数 在组件实例的生命周期函数中, this props children 引用的就是子组件, render 函数会直接this.props.children当做函数来调用,得到的结果就可以作为 render 返回结果的一部分

class CountDown extends React.Component{
  constructor(){
    super(...arguments)
    this.state = {count:this.props.startCount}
  }

  componentDidMount(){
    this.intervalHandle = setInterval(() => {
      const newCount = this.state.count - 1
      if(newCount >= 0){
        this.setState({count:newCount})
      }else{
        window.clearInterval(this.intervalHandle)
      }
    },1000)
  }

  componentWillUnMount(){
    if(this.intervalHandle){
      window.clearInterval(this.intervalHandle)
    }
  }

  render(){
    return this.props.children(this.state.count)
  }
}

CountDown.propTypes = {
  children:PropTypes.func.isRequired,
  startCount:PropTypes.number.isRequired
}
<CountDown>
  {
    (count) => <div>count</div>
  }
</CountDown>
<CountDown>
  {
    (count) => <div>{count > 0 ? count : 'happy new year'}</div>
  }
</CountDown>
<CountDown>
  {
    (count) => <Bomb countdown={count}>
  }
</CountDown>

以函数为子组件这种方法非常合适做动画,作为子组件的函数主要专注于参数来渲染就可以了;但是它难以做性能优化,因为子组件是函数,没有生命周期,无法利用shouldComponentUpdate

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,373评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,732评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,163评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,700评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,036评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,425评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,737评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,421评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,141评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,398评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,908评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,276评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,907评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,018评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,772评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,448评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,325评论 2 261

推荐阅读更多精彩内容

  • 在目前的前端社区,『推崇组合,不推荐继承(prefer composition than inheritance)...
    Wenliang阅读 77,351评论 16 126
  • 高阶组件是对既有组件进行包装,以增强既有组件的功能。其核心实现是一个无状态组件(函数),接收另一个组件作为参数,然...
    柏丘君阅读 2,998评论 0 6
  • React高阶组件探究 在使用React构建项目的过程中,经常会碰到在不同的组件中需要用到相同功能的情况。不过我们...
    绯色流火阅读 2,539评论 4 19
  • 什么是高阶组件? high-order-function(高阶函数)相信大多数开发者来说都熟悉,即接受函数作为参数...
    哇塞田阅读 9,022评论 9 23
  • 不是何时开始,楼道上,多了几只猫,自从有了猫,老鼠也逃得不见踪影。早些年被老鼠困扰多时,现在能灭迹老鼠,对于猫这个...
    七月风阅读 366评论 4 4