You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript如何实现支持重载的高阶组件(HOC)

重载式高阶组件(HOC)实现方案

核心实现思路

要实现同时支持两种调用方式的类型安全HOC,核心是两个部分:

  • 编写独立的TS重载签名,分别对应两种调用场景,将泛型定义在重载签名上以支持自动类型推导
  • 运行时通过判断二次调用的入参类型区分调用路径,不需要对首个入参做复杂校验,配合TS编译时检查即可保证类型安全

完整实现代码

import { ComponentType, JSX, createElement } from 'react';

// Fallback组件的props类型定义
interface FallbackProps {
    error: Error;
    resetErrorBoundary: (...args: Array<unknown>) => void;
}

// 内置默认错误兜底组件,单参数调用时默认使用
const DefaultFallback: ComponentType<FallbackProps> = ({ error }) => {
    return createElement('div', { role: 'alert' }, `加载失败:${error.message}`);
}

/**
 * 重载签名1:传入自定义错误兜底组件,返回柯里化的包装函数
 * 对应调用方式:hoc(CustomFallback)(WrappedComponent)
 */
function withErrorBoundary<P extends object>(
    fallback: ComponentType<FallbackProps>
): (WrappedComponent: ComponentType<P>) => (props: P) => JSX.Element;

/**
 * 重载签名2:直接传入被包裹的业务组件,返回包装后的最终组件
 * 对应调用方式:hoc(WrappedComponent)
 */
function withErrorBoundary<P extends object>(
    WrappedComponent: ComponentType<P>
): (props: P) => JSX.Element;

// 底层实现(该签名对外不可见)
function withErrorBoundary(firstArg: ComponentType<any>) {
    // 通用组件包装逻辑,可替换为实际的错误边界实现
    const wrapComponent = (
        WrappedComponent: ComponentType<any>,
        Fallback: ComponentType<FallbackProps>
    ) => {
        const Wrapped = (props: any) => {
            // 示例为简化错误捕获逻辑,生产环境建议直接复用react-error-boundary的ErrorBoundary组件
            try {
                return createElement(WrappedComponent, props);
            } catch (err) {
                return createElement(Fallback, {
                    error: err as Error,
                    resetErrorBoundary: () => {}
                });
            }
        };
        Wrapped.displayName = `withErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
        return Wrapped;
    };

    // 返回兼容两种场景的函数
    return function hybridFn(secondArg: any) {
        // 判断入参是否为React组件(兼容函数组件、类组件、memo/forwardRef包装组件)
        const isComponent = typeof secondArg === 'function' 
            || (typeof secondArg === 'object' && secondArg !== null && '$$typeof' in secondArg);
        
        if (isComponent) {
            // 柯里化调用路径:第一个参数是fallback,第二个参数是被包裹组件
            return wrapComponent(secondArg, firstArg);
        }
        // 直接调用路径:第一个参数是被包裹组件,当前参数是组件props,直接渲染
        return createElement(wrapComponent(firstArg, DefaultFallback), secondArg);
    };
}

类型校验效果

两种调用方式都可以获得完整的类型提示:

  • 直接传入业务组件时,TS自动匹配第二个重载,返回的组件props类型和原业务组件完全一致
  • 传入自定义fallback再柯里化传入业务组件时,TS自动匹配第一个重载,最终返回的组件props类型同样和业务组件保持一致,fallback组件的props会被校验为FallbackProps类型,传错会直接报类型错误

注意事项

  • 不要把泛型参数定义在最外层的HOC类型上,必须放在每个重载签名上,否则TS无法根据传入的组件自动推导props类型
  • 实现签名的类型需要兼容所有重载的入参、返回值类型,该签名不会暴露给调用方
  • 示例中的错误捕获逻辑是简化版本,生产使用建议直接引入成熟的错误边界库做内部包裹,不需要自行实现错误捕获逻辑

内容的提问来源于stack exchange,提问作者Mahmood Bagheri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 01:54:28