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

0 阅读3分钟

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

  1. HOC(Higher Order Component,高阶组件)
  2. Render Props(渲染属性)
  3. Custom Hook(自定义 Hook)

目前 React 官方推荐使用 Custom Hook 进行逻辑复用,但面试中经常会让你比较三者的区别。


一、HOC(Higher Order Component)

定义

高阶组件本质上是一个函数:

const withLoading = (WrappedComponent) => {
  return (props) => {
    const [loading] = useState(false);

    if (loading) {
      return <div>Loading...</div>;
    }

    return <WrappedComponent {...props} />;
  };
};

使用:

const UserList = () => {
  return <div>用户列表</div>;
};

export default withLoading(UserList);

原理

Component
    ↓
withXXX(Component)
    ↓
NewComponent

本质:

A => B

输入一个组件,返回一个增强后的组件。


常见场景

权限校验

const withAuth = (Component) => {
  return (props) => {
    const token = localStorage.getItem('token');

    if (!token) {
      return <div>请登录</div>;
    }

    return <Component {...props} />;
  };
};

日志埋点

const withLog = (Component) => {
  return (props) => {
    console.log('页面访问');

    return <Component {...props} />;
  };
};

优点

逻辑复用

withAuth(User)
withAuth(Order)
withAuth(Product)

对原组件无侵入

export default withAuth(User);

User 不需要修改代码。


缺点

1. HOC 地狱

export default withAuth(
  withLoading(
    withLog(
      withTheme(User)
    )
  )
);

嵌套过深。


2. Props 命名冲突

<Component data={data} />

如果组件本身也有 data 属性:

<User data="xxx" />

容易覆盖。


3. 调试困难

React DevTools:

withAuth(
  withTheme(
    User
  )
)

组件树很难看。


二、Render Props

定义

通过函数作为 props 传递逻辑。

<DataProvider
  render={(data) => (
    <UserList data={data} />
  )}
/>

示例

数据提供组件

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);
  }
}

使用:

<Mouse
  render={({ x, y }) => (
    <h1>
      {x}, {y}
    </h1>
  )}
/>

children 也是 Render Props

<Mouse>
  {({ x, y }) => (
    <h1>{x},{y}</h1>
  )}
</Mouse>

组件:

return this.props.children(this.state);

优点

灵活

调用方完全控制 UI:

<DataProvider
  render={(data) => (
    <CustomTable data={data} />
  )}
/>

不会产生 HOC 嵌套

<DataProvider />

结构更清晰。


缺点

JSX 嵌套严重

<DataProvider>
  {(data) => (
    <ThemeProvider>
      {(theme) => (
        <User />
      )}
    </ThemeProvider>
  )}
</DataProvider>

俗称:

Callback Hell

性能问题

每次 render:

render={(data)=>{}}

都会创建新的函数。


三、自定义 Hook(Custom Hook)

React 16.8 之后最主流方案。


示例

封装鼠标位置

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;
}

使用

function App() {
  const { x, y } = useMouse();

  return (
    <h1>
      {x} - {y}
    </h1>
  );
}

多组件共享逻辑

function User() {
  const { data } = useUser();
}

function Order() {
  const { data } = useUser();
}

优点

1. 代码最简洁

const data = useUser();

没有嵌套。


2. 逻辑聚合

const {
  data,
  loading,
  error
} = useFetch();

逻辑放一起。


3. TypeScript 友好

function useUser(): UserInfo {}

类型推导自然。


4. 不增加组件层级

React 树:

App
 └ User

不会出现:

withAuth(User)

或者:

Provider
 └ render
    └ User

缺点

只能用于函数组件

class User extends React.Component {}

不能直接使用:

useUser()

必须遵守 Hook 规则

错误:

if (visible) {
  useUser();
}

正确:

const user = useUser();

if (visible) {}

四、三者对比

对比项HOCRender PropsCustom Hook
出现时间React 早期React 16 前后React 16.8+
本质组件包装组件函数作为 propsHook 抽离逻辑
增加组件层级
JSX 嵌套
逻辑复用最好
TypeScript 支持一般一般很好
调试体验一般
性能一般一般
推荐程度⭐⭐⭐⭐⭐⭐⭐⭐⭐

五、面试回答(标准版)

如果面试官问:

React 中 HOC、Render Props、自定义 Hook 有什么区别?

可以回答:

三者本质上都是为了解决组件间逻辑复用问题。

  • HOC 是通过包装组件返回增强组件来复用逻辑,适用于权限控制、埋点统计等场景,但容易出现组件嵌套和 props 冲突问题。
  • Render Props 是通过函数作为 props 传递共享逻辑,灵活性较高,但容易造成 JSX 嵌套过深。
  • Custom Hook 是 React Hooks 推出后的推荐方案,通过抽离状态逻辑实现复用,不增加组件层级,代码更简洁、类型支持更好,也是当前 React 项目中最常用的方案。

在现代 React 开发中,优先使用 Custom Hook;维护老项目时仍然会遇到 HOC 和 Render Props。