人人都会AI编程

10.1 插槽系统

更新时间:2026-07-10

什么是高阶组件

高阶组件(Higher-Order Component,HOC)是一种复用组件逻辑的高级技术,本质上是一个函数。它接收一个组件作为参数,返回一个新的增强组件。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

这个模式借鉴了函数式编程中的“高阶函数”思想——函数可以接收函数作为参数,也可以返回函数。高阶组件就是对组件的包装,它在不修改原组件代码的前提下,为组件添加额外的功能或行为。

实现原理

高阶组件的核心机制是Props 代理继承反转。最常用的 Props 代理模式是这样的:

  1. 高阶函数内部创建一个新的组件。
  2. 新组件在渲染时,将原组件作为子组件,并通过 Props 向原组件注入额外的数据或行为。
  3. 新组件可以拦截、修改、添加 Props,或者在渲染原组件前后插入其他 UI。

基本实现示例:

// 一个给组件添加 loading 状态的高阶组件
function withLoading(WrappedComponent) {
  return function EnhancedComponent({ isLoading, ...props }) {
    if (isLoading) {
      return <div>加载中...</div>;
    }
    return <WrappedComponent {...props} />;
  };
}

// 使用
function UserList({ data }) {
  return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}

const UserListWithLoading = withLoading(UserList);

// 在页面中
<UserListWithLoading isLoading={loading} data={users} />

withLoading 不关心 UserList 的具体实现,只负责处理加载状态的显示逻辑。这个加载逻辑可以给任何需要它的组件复用。

更复杂的例子:订阅数据源

function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {
    state = { data: null };

    componentDidMount() {
      // 订阅某个数据源
      const data = DataSource.getData(this.props.id);
      this.setState({ data });
      this.unsubscribe = DataSource.subscribe((newData) => {
        this.setState({ data: selectData(newData) });
      });
    }

    componentWillUnmount() {
      this.unsubscribe();
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

这个 HOC 把“订阅数据源并自动更新”的逻辑抽象出来,任何组件都可以通过它获得实时数据注入,而不用自己处理订阅与清理。

适用场景

1. 横切关注点(Cross-Cutting Concerns)的复用

当多个组件共享相同的非 UI 逻辑,如权限检查、日志记录、数据获取、主题注入等,HOC 可以将这些逻辑从组件中抽离出来。

// 权限检查
function withAuth(WrappedComponent) {
  return function AuthComponent(props) {
    const { isAuthenticated } = useAuth();
    if (!isAuthenticated) {
      return <Navigate to="/login" />;
    }
    return <WrappedComponent {...props} />;
  };
}

// 埋点日志
function withTracking(WrappedComponent) {
  return function TrackingComponent(props) {
    useEffect(() => {
      analytics.send('page_view', { component: WrappedComponent.name });
    }, []);
    return <WrappedComponent {...props} />;
  };
}

2. 条件渲染逻辑封装

将加载、空数据、错误处理等条件渲染模式封装成 HOC,避免在每个组件中重复编写相同的 if-else 逻辑。

function withAsyncState(WrappedComponent) {
  return function AsyncStateComponent({ loading, error, data, ...props }) {
    if (loading) return <Spinner />;
    if (error) return <ErrorDisplay message={error} />;
    return <WrappedComponent data={data} {...props} />;
  };
}

3. 操作 Props 或拦截渲染

HOC 可以修改传入的 Props、添加默认值、或者对 Props 进行格式转换。例如 React Router 的 withRouter 曾经用于给非路由组件注入 historymatchlocation 等路由信息。

function withDefaultProps(defaultProps) {
  return function(WrappedComponent) {
    return function(props) {
      return <WrappedComponent {...defaultProps} {...props} />;
    };
  };
}

HOC 的缺陷与现代替代方案

尽管 HOC 在类组件时代是核心的复用模式,但它存在一些明显的不足:

1. 嵌套地狱(Wrapper Hell)

多个 HOC 嵌套使用会导致组件树层级过深,调试困难,React DevTools 中会看到层层包裹的匿名组件。

// 多层 HOC 嵌套,可读性差
export default withAuth(withTracking(withLoading(withQuery(UserProfile))));

2. Props 命名冲突

HOC 可能向原组件注入 Props,如果注入的 Props 名称与原组件已有的 Props 或来自其他 HOC 的 Props 重名,就会发生覆盖,且不易排查。

3. 静态方法丢失

高阶函数返回的是一个新的组件,原组件上的静态方法(如 Component.displayName、自定义静态方法)不会自动拷贝。需要手动处理 hoist-non-react-statics 工具。

4. Refs 无法穿透

默认情况下,ref 只会挂载到 HOC 外层容器上,而不会传递到被包裹的组件内部。React 16.3 引入了 React.forwardRef 来解决这个问题,但增加了额外的心智成本。

5. 对 TypeScript 不够友好

HOC 的类型推导相对复杂,往往需要编写额外的泛型和类型声明,在动态 Props 增删的情况下更是繁琐。

正因为这些痛点,现代 React 开发中,自定义 Hooks 已经取代 HOC 成为首选的逻辑复用方式。同样的权限检查、数据订阅、日志记录等功能,用自定义 Hooks 实现更简洁、无嵌套、无 Props 冲突,且类型安全。

HOC 与自定义 Hooks 对比:

// 用 HOC 复用数据获取
function withUserData(WrappedComponent) {
  return function(props) {
    const { data } = useQuery('userData');
    return <WrappedComponent user={data} {...props} />;
  };
}

// 用自定义 Hook 复用,直接在组件内调用
function useUserData() {
  return useQuery('userData').data;
}

function UserProfile() {
  const user = useUserData();
  return <div>{user?.name}</div>;
}

Hooks 的方式不需要创建额外的组件包装层,不会污染 Props,逻辑更直观。不过,HOC 在需要渲染劫持操作组件实例的场景(如 React.forwardRef 的配合)仍有其用武之地。

总结

  • 原理:函数接收组件,返回新组件,通过 Props 注入或渲染拦截来增强能力。
  • 适用:横切逻辑复用、条件渲染封装、Props 操作。
  • 缺陷:嵌套地狱、Props 冲突、静态方法丢失、Ref 穿透问题、TS 类型复杂。
  • 现代选择:优先使用自定义 Hooks 实现逻辑复用,HOC 作为特定场景下的备选方案。

在现有的 React 生态中,HOC 仍广泛存在于第三方库(如 Redux 的 connect 已逐渐被 Hooks 取代)和老项目中。理解 HOC 不仅能让你读懂这些代码,也能更深刻地体会 React 复用模式的演进脉络。