高阶组件HOC即Higher Order Component是React中用于复用组件逻辑的一种高级技巧,HOC自身不是React API的一部分,它是一种基于React的组合特性而形成的设计模式。
描述高阶组件从名字上就透漏出高级的气息,实际上这个概念应该是源自于JavaScript的高阶函数,高阶函数就是接受函数作为输入或者输出的函数,可以想到柯里化就是一种高阶函数,同样在React文档上也给出了高阶组件的定义,高阶组件是接收组件并返回新组件的函数。
A higher-order component is a function that takes a component and returns a new component.具体而言,高阶组件是参数为组件,返回值为新组件的函数,组件是将props转换为UI,而高阶组件是将组件转换为另一个组件。HOC在React的第三方库中很常见,例如Redux的connect和Relay的createFragmentContainer。
// 高阶组件定义 const higherOrderComponent = (WrappedComponent) => { return class EnhancedComponent extends React.Component { // ... render() { return <WrappedComponent {...this.props} />; } }; } // 普通组件定义 class WrappedComponent extends React.Component{ render(){ //.... } } // 返回被高阶组件包装过的增强组件 const EnhancedComponent = higherOrderComponent(WrappedComponent);在这里要注意,不要试图以任何方式在HOC中修改组件原型,而应该使用组合的方式,通过将组件包装在容器组件中实现功能。通常情况下,实现高阶组件的方式有以下两种:
属性代理Props Proxy。
反向继承Inheritance Inversion。
属性代理例如我们可以为传入的组件增加一个存储中的id属性值,通过高阶组件我们就可以为这个组件新增一个props,当然我们也可以对在JSX中的WrappedComponent组件中props进行操作,注意不是操作传入的WrappedComponent类,我们不应该直接修改传入的组件,而可以在组合的过程中对其操作。
const HOC = (WrappedComponent, store) => { return class EnhancedComponent extends React.Component { render() { const newProps = { id: store.id } return <WrappedComponent {...this.props} {...newProps} />; } } }我们也可以利用高阶组件将新组件的状态装入到被包装组件中,例如我们可以使用高阶组件将非受控组件转化为受控组件。
class WrappedComponent extends React.Component { render() { return <input />; } } const HOC = (WrappedComponent) => { return class EnhancedComponent extends React.Component { constructor(props) { super(props); this.state = { name: "" }; } render() { const newProps = { value: this.state.name, onChange: e => this.setState({name: e.target.value}), } return <WrappedComponent {...this.props} {...newProps} />; } } }或者我们的目的是将其使用其他组件包裹起来用以达成布局或者是样式的目的。
const HOC = (WrappedComponent) => { return class EnhancedComponent extends React.Component { render() { return ( <div> <WrappedComponent {...this.props} /> </div> ); } } } 反向继承反向继承是指返回的组件去继承之前的组件,在反向继承中我们可以做非常多的操作,修改state、props甚至是翻转Element Tree,反向继承有一个重要的点,反向继承不能保证完整的子组件树被解析,也就是说解析的元素树中包含了组件(函数类型或者Class类型),就不能再操作组件的子组件了。
当我们使用反向继承实现高阶组件的时候可以通过渲染劫持来控制渲染,具体是指我们可以有意识地控制WrappedComponent的渲染过程,从而控制渲染控制的结果,例如我们可以根据部分参数去决定是否渲染组件。
甚至我们可以通过重写的方式劫持原组件的生命周期。
const HOC = (WrappedComponent) => { return class EnhancedComponent extends WrappedComponent { componentDidMount(){ // ... } render() { return super.render(); } } }由于实际上是继承关系,我们可以去读取组件的props和state,如果有必要的话,甚至可以修改增加、修改和删除props和state,当然前提是修改带来的风险需要你自己来控制。在一些情况下,我们可能需要为高阶属性传入一些参数,那我们就可以通过柯里化的形式传入参数,配合高阶组件可以完成对组件的类似于闭包的操作。
const HOCFactoryFactory = (params) => { // 此处操作params return (WrappedComponent) => { return class EnhancedComponent extends WrappedComponent { render() { return params.isRender && this.props.isRender && super.render(); } } } } HOC与Mixin使用Mixin与HOC都可以用于解决横切关注点相关的问题。
Mixin是一种混入的模式,在实际使用中Mixin的作用还是非常强大的,能够使得我们在多个组件中共用相同的方法,但同样也会给组件不断增加新的方法和属性,组件本身不仅可以感知,甚至需要做相关的处理(例如命名冲突、状态维护等),一旦混入的模块变多时,整个组件就变的难以维护,Mixin可能会引入不可见的属性,例如在渲染组件中使用Mixin方法,给组件带来了不可见的属性props和状态state,并且Mixin可能会相互依赖,相互耦合,不利于代码维护,此外不同的Mixin中的方法可能会相互冲突。之前React官方建议使用Mixin用于解决横切关注点相关的问题,但由于使用Mixin可能会产生更多麻烦,所以官方现在推荐使用HOC。
高阶组件HOC属于函数式编程functional programming思想,对于被包裹的组件时不会感知到高阶组件的存在,而高阶组件返回的组件会在原来的组件之上具有功能增强的效果,基于此React官方推荐使用高阶组件。