React 设计模式 / HOC 模式
Source: Dev.to
请提供您希望翻译的正文内容,我将为您翻译成简体中文。
高阶组件 (HOC) 概述
高阶组件 (HOC) 是一个函数,它接受一个组件作为参数,添加一些逻辑,并返回一个包含该逻辑的新组件。HOC 让你能够在多个组件之间复用行为,而无需重复代码。
1️⃣ 使用 HOC 添加样式
与其在每个组件中都创建 style 对象,不如创建一个 HOC,将相同的样式注入到传入的任意组件中。
// withStyles.tsx
function withStyles(Component: React.ComponentType) {
return (props: T) => {
const style = { padding: '0.6rem', margin: '4rem' };
return ;
};
}
用法
const Button = () => Click me!;
const Text = () =>
Hello World!
;
const StyledButton = withStyles(Button);
const StyledText = withStyles(Text);
StyledButton 和 StyledText 是原始组件,已通过 withStyles 提供的 style 属性进行增强。
2️⃣ 使用 HOC 添加加载指示器
在获取数据时,我们通常希望在请求完成之前显示一个 Loading…(加载中)屏幕。
与其在众多组件中散布加载逻辑,我们可以将其封装在名为 withLoader 的 HOC 中。
2.1 最简骨架
function withLoader(
Element: React.ComponentType,
url: string
) {
return (props: Omit) => {
// implementation will go here
return ;
};
}
2.2 完整实现
import { useEffect, useState } from 'react';
function withLoader(
Element: React.ComponentType,
url: string
) {
return (props: Omit) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((json) => {
setData(json);
setLoading(false);
})
.catch((err) => {
console.error(err);
setLoading(false);
});
}, [url]);
if (loading) {
return Loading…;
}
// Pass the fetched data as a `data` prop to the wrapped component
return ;
};
}
2.3 将 HOC 应用于组件
// CatImages.tsx
type CatImagesProps = {
data: any; // replace `any` with a proper type if you have one
};
function CatImages({ data }: CatImagesProps) {
return (
{data.map((cat: any) => (
))}
);
}
// Export the wrapped component
export default withLoader(CatImages, 'https://api.thecatapi.com/v1/images/search?limit=10');
现在 CatImages 会自动接收一个 data 属性,在请求进行期间,用户会看到 Loading…(加载中)信息。
3️⃣ 组合多个 HOC
您可以堆叠 HOC 来组合行为。
下面我们创建一个提供 hovering 属性的 withHover HOC,然后将其与 withLoader 组合使用。
3.1 withHover HOC
import { useState, useCallback } from 'react';
function withHover(Component: React.ComponentType) {
return (props: T) => {
const [hovering, setHovering] = useState(false);
const onMouseEnter = useCallback(() => setHovering(true), []);
const onMouseLeave = useCallback(() => setHovering(false), []);
return (
);
};
}
3.2 组合 withHover + withLoader
// WrappedCatImages.tsx
export default withHover(
withLoader(CatImages, 'https://api.thecatapi.com/v1/images/search?limit=10')
);
在 CatImages 中,您现在可以同时使用 data(来自 withLoader)和 hovering(来自 withHover)来渲染条件性的 “Hovering” 框。
4️⃣ 用 Hook(useHover)替代 HOC
Hook 通常可以实现与 HOC 相同的效果,但嵌套更少。
4.1 useHover hook
import { useRef, useState, useEffect, RefObject } from 'react';
function useHover(): [RefObject, boolean] {
const ref = useRef(null);
const [hovering, setHovering] = useState(false);
useEffect(() => {
const node = ref.current;
if (!node) return;
const handleMouseEnter = () => setHovering(true);
const handleMouseLeave = () => setHovering(false);
node.addEventListener('mouseenter', handleMouseEnter);
node.addEventListener('mouseleave', handleMouseLeave);
return () => {
node.removeEventListener('mouseenter', handleMouseEnter);
node.removeEventListener('mouseleave', handleMouseLeave);
};
}, []);
return [ref, hovering];
}
4.2 在 CatImages 中使用该 Hook
// CatImagesWithHook.tsx
import { useLoader } from './useLoader'; // a hook version of the loader logic
import { useHover } from './useHover';
function CatImagesWithHook() {
const [ref, hovering] = useHover();
const { data, loading } = useLoader('https://api.thecatapi.com/v1/images/search?limit=10');
if (loading) return Loading…;
return (
{hovering && Hovering}
{data.map((cat: any) => (
))}
);
}
使用 Hook 的方式消除了 HOC 引入的额外组件层级,使组件树更加扁平。
5️⃣ 何时更倾向使用 HOC 与 Hook
- HOC(高阶组件)非常适合处理 横切关注点,需要兼容 任何 组件类型(类组件或函数组件),并且希望保持被包装组件的 API 不变的场景。
- Hook 通常是处理 有状态逻辑 的首选现代方案,因为它们可以避免因大量嵌套 HOC 而产生的 “包装地狱”。
React Docs: “在大多数情况下,Hook 足以满足需求,并且可以帮助减少树中的嵌套。”
6️⃣ 示例:深度嵌套的 HOC(用于说明)
相同的行为通常可以通过组合 hooks 和 context 提供者更简洁地表达。
TL;DR
- HOCs 让你向组件注入 props/逻辑(例如
withStyles、withLoader、withHover)。 - 你可以 组合 多个 HOC 来构建复杂的行为。
- Hooks(
useHover、useLoader)为大多数情况提供了现代的、层级更少的替代方案。
请随意将上述代码片段复制到你的项目中,并根据需要调整类型!
布局示例
Hook 与 高阶组件 (HOC) 的比较
直接在组件中添加 Hook 意味着我们不再需要将组件包装在 HOC 中。
使用 HOC 可以让相同的逻辑提供给多个组件,同时将该逻辑集中在一个地方。然而,Hook 允许我们在组件内部添加自定义行为,这在多个组件依赖该行为时,可能会增加引入错误的风险,相较于 HOC 模式。
高阶组件 的最佳使用场景
- 组件可以独立运行,无需额外的自定义逻辑。
Hook 的最佳使用场景
- 该行为并未在整个应用中广泛传播;只有一个或少数几个组件使用它。
- 该行为为组件添加了多个属性。