高阶组件(Higher-Order Component,简称 HOC)是 React 中用于复用组件逻辑的高级技术。HOC 本身不是 React API 的一部分,它是一种基于 React 组合特性而形成的设计模式。
具体而言,高阶组件是一个函数,它接收一个组件作为参数,并返回一个新的增强组件。HOC 不会修改输入组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组合新组件。
// 高阶组件的基本结构
function withSomething(WrappedComponent) {
// 返回一个新的组件
return function(props) {
// 可以添加新的逻辑、状态或属性
const newProps = { ...props, somethingNew: 'value' };
// 渲染被包装的组件,传入新的 props
return <WrappedComponent {...newProps} />;
};
}高阶组件通常用于以下场景:
// 加载状态高阶组件
function withLoading(WrappedComponent) {
return function WithLoading({ isLoading, ...props }) {
if (isLoading) {
return <div>加载中...</div>;
}
return <WrappedComponent {...props} />;
};
}
// 使用高阶组件
const UserListWithLoading = withLoading(UserList);
// 在父组件中使用
function App() {
const [isLoading, setIsLoading] = useState(true);
const [users, setUsers] = useState([]);
useEffect(() => {
fetchUsers().then(data => {
setUsers(data);
setIsLoading(false);
});
}, []);
return <UserListWithLoading isLoading={isLoading} users={users} />;
}下面我们将实现一个 withLogging 高阶组件,它会在组件挂载、更新和卸载时记录日志。
import React, { useEffect } from 'react';
// 创建一个记录组件生命周期的高阶组件
function withLogging(WrappedComponent) {
// 返回一个新组件
return function WithLogging(props) {
const componentName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
useEffect(() => {
// 组件挂载时记录
console.log(`[${componentName}] 已挂载`);
// 组件卸载时记录
return () => {
console.log(`[${componentName}] 将卸载`);
};
}, []);
// 组件渲染时记录
console.log(`[${componentName}] 正在渲染`, props);
// 渲染被包装的组件,传入所有 props
return <WrappedComponent {...props} />;
};
}
// 一个简单的组件
function Button({ label, onClick }) {
return <button onClick={onClick}>{label}</button>;
}
// 使用高阶组件增强 Button
const ButtonWithLogging = withLogging(Button);
// 在父组件中使用
function App() {
return (
<div>
<ButtonWithLogging
label="点击我"
onClick={() => console.log('按钮被点击')}
/>
</div>
);
}在上面的例子中,withLogging 高阶组件为被包装的组件添加了日志记录功能,而不需要修改原始组件的代码。这展示了高阶组件如何实现关注点分离和代码复用。
// 解决静态方法复制问题
function withLogging(WrappedComponent) {
function WithLogging(props) {
// ...实现...
}
// 复制静态方法
const staticMethods = Object.getOwnPropertyNames(WrappedComponent)
.filter(name => typeof WrappedComponent[name] === 'function' && name !== 'render')
.reduce((methods, name) => {
methods[name] = WrappedComponent[name];
return methods;
}, {});
Object.assign(WithLogging, staticMethods);
// 设置显示名称以便调试
WithLogging.displayName = `WithLogging(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
return WithLogging;
}
// 解决 Refs 不会传递的问题
function withLogging(WrappedComponent) {
function WithLogging(props, ref) {
// ...实现...
return <WrappedComponent {...props} ref={ref} />;
}
// 使用 forwardRef 传递 refs
return React.forwardRef(WithLogging);
}随着 React Hooks 的引入,一些传统上使用高阶组件解决的问题现在可以用 Hooks 更简洁地解决。下面比较两种方法:
// 使用高阶组件
const EnhancedComponent = withSomething(MyComponent);
// 使用
<EnhancedComponent />// 使用 Hooks
function MyComponent() {
const something = useSomething();
return <div>{something}</div>;
}在现代 React 开发中,Hooks 通常是首选的解决方案,因为它们更简洁、更灵活。但在某些复杂场景下,高阶组件仍然有其价值,特别是在处理横切关注点(如权限控制、数据预取等)时。
最佳实践是:优先考虑使用 Hooks,当 Hooks 不足以解决问题时,再考虑使用高阶组件。