将高阶组件(HOC)重构为 React Hooks
随着 React 16.8 版本的发布(也被冠以“The One With Hooks”的称号),人们期待已久的 Hooks 模式也随之推出。这种模式让你无需使用类即可使用状态、生命周期以及(几乎)任何其他 React 功能。如果你已经使用 React 多年,这或许会让你感到如释重负,或许会让你感到震惊。对我来说,这感觉就像是解脱了,因为我一直以来都更喜欢使用函数组件而不是类组件。为了避免处理过多的类组件,我正在做的一些项目正在使用高阶组件(HOC)来复用类逻辑——这可能会变得相当复杂。在这篇文章中,我将把其中一个 HOC 转换为自定义 Hook,以展示这种“新”模式的强大功能。
附注:您可以根据自己的喜好使用类或 Hook,因为目前还没有关于类使用的任何重大变更。
 当您阅读本文时,您可能已经尝试过任何 Hook,或者至少阅读过很多相关内容。如果您还没有尝试过, React 官方文档中的这篇概述是一个很好的起点。
高阶组件(HOC)
如前所述,HOC 是一种在 React 应用程序中复用(类)组件逻辑的模式。这样,您无需重复示例中基于状态更新的逻辑,例如数据获取或路由。React 文档将 HOC 描述为“接受组件并返回新组件的函数”,大致意味着用作 HOC 输入的组件将被增强并作为另一个组件返回。HOC 在 React 中被诸如react-router或 之类的包广泛使用react-redux。这些包中的 HOC 示例包括withRouter和connectHOC。前者允许您在传递给它的任何组件中访问路由属性,而后者则可以从输入组件连接到 Redux 状态。
创建 HOC 并不难,React 官网的文档对此withDataFetching进行了详细的解释。我将通过创建一个名为 的新 HOC 来演示。这将为传递给此 HOC 的任何组件添加使用状态和生命周期的基本数据获取功能。使用 Github API,将创建一个组件来渲染我的公共仓库列表。
- 首先创建一个函数,该函数接受一个组件作为输入,并基于该组件返回一个不同的组件。该函数的作用仅仅是构造一个WithDataFetching返回输入组件的新类组件WrappedComponent。
import React from "react";
const withDataFetching = props => WrappedComponent => {
  class WithDataFetching extends React.Component {
    render() {
      return (
        <WrappedComponent />
      );
    }
  }
  return WithDataFetching;
};
export default withDataFetching;
- 之后,您可以使用状态和生命周期将数据获取逻辑添加到此函数中。在状态中constructor()设置初始值,而在异步componentDidMount()生命周期中使用fetch()方法完成数据获取。
import React from "react";
const withDataFetching = props => WrappedComponent => {
  class WithDataFetching extends React.Component {
    constructor() {
      super();
      this.state = {
        results: [],
        loading: true,
        error: ""
      };
    }
    async fetchData() {
      try {
        const data = await fetch(props.dataSource);
        const json = await data.json();
        if (json) {
          this.setState({
            results: json,
            loading: false
          });
        }
      } catch (error) {
        this.setState({
          loading: false,
          error: error.message
        });
      }
    }
    async componentDidMount() {
      this.fetchData();
    }
    // ...
  }
  return WithDataFetching;
};
export default withDataFetching;
- render()在方法中,- WrappedComponent将返回 ,并且状态值- loading,- results和- error应该作为 props 传递给它。这样,数据获取返回的结果将在输入组件上可用。
import React from "react";
const withDataFetching = props => WrappedComponent => {
  class WithDataFetching extends React.Component {
    // ...
    render() {
      const { results, loading, error } = this.state;
      return (
        <WrappedComponent
          results={results}
          loading={loading}
          error={error}
          {...this.props}
        />
      );
    }
  }
  return WithDataFetching;
};
export default withDataFetching;
- 最后,你可以设置 HOC 返回的组件的显示名称,否则,这个新组件在 React DevTools 等应用中很难追踪。这可以通过设置组件displayName的来实现WithDataFetching。
import React from "react";
const withDataFetching = props => WrappedComponent => {
  class WithDataFetching extends React.Component {
    // ...
    render() {
      // ...
    }
  }
  WithDataFetching.displayName = `WithDataFetching(${WrappedComponent.name})`;
  return WithDataFetching;
};
export default withDataFetching;
这创建了一个 HOC,可用于向传递给此函数的任何组件添加数据获取功能。如您所见,此 HOC 设置为柯里化函数,这意味着它将接受多个参数。因此,您不仅可以将组件作为参数传递,还可以将其他值作为第二个参数传递。对于withDataFetchingHOC,您还可以发送一个包含组件 props 的对象,其中 propsdataSource用作方法的 url 。您传递给此对象的任何其他 props 都将在返回的 urlfetch()上进行传播。WrappedComponent
- Repositories在名为HOC组件的函数组件中,- withDataFetching必须导入它。该文件默认导出的是 HOC 组件,它接受- Repositories组件本身以及一个包含字段 的对象- dataSource。该字段的值是 Github API 的 URL,用于根据用户名检索仓库。
import React from "react";
import withDataFetching from "./withDataFetching";
function Repositories() {
  return '';
}
export default withDataFetching({
  dataSource: "https://api.github.com/users/royderks/repos"
})(Repositories);
- 由于 HOC 为Repositories组件添加了数据获取功能,因此 propsloading、results和error会被传递给该组件。它们由 中的状态和生命周期值组成withDataFetching,可用于显示所有仓库的列表。当对 Github API 的请求尚未解析或发生错误时,将显示一条消息而不是仓库列表。
import React from "react";
import withDataFetching from "./withDataFetching";
function Repositories({ loading, results, error }) {
  if (loading || error) {
    return loading ? "Loading..." : error.message;
  }
  return (
    <ul>
      {results.map(({ id, html_url, full_name }) => (
        <li key={id}>
          <a href={html_url} target="_blank" rel="noopener noreferrer">
            {full_name}
          </a>
        </li>
      ))}
    </ul>
  );
}
export default withDataFetching({
  dataSource: "https://api.github.com/users/royderks/repos"
})(Repositories);
通过这最后的更改,Repositories可以显示在 HOC 中完成的数据获取结果。这可以用于任何端点或组件,因为 HOC 使逻辑复用变得容易。
在下面的CodeSandbox中,您可以看到将Repositories组件传递给 HOC 的结果:
自定义钩子
在本文的引言中,我提到 Hooks 使得在类组件之外使用 React 特性(例如状态)成为可能。更正一下:Hooks 只能在函数组件中使用。此外,通过构建自定义 Hooks,你可以以几乎相同的方式重用之前 HOC 中的数据获取逻辑。但首先,让我们简单了解一下 Hooks,特别是useState()和useEffect()Hook。
- 
  Hook让您可以处理任何函数组件的状态, useState()而无需使用constructor()and/orthis.setState()方法。
- 
  Hook相当于生命周期方法。仅使用这个 Hook useEffect(),你就可以监视特定(状态)变量的更新,或者根本不监视任何变量。componentDidMount()componentDidUpdate()
如果您还不熟悉这些 Hook,这可能听起来有些令人困惑,但幸运的是,您将使用这两个 Hook 来创建一个自定义useDataFetching()Hook。此 Hook 将具有与 HOC 相同的数据获取逻辑withDataFetching,并使用 方法调用 Github API fetch()。此 Hook 将返回与 HOC 相同的值,即loading、results和error。
- useDataFetching首先,你需要创建一个接受参数的函数- dataSource,该参数是稍后需要获取的 URL。- react由于你想使用 React 特性,所以需要将这个自定义 Hook 作为依赖项,并从中导入你将要使用的两个 Hook。
import React, { useState, useEffect } from "react";
function useDataFetching(dataSource) {
  return {};
}
export default useDataFetching;
- Hook 应该返回值loading、results和error;这些值必须添加到此 Hook 的状态中,然后返回。使用useState()Hook,您可以创建这些状态值,以及一个用于更新这些值的函数。但首先要创建状态值,并在函数末尾返回它们useDataFetching。
import React, { useState, useEffect } from "react";
function useDataFetching(dataSource) {
  const [loading, setLoading] = useState(true);
  const [results, setResults] = useState([]);
  const [error, setError] = useState("");
  return {
    loading,
    results,
    error
  };
}
export default useDataFetching;
返回值的初始值在调用useStateHook 时设置,可以使用 Hook 返回的数组的第二个值进行更新。第一个值是当前状态值,因此应在自定义 Hook 结束时返回。
- 在 HOC中,withDataFetching有一个名为 的函数用于向 Github API 发送请求fetchData。此函数也必须添加到自定义 Hook 中。唯一的区别在于,状态值不是使用this.setState()方法来更新,而是通过调用 Hook 返回的更新函数来更新useState()。此fetchData函数必须放在useEffect()Hook 内部,这样您就可以控制何时调用此函数。
import React, { useState, useEffect } from "react";
function useDataFetching(dataSource) {
  const [loading, setLoading] = useState(true);
  const [results, setResults] = useState([]);
  const [error, setError] = useState("");
  useEffect(() => {
    async function fetchData() {
      try {
        const data = await fetch(dataSource);
        const json = await data.json();
        if (json) {
          setLoading(false);
          setResults(json);
        }
      } catch (error) {
        setLoading(false);
        setError(error.message);
      }
      setLoading(false);
    }
    fetchData();
  }, [dataSource]);
  return {
    error,
    loading,
    results
  };
}
export default useDataFetching;
在上面的代码块中,fetchData当的值dataSource更新时,将调用该函数,因为该值被添加到 Hook 的依赖数组中useEffect()。
现在,您可以从函数组件中调用自定义useDataFetching()Hook 来使用该组件中的数据获取值。与 HOC 不同,这些值不是作为 props 添加到组件中,而是由 Hook 返回的。
- 在一个名为 的新函数组件中,RepositoriesHooks您需要导入useDataFetching()并解构 的值loading,results以及error此 Hook 返回的结果。从 Github API 检索用户所有存储库的 URL 应作为参数添加。
import React from "react";
import useDataFetching from "./useDataFetching";
function RepositoriesHooks() {
  const { loading, results, error } = useDataFetching("https://api.github.com/users/royderks/repos");
  return '';
}
export default RepositoriesHooks;
- 要在列表中显示存储库,您可以复制组件的返回值,因为除了在此组件中添加、和的Repositories值的方式之外,没有任何变化。loadingresultserror
import React from "react";
import useDataFetching from "./useDataFetching";
function RepositoriesHooks() {
  const { loading, results, error } = useDataFetching(
    "https://api.github.com/users/royderks/repos"
  );
  if (loading || error) {
    return loading ? "Loading..." : error.message;
  }
  return (
    <ul>
      {results.map(({ id, html_url, full_name }) => (
        <li key={id}>
          <a href={html_url} target="_blank" rel="noopener noreferrer">
            {full_name}
          </a>
        </li>
      ))}
    </ul>
  );
}
export default RepositoriesHooks;
通过创建自定义useDataFetchingHook,您现在可以使用 React Hooks 在任何函数组件中使用数据获取,而无需创建 HOC。如果您想在CodeSandbox中查看受影响的更改,则需要注释掉该Repositories组件的导入src/index.js,并改为导入该RepositoriesHooks组件。
import React from "react";
import ReactDOM from "react-dom";
// import Repositories from "./Repositories";
import { default as Repositories } from "./RepositoriesHooks";
function App() {
  return (
    <div className="App">
      <h1>My Github repos</h1>
      <Repositories />
    </div>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
概括
新的 Hooks 模式使得在类组件之外使用 React 的状态、生命周期和其他功能成为可能。之前,您只能在类组件中使用这些功能,因此需要高阶组件 (HOC) 来复用您放入其中的任何逻辑。从 React 16.8 版本开始,您可以使用 Hook 从函数组件访问 React 功能,例如状态。通过创建自定义 Hook(例如useDataFetching()上面的 Hook),您可以在任何函数组件中复用示例中的状态逻辑。
希望这篇文章能帮助你决定是否应该将你的 HOC 转换为自定义 Hook!别忘了留下任何反馈,或者在Twitter上关注我,及时了解最新动态😄!
文章来源:https://dev.to/gethackteam/from-higher-order-components-hoc-to-react-hooks-2bm9 后端开发教程 - Java、Spring Boot 实战 - msg200.com
            后端开发教程 - Java、Spring Boot 实战 - msg200.com