组件复用:HOC、Render Props、自定义Hook 对比

在 React 中,组件逻辑复用经历了三个主要阶段:

  1. HOC(Higher Order Component,高阶组件)
  2. Render Props(渲染属性)
  3. 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。

相关推荐
+VX:Fegn089532 分钟前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·课程设计
Nicander2 小时前
我给 Excalidraw 做了一个本地工作区:DrawSpace
前端·开源
linux_cfan4 小时前
17 · 引擎适配器全景:HLS/DASH/Vimeo/Mux/Cast
前端·javascript·音视频
计算机魔术师4 小时前
烧掉2780亿美元还不够?OpenAI的资本豪赌让人头皮发麻
前端
IT_陈寒5 小时前
Java线程池用错参数,我的服务居然悄悄崩溃了
前端·人工智能·后端
神秘的猪头5 小时前
React 脱围机制(二):重新理解 useEffect——它不是生命周期,而是同步外部系统
react.js
卡布鲁5 小时前
React useMemo 与 useCallback 完全指南:原理、场景与避坑
前端·react.js
碳基修炼5 小时前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http
步行cgn5 小时前
Spring p 命名空间注入详解
java·前端·spring
何何____6 小时前
Vue 生命周期详解
前端·javascript