用高阶组件,渲染属性,React Hook 实现组件复用

4,487 阅读3分钟

React 中实现组件复用的方法主要有三个,高阶组件,渲染属性,还有 React Hook,下面通过一个比较常见的复用场景,数据请求的场景,分别用这三种方法实现一次

准备工作

首先定义一些公共的属性和接口

// 定义请求接口的接口
export interface Result<T> {
  data: T;
  loading: boolean;
  hasData: boolean;
  hasError: boolean;
}

// 定义 http 请求参数的信息
export interface UrlInfo {
  url: string;
  path: string;
  method: string;
  query: { [prop: string]: string };
  body: any;
}

高阶组件

关于高阶组件,React 文档是这么提到的:

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

看起来很隐晦,可以联想下相似的概念,高阶函数,高阶函数指的是接受函数作为参数,或者返回值是函数的称之为高阶函数;高阶函数也是类似,高阶组件是参数为组件,返回值为新组件的函数

一个简单的请求高阶函数如下,需要传入请求的参数,还有一个子组件,往子组件里面注入组件自身的状态,就是请求的结果,还有外部传入的属性,作为子组件的属性

export default function highOrderRequest(requestOption) {
  return function (Component) {
    return class WapperedComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          data: null,
          loading: false,
          hasData: false,
          hasError: false,
        };
      }
      async componentDidMount() {
        this.setState({ loading: true });
        /** 请求相关代码,不写了 */
        await request();
        this.setState({
          data: {
            /** 请求回来的数据 */
          },
          loading: false,
          hasData: true,
          hasError: false,
        });
      }
      render() {
        const combinedProps = { ...this.props, ...this.state };
        return <Component {...combinedProps} />;
      }
    };
  };
}

方法的使用

function UseHighOrderExample(props) {
  console.log(props);
  const { data, loading, hasData, hasError } = props;
  return <div>high render</div>;
}

export default highOrderRequest({
  url: '',
  path: '/',
  method: 'post',
  body: { test: 1 },
})(UseHighOrderExample);

渲染属性

下面看下渲染属性,React 的文档如下:

术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

依旧很隐晦,其实可以理解为,组件的状态作为子组件的属性,传给子组件,同样类似的概念有回调函数,那渲染函数可以称之为回调组件

一个简单的请求的渲染属性实现方式

export default class RenderProps extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      data: null,
      loading: false,
      hasData: false,
      hasError: false,
    };
  }
  async componentDidMount() {
    this.setState({ loading: true });
    /** 请求相关代码,不写了 */
    await request();
    this.setState({
      data: {
        /** 请求回来的数据 */
      },
      loading: false,
      hasData: true,
      hasError: false,
    });
  }
  render() {
    return this.props.children(this.state);
  }
}

可以看到实际上渲染属性是往子组件注入了这个组件的 state,这样的话,子组件就可以获取父组件的 state

使用方式

export default function UseRenderProps(props) {
  return (
    <RenderProps {...props}>
      {(propsFromRenderProps) => {
        const { data, loading, hasData, hasError } = propsFromRenderProps;
        console.log(propsFromRenderProps);
        return <div>render props</div>;
      }}
    </RenderProps>
  );
}

React Hook

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

里面提到通过自定义 Hook,实现复用

一个简单的请求的自定义 Hook

export default function useRequest(requestOptions) {
  const [result, setResult] = useState({
    data: null,
    loading: false,
    hasData: false,
    hasError: false,
  });
  useEffect(() => {
    (async () => {
      setResult({
        data: null,
        loading: true,
        hasData: false,
        hasError: false,
      });
      await request();
      setResult({
        data: {
          /** 请求回来的数据 */
        },
        loading: false,
        hasData: true,
        hasError: false,
      });
    })();
  }, []);
  return result;
}

如何使用

export default function UseRequestEg(requestOptions) {
  const { data, loading, hasData, hasError } = useRequest(requestOptions);
  console.log({ hook: 1, data, loading, hasData, hasError });
  return <div>use hook</div>;
}

总结

通过高阶函数,渲染属性,hook 都可以实现代码复用,实现的方法大同小异;在自定义 Hook 大放异彩的时候,也请不要忘记高阶函数,渲染属性这些可以复用逻辑的方法。