React 进阶

高阶组件 (2/2)

Posted by Ericteen on August 16, 2017

高阶组件

引入组件

引入一个简单的高阶组件

import React, { Component } from 'react';

const HOC = (WrappedComponent) => {
  console.log('HOC');
  return class extends Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}
export default HOC;
import React, { Component } from 'react';
import HOC from './HOC';

class App extends Component {
  componentDidMount() {
      console.log(this.props, 'props')
    }

  render() {
    return (
      <div>
        App
      </div>
    )
  }
}
export default HOC(App);

组件 App 通过 HOC 的包裹,输出了 props。高阶组件 HOC 接收组件 App 并返回一个新的组件。在高阶组件里我们可以做很多操作,并且返回的新组件也有自己的生命周期。也可以将 props 传给 WrappedComponent

装饰器模式

高阶组件可以看做是装饰器模式 (Decorator Pattern) 在 React 中的实现。即允许向一个现有的组件添加新的功能,同时又不改变其结构。

ES7中添加了一个decorator的属性,使用@符表示,可以更精简的书写。那以上的例子就可以改成:

import React, { Component } from 'react';
import HOC from './HOC';

@HOC
export default class App extends Component {
  render() {
    return (
      <div>
        App
      </div>
    )
  }
}

HOC 的两种实现方式

属性代理(Props Proxy)

import React, { Component } from 'react'

const ppHOC = (WrapperComponent) => (
  class extends Component {
    render() {
      return <WrappedComponent {this.props} />
    }
  }
)

这里主要是 HOC 在 render 方法中 返回 了一个 WrappedComponent 类型的 React Element。我们还传入了 HOC 接收到的 props,这就是名字 Props Proxy 的由来。

使用 Props Proxy 可以用来:

  1. 操作 props
  2. 通过 Refs 访问到组件实例
  3. 提取 state
  4. 用其他元素包裹 WrappedComponent

操作 props

可对传给 WrappedComponent 的 props 进行读取,编辑,添加,删除等操作。

例如,向 WrappedComponent 中添加新的属性。

import React, { Component } from 'react'

const ppHOC = (WrapperComponent) => (
  class extends Component {
    render() {
      const newProps = {
        user: CurrentLoggedInUser
      }
      return <WrappedComponent {...this.props} {...newProps} />
    }
  }
)

通过 Refs 访问到组件实例

import React, { Component } from 'react'

const refsHOC = (WrapperComponent) => (
  class extends Component {
    method(wrappedComponentInstance) {
      wrappedComponentInstance.method()
    }

    render() {
      const props = Object.assign({}, this.props, {ref: this.method.bind(this)})
      return <WrappedComponent {...props} />
    }
  }
)

Ref 的回调函数会在 WrappedComponent 渲染时执行,你就可以得到 WrappedComponent 的引用。这可以用来读取/添加实例的 props ,调用实例的方法。

提取 state

通过传入 props 和回调函数把 state 提取出来。

import React, { Component } from 'react'

const ppHOC = (WrappedComponent) =>
  class extends Component {
    constructor(props) {
      super(props)
      this.state = {
        name: ''
      }

      this.onNameChange = this.onNameChange.bind(this)
    }
    onNameChange(event) {
      this.setState({
        name: event.target.value
      })
    }
    render() {
      const newProps = {
        name: {
          value: this.state.name,
          onChange: this.onNameChange
        }
      }
      return <WrappedComponent {...this.props} {...newProps}/>
    }
  }

也可以用 ES7 的装饰器

@ppHOC
class Example extends React.Component {
  render() {
    return <input name="name" {...this.props.name}/>
  }
}

这个 input 会自动成为受控input。

用其他元素包裹 WrappedComponent

为了封装样式、布局或别的目的,你可以用其它组件和元素包裹 WrappedComponent。 包裹样式:

const ppHOC = (WrappedComponent) => 
  class extends Component {
    render() {
      return (
        <div>
          <WrappedComponent {...this.props} />
        <div />
      )
    }
  }

反向继承(Inheritance Inversion)

const iiHOC = (WrappedComponent) => 
  class Enhancer extends WrappedComponent {
    render() {
      return super.render()
    }
  }

可以看出,返回的 Enhancer 类继承了 WrappedComponent

反响继承允许 HOC 通过 this 访问到 WrappedComponent,意味着它可以访问到 state、props、组件生命周期方法和 render 方法。注意通过 II 你可以创建新的生命周期方法。为了不破坏 WrappedComponent,记得调用 super.[lifecycleHook]。