这是历史遗留问题,所以不建议使用(现在有了Hooks),在这里只是让我们了解这段历史
在我们的应用程序中,我们经常希望在多个组件中使用相同的逻辑。此逻辑可以包括将特定样式应用于组件、要求授权或添加全局状态。
能够在多个组件中重用相同逻辑的一种方法是使用高阶组件模式。这种模式允许我们在整个应用程序中重用组件逻辑。
高阶组件 (HOC) 是接收另一个组件的组件。HOC 包含我们想要应用于作为参数传递的组件的某些逻辑。应用该逻辑后,HOC 返回带有附加逻辑的元素。
容器/展示 模式
假设我们一直想为应用程序中的多个组件添加某种样式。不是style每次都在本地创建一个对象,我们可以简单地创建一个 HOC,将style对象添加到我们传递给它的组件中
function withStyles(Component) {
return props => {
const style = { padding: '0.2rem', margin: '1rem' }
return <Component style={style} {...props} />
}
}
const Button = () = <button>Click me!</button>
const Text = () => <p>Hello World!</p>
const StyledButton = withStyles(Button)
const StyedText = withStyles(Text)
我们刚刚创建了一个 StyledButton 和 StyledText 组件,它们是 Button 和 Text 组件的修改版本。它们现在都包含在 withStyles HOC 中添加的样式!
让我们看一下之前在 Container/Presentational
模式中使用的同一个 DogImages 示例!该应用程序仅渲染从 API 获取的狗图像列表。
HOC 模式
让我们稍微改善一下用户体验。 当我们获取数据时,我们希望向用户显示“正在加载…”屏幕。
我们可以使用高阶组件为我们添加此逻辑,而不是直接向 DogImages 组件添加数据。
让我们创建一个名为 withLoader 的 HOC。 HOC 应该接收一个组件,并返回该组件。
在这种情况下,withLoader HOC 应该接收应该显示 Loading… 的元素,直到获取数据。
让我们创建我们想要使用的 withLoader HOC h 的最低版本!
function withLoader(Element) {
return props => <Element />;
}
然而,我们不只是想返回它收到的元素。 相反,我们希望这个元素包含告诉我们数据是否仍在加载的逻辑。
为了使 withLoader HOC 非常可重用,我们不会在该组件中硬编码 Dog API url。
相反,我们可以将 URL 作为参数传递给 withLoader HOC,因此这个加载器可以用于任何需要加载指示器的组件,同时从不同的 API 端点获取数据。
function withLoader(Element, url) {
return props => {};
}
一个 HOC 返回一个元素,一个功能组件 props => {}
想要添加逻辑,允许显示带有 Loading… 的文本,因为数据仍在获取中。
获取数据后,组件应将获取的数据作为 prop 传递。
|
|
|
|
Perfect! 完美的! 我们刚刚创建了一个可以接收任何组件和 url 的 HOC。
-
在 useEffect 钩子中,withLoader HOC 从我们作为 url 值传递的 API 端点获取数据。 虽然数据尚未返回,但我们返回包含 Loading… 文本的元素。
-
获取数据后,我们将 data 设置为等于已获取的数据。 由于数据不再为空,我们可以显示我们传递给 HOC 的元素!
那么,我们如何将这种行为添加到我们的应用程序中,以便它实际上会在 DogImages 列表上显示 Loading… 指示器?
在 DogImages.js 中,我们不再只想导出普通的 DogImages 组件。
相反,我们希望围绕 DogImages 组件导出“包装的” withLoading HOC。
export default withLoading(DogImages);
withLoading HOC 还希望 url 知道从哪个端点获取数据。 在这种情况下,我们要添加 Dog API 端点。
export default withLoader(
DogImages,
"https://dog.ceo/api/breed/labrador/images/random/6"
);
由于 withLoader HOC 返回了带有额外数据道具的元素,在本例中为 DogImages,我们可以访问 DogImages 组件中的数据道具。
|
|
|
|
Perfect! 我们现在在获取数据时看到一个 Loading… 屏幕。
高阶组件模式允许我们为多个组件提供相同的逻辑,同时将所有逻辑保存在一个地方。
withLoader HOC 不关心它接收到的组件或 url:只要它是一个有效的组件和一个有效的 API 端点,它就会简单地将数据从该 API 端点传递给我们传递的组件。
组合
我们还可以组合多个高阶组件。
假设还想添加显示悬停的功能! 当用户将鼠标悬停在 DogImages 列表上时的文本框。
需要创建一个 HOC,为传递的元素提供悬停道具。
基于该道具,可以根据用户是否将鼠标悬停在 DogImages 列表上,有条件地呈现文本框。
现在可以将 withHover HOC 包裹在 withLoader HOC 周围。
|
|
|
|
|
|
DogImages 元素现在包含从 withHover 和 withLoader 传递的所有道具。
现在可以有条件地渲染悬停! 文本框,基于悬停道具的值是真还是假。
一个著名的用于组成 HOC 的库是 recompose
。
由于 HOC 在很大程度上可以被 React Hooks 替代,因此不再维护 recompose 库,因此本文不会涉及
。
优点
使用高阶组件模式允许我们将所有要重用的逻辑放在一个地方。
通过一遍又一遍地复制代码,这降低了在整个应用程序中意外传播错误的风险,每次都可能引入新的错误。
通过将逻辑全部放在一个地方,我们可以保持我们的代码 DRY 并轻松实施关注点分离。
缺点
HOC 可以传递给元素的 prop 的名称可能会导致命名冲突。
function withStyles(Component) {
return props => {
const style = { padding: '0.2rem', margin: '1rem' }
return <Component style={style} {...props} />
}
}
const Button = () = <button style={{ color: 'red' }}>Click me!</button>
const StyledButton = withStyles(Button)
在这种情况下, withStyles HOC 向我们传递给它的元素添加了一个名为 style 的道具。
但是,Button 组件已经有一个名为 style 的 prop,它将被覆盖!
通过重命名道具或合并道具,确保 HOC 可以处理意外的名称冲突。
function withStyles(Component) {
return props => {
const style = {
padding: '0.2rem',
margin: '1rem',
...props.style
}
return <Component style={style} {...props} />
}
}
const Button = () = <button style={{ color: 'red' }}>Click me!</button>
const StyledButton = withStyles(Button)
当使用多个组合的 HOC 将 props 传递给包裹在其中的元素时,可能很难确定哪个 HOC 负责哪个 prop。 这可能会阻碍调试和轻松扩展应用程序。