# 高阶组件

高阶组件是重用组件逻辑的一项高级技术。

高阶组件(HOC全称Higher-order component)是一种React的进阶使用方法,主要还是为了便于组件的复用。

高阶组件类似于高阶函数,接收 React 组件作为输入,输出一个新的 React 组件。高阶组件让代码更具有复用性、逻辑性与抽象特征。可以对 render 方法作劫持,也可以控制 props 与 state。

通俗来讲高阶组件是一个函数,能够接受一个组件并返回一个高级的组件。

var newComponent = higherOrderComponent(oldComponent);

最简单的HOC实现是这个样子的:

//定义高阶组件
function HOCFactory (WrappedComponent){
    //返回一个中间组件,在其中可包含复用逻辑
    return class extends Component{
        return <WrappedComponent {...this.props}/>
    }
}

class MyComponent extends Component{}

//创建高阶组件
export default HOCFactory(MyComponent)

# 什么时候使用高阶组件?

在React开发过程中,发现有很多情况下,组件需要被"增强",比如说给组件添加或者修改一些特定的props,一些权限的管理,或者一些其他的优化之类的。而如果这个功能是针对多个组件的,同时每一个组件都写一套相同的代码,明显显得不是很明智,所以就可以考虑使用高阶组件。 bad-component

HOC: hoc-component

例如:react-redux的connect方法就是一个HOC,他获取wrappedComponent,在connect中给wrappedComponent添加需要的props。

# 高阶组件实现

实现高阶组件的方法有如下两种。

  1. 属性代理(props proxy)。属性组件通过被包裹的 React 组件来操作 props。
  • 反向代理(inheritance inversion)。高阶组件继承于被包裹的 React 组件。

# 属性代理

属性代理是常见高阶组件的实现方法。

const MyContainer = (WrappedComponent) => {
    return class extends Component {
        //可复用逻辑
        render() {
            return (
                <WrappedComponent
                    {...props}
                />
            )
        }
    }
}

export default MyContainer;

原始组件想要具备高阶组件对它的修饰,有这样两种方式:

# 方式一:传统方式

export default function HOCFactory (WrappedComponent){
    return class extends Component{
        return <WrappedComponent {...this.props}/>
    }
}

class MyComponent extends Component{}

export default HOCFactory(MyComponent)

# 方式二:ES7 decorator(装饰器)

ES7 添加了 decorator 的属性,我们可以通过 decorator 来转换,以此简化高阶组件的调用。

export default function HOCFactory (WrappedComponent){
    return class extends Component{
        return <WrappedComponent {...this.props}/>
    }
}

@HOCFactory
class MyComponent extends Component{}

export default MyComponent;

生命周期

HOC didmount -> (WrappedComponent didmount) -> (WrappedComponent will unmount) -> Hoc will unmount

# 作用

高阶组件可以控制 props、通过 refs 使用引用、抽象 state 和使用其他元素包裹 WrappedComponent。

# 控制 props

我们可以读取、增加、编辑或是移除从 WrappedComponent 传进来的 props,但需要小心删除与编辑重要的 props。应该尽量对高阶组件的 props 作新的命名以防止混淆。

const myContainer = (WrappedComponent)=>{
    return class extends React.Component{
        render(){
            const newProps = {
                ...this.props,
                text: nextText
            }
            return (<WrappedComponent
                {...this.props}
            />)
        }
    }
}
export default myContainer;

当调用该高阶组件时,就可以使用 text 这个新的 props了。

# 通过 refs 使用引用

在高阶组件中,可以接受 refs 使用 WrappedComponent 的引用。

//子组件
class Child extends React.Component{
  render(){
    return <input />
  }
}

//高阶组件
function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    render() {
        const {forwardedRef, ...rest} = this.props;
        return <WrappedComponent ref={forwardedRef} {...rest} />;
    }
  }

  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

//通过logProps 生成一个新组件
const logProps=logProps(Child);


class Father extends React.Component{
  constructor(props){
    super(props);
    this.myRef=React.createRef();
  }
  componentDidMount(){
    console.log(this.myRef.current); // 输出为 child
  }
  render(){
    return <LogProps ref={this.myRef}/>
  }
}
# 抽象 state

可以通过 WrappedComponent 提供的 props 和回调函数抽象 state。将原组件抽象为展示型组件,分离内部状态 。

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

        onNameChange(text) {
            this.setState({
                name: text
            });
        }

        render() {
            const newProps =  {
                name = {
                    value: this.state.name,
                    onChangeText: this.onNameChange
                }
            }
            return (
                <WrappedComponent
                    {...this.props}
                    {...newProps}
                />
            );
        }
}

export default MyContainer;

使用

@MyContainer
class MyComponent extends Component {
    render() {
        return (
            <TextInput
                {...this.props.name}
            />
        );
    }
}
# 使用其他元素包裹 WrappedComponent

可以使用其他元素包裹 WrappedComponent。

const MyContainer = (WrappedComponent) => {
    return class extends Component {
        render() {
            return (
                <View>
                    <WrappedComponent />
                </View>
            );
        }
    }
}

export default MyContainer;

# HOC可以做什么?

  • 代码复用,代码模块化
  • 增删改props
  • 渲染劫持

其实,除了代码复用和模块化,HOC做的其实就是劫持,由于传入的wrappedComponent是作为一个child进行渲染的,上级传入的props都是直接传给HOC的,所以HOC组件拥有很大的权限去修改props和控制渲染。

# 代码复用

例如:组件A的代码 与组件B的代码 部分逻辑相同,针对这种情况,我们可以采用高阶组件去复用这部分逻辑。

https://segmentfault.com/a/1190000008112017

https://juejin.im/post/5bd68bc5518825287847a860