React系列之高阶组件HOC实际应用指南

前言

Higher-Order function(高阶函数)你们很熟悉,在函数式编程中的一个基本概念,它描述了这样一种函数:这种函数接受函数做为输出,或者输出一个函数。好比经常使用的工具方法reduce,map等都是高阶函数

如今咱们都知道高阶函数是什么,Higher-Ordercomponents(高阶组件)其实也是相似于高阶函数,它接受一个React组件做为输入,输出一个新的React组件html

Concretely, a higher-order component is a function that takes a component and returns a new component.react

通俗的语言解释:当咱们用一个容器(w)把React组件包裹,高阶组件会返回一个加强(E)的组件。高阶组件让咱们的代码更具备复用性,逻辑性与抽象特。它能够对props和state进行控制,也能够对render方法进行劫持...编程

大概是这样:redux

const EnhancedComponent = higherOrderComponent(WrappedComponent)api

简单例子:bash

import React, { Component } from 'react';
import ExampleHoc from './example-hoc';

class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return (
    <div>
       {this.props.title} - {this.props.name}
    </div>
    )
  }
}
export default ExampleHoc(UseContent)
复制代码
import React, { Component } from 'react';

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    constructor(props) {
        super(props)
        this.state = {
         title: 'hoc-component',
         name: 'arcsin1',
        }
    }
    render() {
       const newProps = {
        ...this.state,
       }
       return <WrappedComponent {...this.props} {...this.newProps} />
    }
  }
}
export default ExampleHoc
复制代码

组件UseContent,你能够看到实际上是一个很简单的一个渲染而已,而组件ExampleHoc对它进行了加强,很简单的功能.app

应用场景

如下代码我会用装饰器(decorator)书写函数式编程

属性代理。 高阶组件经过被包裹的React组件来操做props

反向继承。 高阶组件继承于被包裹的React组件

1. 属性代理

小列子说明:函数

import React, { Component } from 'react'
import ExampleHoc from './example-hoc'

@ExampleHoc
export default class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return (
        <div>
           {...this.props} //这里只是演示
        </div>
    )
  }
}
复制代码
import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       return <WrappedComponent {...this.props} />
    }
  }
}
export default ExampleHoc
复制代码

这样的组件就能够做为参数被调用,原始组件就具有了高阶组件对它的修饰。就这么简单,保持单个组件封装性的同时还保留了易用性。固然上述的生命周期以下:工具

didmount -> HOC didmount ->(HOCs didmount) ->(HOCs willunmount)-> HOC willunmount -> unmount

  • 控制props

    我能够读取,编辑,增长,移除从WrappedComponent传来的props,可是须要当心编辑和移除props。咱们应该对高阶组件的props做新的命名防止混淆了。

    例如:

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       const newProps = {
           name: newText,
       }
       return <WrappedComponent {...this.props}  {...newProps}/>
    }
  }
}
export default ExampleHoc
复制代码
  • 经过refs使用引用

    在高阶组件中,咱们能够接受refs使用WrappedComponent的引用。 例如:

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    proc = wrappedComponentInstance => {
        wrappedComponentInstance.method()
    }
    render() {
       const newProps = Object.assign({}, this.props,{
           ref: this.proc,
       })
       return <WrappedComponent {...this.newProps} />
    }
  }
}
export default ExampleHoc
复制代码

当WrappedComponent被渲染的时候,refs回调函数就会被执行,这样就会拿到一份WrappedComponent的实例的引用。这样就能够方便地用于读取和增长实例props,并调用实例。

  • 抽象state

咱们能够经过WrappedComponent提供props和回调函数抽象state。就像咱们开始的例子,咱们能够把原组件抽象为展现型组件,分离内部状态,搞成无状态组件。

例子:

import React, { Component } from 'react';

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    constructor(props) {
        super(props)
        this.state = {
         name: '',
        }
    }
    onNameChange = e => {
        this.setState({
            name: e.target.value,
        })
    }
    render() {
       const newProps = {
         name: {
            value: this.state.name,
            onChange: this.onNameChange,
         }
       }
       return <WrappedComponent {...this.props} {...newProps} />
    }
  }
}
export default ExampleHoc
复制代码

在上面 咱们经过把input的name prop和onchange方法提到了高阶组件中,这样有效的抽象了一样的state操做。

用法:
import React, { Component } from 'react'
import ExampleHoc from './example-hoc'

@ExampleHoc
export default class UseContent extends Component {
  render() {
    console.log('props:',this.props);
    return (
        <input name="name" {this.props.name} />
    )
  }
}
这样就是一个受控组件
复制代码
  • 其它元素包裹WrappedComponent

    其它,咱们可使用其它元素包裹WrappedComponent,这样既能够增长样式,也能够方便布局。例如

import React, { Component } from 'react'

const ExampleHoc = WrappedComponent => {
  return class extends Component {
    render() {
       return (
            <div style={{display: 'flex'}}>
                <WrappedComponent {...this.props} />
            </div>
       )
    }
  }
}
export default ExampleHoc
复制代码

2. 反向继承

从字面意思,能够看出它与继承相关,先看看例子:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
       return super.render()
    }
  }
}
复制代码

正如看见的,高阶组件返回的组件继承与WrappedComponent,由于被动继承了WrappedComponent,全部的调用都是反向。因此这就是反代继承的由来。 这种方法与属性代理不太同样,它经过继承WrappedComponent来实现,方法能够经过super来顺序调用,来看看生命周期:

didmount -> HOC didmount ->(HOCs didmount) -> willunmount -> HOC willunmount ->(HOCs willunmount)

在反向继承中,高阶函数可使用WrappedComponent的引用,这意味着可使用WrappedComponent的state,props,生命周期和render方法。但它并不能保证完整的子组件树被解析,得注意。

  • 渲染劫持

渲染劫持就是高阶组件能够控制WrappedComponent的渲染过程,并渲染各类各样的结果。咱们能够在这个过程当中任何React元素的结果中读取,增长,修改,删除props,或者修改React的元素树,又或者用样式控制包裹这个React元素树。

由于前面说了它并不能保证完整的子组件树被解析,有个说法:咱们能够操控WrappedComponent元素树,并输出正确结果,但若是元素树种包含了函数类型的React组件,就不能操做组件的子组件。

先看看有条件的渲染例子:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      if(this.props.loggedIn) { //当登陆了就渲染
           return super.render()
      } else {
          return null
      }
      
    }
  }
}
复制代码

对render输出结果的修改:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      const eleTree = super.render()
      let newProps = {}
      
      if(eleTree && eleTree.type === 'input') { 
           newProps = {value: '这不能被渲染'}
      } 
      const props = Object.assgin({},eleTree.props,newProps)
      const newEleTree = React.cloneElement(eleTree, props, eleTree.props.children)
      return newEleTree
      
    }
  }
}
复制代码
  • 控制state

    高阶组件是能够读取,修改,删除WrappedComponent实例的state,若是须要的话,也能够增长state,但这样你的WrappedComponent会变得一团糟。所以大部分的高阶组件多都应该限制读取或者增长state,尤为是增长state,能够经过从新命名state,以防止混淆。

    看看例子:

const ExampleHoc = WrappedComponent => {
  return class extends WrappedComponent {
    render() {
      <div>
       <h3>HOC debugger</h3>
       <p>Props <pre>{JSON.stringfy(this.props,null,1)}</pre></p>
       <p>State <pre>{JSON.stringfy(this.state,null,2)}</pre></p>
       {super.render()}
      </div>
    }
  }
}
复制代码

高阶组件接受其它参数

举个列子,我把用户信息存在本地LocalStorage中,固然里面有不少key,可是我不须要用到全部,我但愿按照个人喜爱获得我本身想要的。

import React, { Component } from 'react'

const ExampleHoc = (key) => (WrappedComponent) => {

  return class extends Component {
    componentWillMount() {
      let data = localStorage.getItem(key);
        this.setState({data});
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />
    }
  }
}

复制代码
import React, { Component } from 'react'

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

}

const MyComponent2WithHOC = ExampleHoc(MyComponent2, 'data')

export default MyComponent2WithHOC

复制代码
import React, { Component } from 'react'

class MyComponent3 extends Component {  
  render() {
    return <div>{this.props.data}</div>
  }
}
const MyComponent3WithHOC = ExampleHoc(MyComponent3, 'name')

export default MyComponent3WithHOC
复制代码

实际上,此时的ExampleHoc和咱们最初对高阶组件的定义已经不一样。它已经变成了一个高阶函数,但这个高阶函数的返回值是一个高阶组件。咱们能够把它当作高阶组件的变种形式。这种形式的高阶组件大量出如今第三方库中。如react-redux中的connect就是一个典型。请去查看react-redux的api就能够知道了。

有问题望指出,谢谢!

参考:

  1. Higher-Order Components: higher-order-components

  2. React Higher Order Components in depth: React Higher Order Components in depth

相关文章
相关标签/搜索