React 高阶组件其实很简单

·  阅读 3227
原文链接: blog.hhking.cn

前言

高阶组件 High-Order Components,不属于 React 的 API,而是 React 的一种运用技巧,或者说设计模式。所以,不使用高阶组件,也是可以实现功能,但是掌握高阶组件,可以提高代码的灵活性和复用性,实现起来更加优雅。当然,学会 React 的高级运用,也可以让你更加深入的理解 React。

高阶组件,其实没有想象的那么复杂。

什么是高阶组件

高阶组件是一个函数,接收一个组件,然后返回一个新的组件。

const EnhancedComponent = highOrderComponent(WrappedComponent);
复制代码

要记住的是,虽然名称是高阶组件,但是高阶组件不是组件,而是函数

既然是函数,那就可以有参数,有返回值。从上面可以看出,这个函数接收一个组件 WrappedComponent 作为参数 ,返回加工过的新组件 EnhancedComponent。其实高阶组件就是设计模式里的装饰者模式。

可以说,组件是把 props 转化成 UI,而高阶组件是把一个组件转化成另外一个组件。

下面是一个简单的高阶组件:

import React, { Component } from 'react';

export default (WrappedComponent) => {
  return class EnhancedComponent extends Component {
    // do something
    render() {
      return <WrappedComponent />;
    }
  }
}
复制代码

从上面的代码可以看出,我们可以对传入的原始组件 WrappedComponent 做一些你想要的操作(比如操作 props,提取 state,给原始组件包裹其他元素等),从而加工出你想要的组件 EnhancedComponent 。把通用的逻辑放在高阶组件中,对组件实现一致的处理,从而实现代码的复用。

使用高阶组件

高阶组件的一个应用场景,就是在表单中的使用。

React 中 受控组件,是通过 state 来控制用户的输入行为,所以我们需要通过监听 onChange 来更新 state,例如:

import React, { Component } from 'react';

class Input extends Component {
  constructor(props) {
    super(props);

    this.state = {
      value: '',
    }
  }
  
  handleChange = (e) => {
    this.setState({
      value: e.target.value,
    });
  }
  
  render() {
    return (
      <input type="text" value={this.state.value} onChange={this.handleChange} />
    );
  }
}

export default Input;
复制代码

通常,我们还需要对表单元素进行验证,这时可能需要在 handleChange 中添加验证的逻辑。如果是一个表单元素,这样写没什么问题,但是当表单元素越来越多,还是这样一个一个元素的添加逻辑的话,是一种什么体验?如果产品再改一下需求,那你就只能含泪加班了!

这时候,高阶组件的作用就可以体现出来了。只需要把所有表单元素通用的逻辑放在高阶组件中。把输入框需要的 valueonChange 提取到高阶组件,再通过 props 传给原始的组件:

import React, { Component } from 'react';

export default (WrappedComponent) => {
  return class InputHOC extends Component {
    constructor(props) {
      super(props);
      this.state = {
        value: '',
      }
    }

    handleChange = (e) => {
      this.setState({
        value: e.target.value,
      });
    }

    render() {
      const passProps = {
        value: this.state.value,
        onChange: this.handleChange,
      }
      return <WrappedComponent {...passProps} />;
    }
  }
}
复制代码

然后表单元素里就不需要实现 stateonChange 的逻辑,只需要处理传入的 props, 并调用一下高阶组件,得到的就是带有我们需要的属性和功能的新组件

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

import './style.css';

class Input extends Component {
  render() {
    return (
      <input className="input" type="text" {...this.props} />
    )
  }
}

export default InputHOC(Input);
复制代码

然后你只要拿新的组件去使用,就可以了。如果还有其他的表单元素,同样只需要调用高阶组件,就可以得到你需要的组件。这就是一个简单的高阶组件的运用,实现代码的复用。

从上面的例子可以看出,在高阶组件里,你可以操作 propsstate,甚至修改 render 方法里的渲染内容。

高阶组件的模式和数据流实际如图:

一些注意点

参数

既然高级组件是一个函数,那么这个函数也可以有多个参数,例如:

export default (WrappedComponent, data) => {
  return class EnhancedComponent extends Component {
    // do something with data
    render() {
      return <WrappedComponent />;
    }
  }
}
复制代码

命名

高阶组件的创建的容器组件的名称在 React Developer Tools 中看起来和普通组件一样,看不出是不是使用了高阶组件。

为了方便调试,一般约定用容器组件类名包裹原始组件来命名容器组件:

import React, { Component } from 'react';

const getDisplayName = (WrappedComponent) => {
  return WrappedComponent.displayName ||
    WrappedComponent.name ||
    'Component'
}

export default (WrappedComponent, validate) => {
  return class InputHOC extends Component {
    static displayName = `InputHOC(${getDisplayName(WrappedComponent)})`;
    constructor(props) {
      super(props);
      this.state = {
        value: '',
      }
    }

    handleChange = (e) => {
      console.log(e);
      this.setState({
        value: e.target.value,
      });
    }

    render() {
      const passProps = {
        value: this.state.value,
        onChange: this.handleChange,
      }
      return <WrappedComponent {...passProps} />;
    }
  }
}
复制代码

结果如下,这样就可以直观的看出是使用了高阶组件:

多层高阶组件

可以对一个组件,应用多个高阶组件,形成多层嵌套:

Input = InputHOC(Input);
Input = InputHHOC(Input);
复制代码

总结

高阶组件是属于 React 高级运用,但是其实是一个很简单的概念,但是它非常实用。在实际的业务场景中,灵活合理的使用高阶组件,可以提高代码的复用性和灵活性。

对高阶组件,我们可以总结以下几点:

  • 高阶组件是一个函数,而不是组件
  • 组件是把 props 转化成 UI,高阶组件是把一个组件转化成另一个组件
  • 高阶组件的作用是复用代码
  • 高阶组件对应设计模式里的装饰者模式
分类:
前端
分类:
前端