高阶组件 #
一、高阶组件概述 #
1.1 什么是高阶组件 #
高阶组件(Higher-Order Component,HOC)是一个函数,接收一个组件并返回一个新组件。
javascript
const EnhancedComponent = higherOrderComponent(WrappedComponent);
1.2 类比理解 #
text
高阶函数:接收函数,返回函数
高阶组件:接收组件,返回组件
1.3 使用场景 #
| 场景 | 说明 |
|---|---|
| 代码复用 | 多个组件共享逻辑 |
| 权限控制 | 检查用户权限 |
| 数据获取 | 封装数据请求逻辑 |
| 日志记录 | 记录组件行为 |
| 性能监控 | 测量渲染性能 |
二、基本实现 #
2.1 基本结构 #
javascript
function withEnhancement(WrappedComponent) {
return function EnhancedComponent(props) {
return <WrappedComponent {...props} />;
};
}
// 使用箭头函数
const withEnhancement = (WrappedComponent) => {
return (props) => <WrappedComponent {...props} />;
};
2.2 添加新功能 #
javascript
function withLogger(WrappedComponent) {
return function WithLogger(props) {
useEffect(() => {
console.log(`${WrappedComponent.name} mounted`);
return () => {
console.log(`${WrappedComponent.name} unmounted`);
};
}, []);
return <WrappedComponent {...props} />;
};
}
// 使用
const EnhancedButton = withLogger(Button);
2.3 操作Props #
javascript
function withDefaultProps(defaultProps) {
return function (WrappedComponent) {
return function EnhancedComponent(props) {
return <WrappedComponent {...defaultProps} {...props} />;
};
};
}
// 使用
const ButtonWithDefaults = withDefaultProps({
type: 'button',
className: 'btn'
})(Button);
三、常见HOC模式 #
3.1 属性代理 #
javascript
function withLoading(WrappedComponent) {
return function WithLoading({ isLoading, ...props }) {
if (isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
}
// 使用
const UserListWithLoading = withLoading(UserList);
function App() {
const [loading, setLoading] = useState(true);
const [users, setUsers] = useState([]);
return <UserListWithLoading isLoading={loading} users={users} />;
}
3.2 反向继承 #
javascript
function withLogging(WrappedComponent) {
return class extends WrappedComponent {
componentDidMount() {
console.log('Component mounted');
super.componentDidMount?.();
}
render() {
console.log('Component rendering');
return super.render();
}
};
}
3.3 条件渲染 #
javascript
function withAuth(WrappedComponent) {
return function WithAuth(props) {
const { user } = useAuth();
if (!user) {
return <LoginPage />;
}
return <WrappedComponent {...props} user={user} />;
};
}
// 使用
const ProtectedDashboard = withAuth(Dashboard);
3.4 数据获取 #
javascript
function withFetch(url) {
return function (WrappedComponent) {
return function WithFetch(props) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [url]);
return (
<WrappedComponent
{...props}
data={data}
loading={loading}
error={error}
/>
);
};
};
}
// 使用
const UserListWithData = withFetch('/api/users')(UserList);
四、组合HOC #
4.1 嵌套组合 #
javascript
const EnhancedComponent = withAuth(
withLoading(
withLogger(Component)
)
);
4.2 compose函数 #
javascript
function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
// 使用
const enhance = compose(
withAuth,
withLoading,
withLogger
);
const EnhancedComponent = enhance(Component);
4.3 使用库 #
javascript
import { compose } from 'redux';
// 或
import { flowRight } from 'lodash';
const enhance = compose(
withAuth,
withLoading,
withLogger
);
五、转发Ref #
5.1 问题 #
javascript
function withEnhancement(WrappedComponent) {
return function EnhancedComponent(props) {
return <WrappedComponent {...props} />;
};
}
// ❌ ref会指向EnhancedComponent,而不是WrappedComponent
const EnhancedButton = withEnhancement(Button);
5.2 解决方案 #
javascript
function withEnhancement(WrappedComponent) {
const EnhancedComponent = forwardRef((props, ref) => {
return <WrappedComponent {...props} ref={ref} />;
});
EnhancedComponent.displayName = `withEnhancement(${WrappedComponent.displayName || WrappedComponent.name})`;
return EnhancedComponent;
}
六、最佳实践 #
6.1 设置displayName #
javascript
function withHOC(WrappedComponent) {
function WithHOC(props) {
return <WrappedComponent {...props} />;
}
WithHOC.displayName = `withHOC(${WrappedComponent.displayName || WrappedComponent.name})`;
return WithHOC;
}
6.2 传递不相关的Props #
javascript
function withHOC(WrappedComponent) {
return function WithHOC({ specialProp, ...passThroughProps }) {
// 处理specialProp
const enhancedProps = {
...passThroughProps,
// 添加新的props
};
return <WrappedComponent {...enhancedProps} />;
};
}
6.3 复制静态方法 #
javascript
import hoistNonReactStatics from 'hoist-non-react-statics';
function withHOC(WrappedComponent) {
function WithHOC(props) {
return <WrappedComponent {...props} />;
}
hoistNonReactStatics(WithHOC, WrappedComponent);
return WithHOC;
}
6.4 不要在渲染中使用HOC #
javascript
// ❌ 错误:每次渲染都创建新组件
function Component() {
const EnhancedComponent = withHOC(Button);
return <EnhancedComponent />;
}
// ✅ 正确:在组件外部创建
const EnhancedButton = withHOC(Button);
function Component() {
return <EnhancedButton />;
}
6.5 复制方法到类组件 #
javascript
function withHOC(WrappedComponent) {
class WithHOC extends React.Component {
static displayName = `withHOC(${WrappedComponent.displayName || WrappedComponent.name})`;
static WrappedComponent = WrappedComponent;
render() {
return <WrappedComponent {...this.props} />;
}
}
return hoistNonReactStatics(WithHOC, WrappedComponent);
}
七、HOC vs Hooks #
7.1 对比 #
| 特性 | HOC | Hooks |
|---|---|---|
| 代码量 | 较多 | 较少 |
| 嵌套问题 | 存在 | 不存在 |
| 可读性 | 较差 | 较好 |
| TypeScript | 复杂 | 简单 |
| 推荐程度 | 旧代码维护 | 新项目推荐 |
7.2 HOC改写为Hooks #
javascript
// HOC方式
function withWindowSize(WrappedComponent) {
return function WithWindowSize(props) {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return <WrappedComponent {...props} windowSize={size} />;
};
}
// Hooks方式
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 使用
function Component() {
const windowSize = useWindowSize();
return <div>{windowSize.width} x {windowSize.height}</div>;
}
八、实际案例 #
8.1 权限控制HOC #
javascript
function withPermission(permission) {
return function (WrappedComponent) {
return function WithPermission(props) {
const { user } = useAuth();
if (!user?.permissions?.includes(permission)) {
return <AccessDenied />;
}
return <WrappedComponent {...props} />;
};
};
}
// 使用
const AdminPanel = withPermission('admin')(Panel);
8.2 性能监控HOC #
javascript
function withPerformance(WrappedComponent) {
return function WithPerformance(props) {
useEffect(() => {
const startTime = performance.now();
return () => {
const endTime = performance.now();
console.log(`${WrappedComponent.name} rendered in ${endTime - startTime}ms`);
};
});
return <WrappedComponent {...props} />;
};
}
8.3 错误边界HOC #
javascript
function withErrorBoundary(WrappedComponent) {
return class WithErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} />;
}
return <WrappedComponent {...this.props} />;
}
};
}
九、总结 #
| 要点 | 说明 |
|---|---|
| 定义 | 接收组件返回组件的函数 |
| 模式 | 属性代理、反向继承 |
| 组合 | 使用compose函数 |
| 注意 | 设置displayName、转发ref |
核心原则:
- HOC 用于复用组件逻辑
- 不要在渲染中创建 HOC
- 设置 displayName 便于调试
- 新项目优先使用 Hooks
最后更新:2026-03-26