在 React 中,组件逻辑复用经历了三个主要阶段:
- HOC(Higher Order Component,高阶组件)
- Render Props(渲染属性)
- Custom Hook(自定义 Hook)
目前 React 官方推荐使用 Custom Hook 进行逻辑复用,但面试中经常会让你比较三者的区别。
一、HOC(Higher Order Component)
定义
高阶组件本质上是一个函数:
javascript
const withLoading = (WrappedComponent) => {
return (props) => {
const [loading] = useState(false);
if (loading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props} />;
};
};
使用:
javascript
const UserList = () => {
return <div>用户列表</div>;
};
export default withLoading(UserList);
原理
scss
Component
↓
withXXX(Component)
↓
NewComponent
本质:
css
A => B
输入一个组件,返回一个增强后的组件。
常见场景
权限校验
javascript
const withAuth = (Component) => {
return (props) => {
const token = localStorage.getItem('token');
if (!token) {
return <div>请登录</div>;
}
return <Component {...props} />;
};
};
日志埋点
javascript
const withLog = (Component) => {
return (props) => {
console.log('页面访问');
return <Component {...props} />;
};
};
优点
逻辑复用
scss
withAuth(User)
withAuth(Order)
withAuth(Product)
对原组件无侵入
arduino
export default withAuth(User);
User 不需要修改代码。
缺点
1. HOC 地狱
arduino
export default withAuth(
withLoading(
withLog(
withTheme(User)
)
)
);
嵌套过深。
2. Props 命名冲突
ini
<Component data={data} />
如果组件本身也有 data 属性:
ini
<User data="xxx" />
容易覆盖。
3. 调试困难
React DevTools:
scss
withAuth(
withTheme(
User
)
)
组件树很难看。
二、Render Props
定义
通过函数作为 props 传递逻辑。
ini
<DataProvider
render={(data) => (
<UserList data={data} />
)}
/>
示例
数据提供组件
scala
class Mouse extends React.Component {
state = {
x: 0,
y: 0
};
componentDidMount() {
window.addEventListener('mousemove', this.handleMove);
}
handleMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
});
};
render() {
return this.props.render(this.state);
}
}
使用:
javascript
<Mouse
render={({ x, y }) => (
<h1>
{x}, {y}
</h1>
)}
/>
children 也是 Render Props
javascript
<Mouse>
{({ x, y }) => (
<h1>{x},{y}</h1>
)}
</Mouse>
组件:
kotlin
return this.props.children(this.state);
优点
灵活
调用方完全控制 UI:
ini
<DataProvider
render={(data) => (
<CustomTable data={data} />
)}
/>
不会产生 HOC 嵌套
xml
<DataProvider />
结构更清晰。
缺点
JSX 嵌套严重
javascript
<DataProvider>
{(data) => (
<ThemeProvider>
{(theme) => (
<User />
)}
</ThemeProvider>
)}
</DataProvider>
俗称:
Callback Hell
性能问题
每次 render:
ini
render={(data)=>{}}
都会创建新的函数。
三、自定义 Hook(Custom Hook)
React 16.8 之后最主流方案。
示例
封装鼠标位置
scss
function useMouse() {
const [position, setPosition] = useState({
x: 0,
y: 0
});
useEffect(() => {
const move = (e) => {
setPosition({
x: e.clientX,
y: e.clientY
});
};
window.addEventListener('mousemove', move);
return () => {
window.removeEventListener('mousemove', move);
};
}, []);
return position;
}
使用
javascript
function App() {
const { x, y } = useMouse();
return (
<h1>
{x} - {y}
</h1>
);
}
多组件共享逻辑
scss
function User() {
const { data } = useUser();
}
function Order() {
const { data } = useUser();
}
优点
1. 代码最简洁
ini
const data = useUser();
没有嵌套。
2. 逻辑聚合
go
const {
data,
loading,
error
} = useFetch();
逻辑放一起。
3. TypeScript 友好
arduino
function useUser(): UserInfo {}
类型推导自然。
4. 不增加组件层级
React 树:
sql
App
└ User
不会出现:
scss
withAuth(User)
或者:
markdown
Provider
└ render
└ User
缺点
只能用于函数组件
scala
class User extends React.Component {}
不能直接使用:
scss
useUser()
必须遵守 Hook 规则
错误:
scss
if (visible) {
useUser();
}
正确:
ini
const user = useUser();
if (visible) {}
四、三者对比
| 对比项 | HOC | Render Props | Custom Hook |
|---|---|---|---|
| 出现时间 | React 早期 | React 16 前后 | React 16.8+ |
| 本质 | 组件包装组件 | 函数作为 props | Hook 抽离逻辑 |
| 增加组件层级 | 是 | 是 | 否 |
| JSX 嵌套 | 少 | 多 | 无 |
| 逻辑复用 | 好 | 好 | 最好 |
| TypeScript 支持 | 一般 | 一般 | 很好 |
| 调试体验 | 差 | 一般 | 好 |
| 性能 | 一般 | 一般 | 好 |
| 推荐程度 | ⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
五、面试回答(标准版)
如果面试官问:
React 中 HOC、Render Props、自定义 Hook 有什么区别?
可以回答:
三者本质上都是为了解决组件间逻辑复用问题。
- HOC 是通过包装组件返回增强组件来复用逻辑,适用于权限控制、埋点统计等场景,但容易出现组件嵌套和 props 冲突问题。
- Render Props 是通过函数作为 props 传递共享逻辑,灵活性较高,但容易造成 JSX 嵌套过深。
- Custom Hook 是 React Hooks 推出后的推荐方案,通过抽离状态逻辑实现复用,不增加组件层级,代码更简洁、类型支持更好,也是当前 React 项目中最常用的方案。
在现代 React 开发中,优先使用 Custom Hook;维护老项目时仍然会遇到 HOC 和 Render Props。