Higher Order Component

在国内 Higher Order Component 似乎是被译为“高阶组件”?第一次看到这个词的时候愣了半天硬是没反应过来。我还是习惯简称它为 HOC(发音 [hɑk])],在接下来内容中我就用 HOC 作为 High Order Component 的缩写

关于什么是 HOC, Facebook 的官方文档已经做出了详细的解释,详细到有点繁琐了。在这里我借鉴 reactpatterns 的另一个实例也同样能说明问题

假设你目前有一个 stateless 组件用于欢迎用户

const Greeting = ({ name }) => {
  return <div>Hi {name}!</div>
}

但是这个组件仅仅能用于展示数据,我们想让它变得能力更强,比如能够主动加载用户信息(无论是从后端还是 store 中),在缺失用户信息是给出适当提示等等。那么可以在不改动原组件的基础上,使用另一个组件将它封装起来:

class AdcancedGreeting extends React.Component {
  constructor(pops) {
    super()
    this.state = { name: "" }
  }
  componentDidMount() {
    // 或者从远程加载数据
    this.setState({ name: "Michael" })
  }
  render() {
    if (!this.state.name) {
      return null
    }
    return (
      <Greeting name={this.state.name} />
    )
  }
}

还可以考虑的更长远一些,不仅仅是 Greeting 组件需 name 数据、需要异常处理,所以我们可以把封装行为抽象为一个函数,能够封装任何有相同需求的组件

const connect = (ComposedComponent) => {
  class extends React.Component {
    constructor(pops) {
      super()
      this.state = { name: "" }
    }
    componentDidMount() {
      // 或者从远程加载数据
      this.setState({ name: "Michael" })
    }
    render() {
      if (!this.state.name) {
        return null
      }
      return (
        <ComposedComponent {...this.props} name={this.state.name} />
      )
    }
  }
}

那么之前定义 AdcancedGreeting 就可以通过这个函数实现了:

const AdcancedGreeting = connect(Greeting)

从形式上看,HOC 是向一个函数传入组件之后返回的新组件。但这么做本质上是为了提高了组件逻辑的复用性,对于有相同处理逻辑的组件,相同的处理逻辑代码不必重复两次,而是放在工厂函数里。

再往上抽象点看,HOC 的功能私塾是向组件中注入属性

Currying 技巧

如果我们想自定义更多的属性怎么办,可以向 connect 函数中传入更多的参数即可,例如

const AdcancedGreeting = connect(Greeting, customProp1, customProp2)

这种模式在 React 生态中使用的非常广,例如 Redux 中的 connect 函数使用的就是相同的原理。不过它的调用方式可能稍有不同:

const AdvancedComponent = connect(mapStateToProps, mapDispathToProps)(TargetComponent)

看上去是两次调用函数,为什么不直接把 TargetComponent 也当作参数传入 connect 函数中好了

为了回答这个问题,我们先考虑另一个需求:定义一个函数,只传入一个参数,始终把传入参数加 10,这非常好实现:

const add10 = (x) => {
    return x + 10
}

那如果更多的需求可能会是让传入参数加 20 加 30 甚至不计其数怎么办,那么 return x + n 这行代码无疑会被复制粘贴很多次。与其如此,我们不够构造一个工厂函数,能够返回任意 x + n 的计算函数

const add = (x) => {
    return (y) => {
        return x + y
    }
}

接下来当我们需要 add30、add40 等等时,只要调用 add 即可:

const add30 = add(30)
const add40 = add(40)

我们当然也可以直接通过 add 函数计算 x + n 的值:

const result = add(n)(x)

这就是 connect 函数的原理和雏形,这种编程技巧成为 currying

connect(mapStateToProps, mapDispatchToProps)(TargetComponent) 应该拆成两部分看,connect(mapStateToProps, mapDispatchToProps) 部分实际上是返回了一个工厂函数,我们不如称之为 enhance, enhance 的独特之处在于我们已经为它注入一些特殊的属性;而 enhance(TargetComponent) 则正式的对传入组件进行封装,并且返回新的组件。enhance 其实是可以被复用的,当不同的组件都有需求被注入相同的属性,我们都可以调用 enhance 函数对它进行封装

总结

在我看来,HOC 的本质是可以赋予组件能力,例如 withRouter 赋予了组件访问路由的能力;rc-form 赋予了组件快速创建表单、校验表单的能力;你还可以编写一个名为 withUserRequest 的HOC用来赋予组件发出请求用户请求的能力。只不过这些能力都是通过属性注入来实现的

参考材料

results matching ""

    No results matching ""