如果您在 React 生态系统中待了一段时间,您可能听说过 **高阶组件**。 让我们看一下简单的实现,同时尝试解释核心思想。 从这里,您应该了解它们的工作原理,甚至可以将它们投入使用。
为什么选择高阶组件?
在构建 React 应用程序时,您会遇到想要 *在多个组件中共享相同功能* 的情况。
例如:您需要管理应用程序中当前登录用户的状态。 而不是在所有需要该状态的组件中管理该状态,您可以创建一个高阶组件,将登录用户状态分离到一个容器组件中,然后将该状态传递给将使用它的组件。

从高阶组件接收状态的组件将充当 *展示* 组件。 状态被传递给它们,它们根据它有条件地渲染 UI。 它们不关心状态的管理。
让我们看另一个例子。 假设您的应用程序中有三个 JSON 文件。 这些文件包含将在三个不同组件中加载到应用程序中的不同数据。 您希望让用户能够*搜索*从这些文件加载的数据。 您可以在所有三个组件中实现搜索功能。 此重复在开始时可能不是问题,但随着应用程序的增长,更多组件需要此功能,持续重复将变得繁琐且容易出现问题。
更好的前进方向是创建一个高阶组件来处理搜索功能。 使用它,您可以将其他组件分别包装在您的高阶组件中。
高阶组件是如何工作的?
React 文档 表示高阶组件 **接收一个组件** 并 **返回一个组件**。
当您在架构上准备好将容器组件与展示组件分离时,高阶组件的使用非常方便。 展示组件通常是一个无状态函数组件,它接收道具并渲染 UI。 无状态函数组件是简单的 JavaScript 函数,没有状态。 以下是一个示例
import React from 'react'
const App = ({name}) => {
return (
<div>
<h2>This is a functional component. Your name is {name}.</h2>
</div>
)
}
ReactDOM.render(<App name='Kingsley' />, document.getElementById("root"));
容器组件负责管理状态。 在这种情况下,容器是高阶组件。
在之前提到的搜索示例中,搜索组件将是管理搜索状态并包装需要搜索功能的展示组件的容器组件。 展示组件通常不知道状态或如何管理状态。
高阶组件示例
让我们从一个基本的示例开始。 以下是一个将用户名转换为大写并返回它的高阶组件
const hoc = (WrappedComponent) => (props) => {
return (
<div>
<WrappedComponent {...props}>
{props.children.toUpperCase()}
</WrappedComponent>
</div>
)
}
此高阶组件接收一个 WrappedComponent
作为参数。 然后它返回一个带有一些传递给它的道具的*新*组件,并创建一个 React 元素。 我们在 props.children
上调用 .toUpperCase()
,将传递的 props.children
转换为大写。
为了使用这个高阶组件,我们需要创建一个接收道具并渲染子元素的组件。
const Username = (props) => (
<div>{props.children}</div>
)
接下来,我们用高阶组件包装 Username
。 让我们将其存储在一个变量中
const UpperCaseUsername = hoc(Username)
在我们的 App
组件中,我们现在可以像这样使用它
const App = () => (
<div>
<UpperCaseUsername>Kingsley</UpperCaseUsername>
</div>
);
UpperCaseUsername
组件仅仅是 Username
UI 的呈现,而 Username
又从充当高阶组件的 WrappedComponent
中获取状态。
更实用的高阶组件
想象一下,我们想创建一个包含一个搜索表单的地理位置列表,用于过滤它们。 JSON 将位于平面文件中,并作为单独的组件加载。 让我们从加载数据开始。

我们的第一个组件将为用户加载地理位置。 我们将使用 .map()
来遍历该 JSON 文件中包含的数据。
import React from 'react'
// Where the data is located
import preload from './locations.json'
// Manages the data
import LocationCard from './LocationCard'
// Renders the presentation of the data
const Location = (props) => {
return (
<div>
<div>
<div>
<h2>Preferred Locations</h2>
</div>
</div>
<div>
{preload.data
.map(location => <LocationCard key={location.id} {...location} />)}
</div>
</div>
)
}
export default Location
此组件将在 LocationCard
组件中渲染数据。 我将其移到另一个组件以保持清晰。 此组件是一个处理数据呈现的函数组件。 来自文件的 data(地理位置)通过 props
接收,每个地理位置将被传递给 LocationCard
组件。
现在我们需要一个第二个组件,最终也需要搜索功能。 它将与我们刚刚构建的第一个组件非常相似,但它将具有不同的名称并从不同的地方加载数据。
我们希望用户能够使用 input
字段搜索项目。 应用上显示的项目列表应由搜索状态决定。 此功能将在我们正在使用的两个组件之间共享。 由于高阶组件的概念,我们可以创建一个搜索容器组件,并将其包装在其他组件周围。
让我们将组件命名为 withSearch
。 此组件将渲染用于我们搜索的 input
字段,并管理我们的 searchTerm
状态。 searchTerm
将作为道具传递给包装的组件,它将用于过滤提取的数据
import React, { Component } from 'react'
const withSearch = (WrappedComponent) => {
return class extends Component {
state = {
searchTerm: ''
}
handleSearch = event => {
this.setState({ searchTerm: event.target.value })
}
render() {
return (
<div>
<div>
<input onChange={this.handleSearch} value={this.state.searchTerm} type="text" placeholder="Search" />
</div>
<WrappedComponent searchTerm={this.state.searchTerm} />
</div>
)
}
}
}
export default withSearch
searchTerm
被赋予了一个空字符串的状态。 用户在搜索框中输入的值将被获取,并用于设置 searchTerm
的新状态。 接下来,我们将 searchTerm
传递给 WrappedComponent
。 我们将在过滤数据时使用它。
为了使用高阶组件,我们需要对我们的展示组件进行一些更改。
import React, { Component } from 'react'
// Where the data is located
import preload from './locations.json'
// Searches the data
import withSearch from './withSearch
// Manages the data
import LocationCard from './LocationCard'
// Renders the presentation of the data
const Location = (props) => {
const { searchTerm } = props
return (
<div>
<div>
<div>
<h2>Preferred Locations</h2>
</div>
</div>
<div>
{preload.data
// Filter locations by the inputted search term
.filter(location => `${location.name} ${location.zone} ${location.region}`.toUpperCase().indexOf(searchTerm.toUpperCase()) >= 0)
// Loop through the locations
.map(location => <LocationCard key={location.id} {...location} />)}
</div>
</div>
)
}
export default withSearch(Location)
我们在上面做的第一件事是导入高阶组件。 然后,我们添加了一个 filter 方法来根据用户在搜索输入中输入的内容过滤数据。 最后,我们需要用 withSearch
组件将其包装起来。
查看 CodePen 上 Kingsley Silas Chijioke 的笔 hoc Pen (@kinsomicrote)。
结论
高阶组件不必令人恐惧。 在理解了基础知识之后,您可以通过抽象化可以在不同组件之间共享的功能来将该概念投入使用。
更多资源
- 高阶组件 – React 文档
- 深入了解 React 高阶组件 – 由 Fran Guijarro 撰写
- React 中的高阶组件 – 由 Chris Nwamba 撰写
- 更多示例 – CodePen 集合
很棒的文章,@Kingsley
值得一提的是,高阶组件可能会带来一些额外的负担,传递给包装组件的不必要的数据。 请查看有关将 Render Props 作为高阶组件替代方案的讨论。
https://medium.com/tandemly/im-breaking-up-with-higher-order-components-44b0df2db052