将高阶组件(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
和connect
HOC。前者允许您在传递给它的任何组件中访问路由属性,而后者则可以从输入组件连接到 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 设置为柯里化函数,这意味着它将接受多个参数。因此,您不仅可以将组件作为参数传递,还可以将其他值作为第二个参数传递。对于withDataFetching
HOC,您还可以发送一个包含组件 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;
返回值的初始值在调用useState
Hook 时设置,可以使用 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
值的方式之外,没有任何变化。loading
results
error
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;
通过创建自定义useDataFetching
Hook,您现在可以使用 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