高阶组件(HOC)与TypeScript:类型安全的组件复用模式

高阶组件(HOC)与TypeScript:类型安全的组件复用模式

【免费下载链接】react Cheatsheets for experienced React developers getting started with TypeScript 【免费下载链接】react 项目地址: https://gitcode.com/gh_mirrors/rea/react

本文深入探讨了在TypeScript环境下使用高阶组件(HOC)实现类型安全的组件复用模式。文章从HOC的基础模式与类型定义开始,详细介绍了属性注入、类型排除与传播技术,复合HOC的类型组合策略,并提供了实际项目中的最佳实践案例。通过丰富的代码示例和类型定义,展示了如何创建类型安全的HOC,确保在开发过程中获得完整的类型检查和智能提示支持。

HOC基础模式与类型定义

高阶组件(Higher-Order Component,HOC)是React中用于组件逻辑复用的高级技术,它本质上是一个函数,接收一个组件并返回一个新的组件。在TypeScript环境下,正确的类型定义对于保证HOC的类型安全和开发体验至关重要。

基础HOC模式

一个典型的HOC遵循以下基本模式:

import React from 'react';

// 基础HOC函数签名
function withHOC<P extends object>(
  WrappedComponent: React.ComponentType<P>
): React.ComponentType<P> {
  // 返回新的组件
  return function EnhancedComponent(props: P) {
    // 可以在这里添加额外的逻辑
    return <WrappedComponent {...props} />;
  };
}

类型安全的HOC定义

为了创建类型安全的HOC,我们需要正确处理泛型类型参数。以下是一个完整的类型安全HOC示例:

type PropsAreEqual<P> = (
  prevProps: Readonly<P>,
  nextProps: Readonly<P>
) => boolean;

const withSampleHoc = <P extends {}>(
  component: {
    (props: P): Exclude<React.ReactNode, undefined>;
    displayName?: string;
  },
  propsAreEqual?: PropsAreEqual<P> | false,
  componentName = component.displayName ?? component.name
): {
  (props: P): React.JSX.Element;
  displayName: string;
} => {
  function WithSampleHoc(props: P) {
    // 在这里添加HOC特有的逻辑
    return component(props) as React.JSX.Element;
  }

  WithSampleHoc.displayName = `withSampleHoc(${componentName})`;

  let wrappedComponent = propsAreEqual === false 
    ? WithSampleHoc 
    : React.memo(WithSampleHoc, propsAreEqual);

  return wrappedComponent as typeof WithSampleHoc;
};

属性注入模式

HOC最常见的用途之一是属性注入。以下示例展示了如何创建一个注入主题属性的HOC:

interface WithThemeProps {
  primaryColor: string;
  secondaryColor: string;
}

function withTheme<T extends WithThemeProps>(
  WrappedComponent: React.ComponentType<T>
) {
  const displayName = WrappedComponent.displayName || WrappedComponent.name || "Component";

  const ComponentWithTheme = (props: Omit<T, keyof WithThemeProps>) => {
    const themeProps = useTheme(); // 假设的hook,返回主题属性
    
    return <WrappedComponent {...themeProps} {...(props as T)} />;
  };

  ComponentWithTheme.displayName = `withTheme(${displayName})`;
  return ComponentWithTheme;
}

类型排除与属性映射

在处理属性注入时,TypeScript需要明确知道哪些属性应该被排除。以下是属性排除的通用模式:

// 通用属性排除工具类型
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

// 属性注入HOC
function withInjectedProps<U extends Record<string, unknown>>(
  injectedProps: U
) {
  return function <T extends U>(Component: React.ComponentType<T>) {
    return function (props: Omit<T, keyof U>): React.JSX.Element {
      const newProps = { ...props, ...injectedProps } as T;
      return <Component {...newProps} />;
    };
  };
}

组件静态属性处理

HOC还需要正确处理被包装组件的静态属性:

function copyStaticProperties(source: any, target: any) {
  Object.keys(source).forEach(key => {
    if (!target.hasOwnProperty(key)) {
      Object.defineProperty(
        target,
        key,
        Object.getOwnPropertyDescriptor(source, key)!
      );
    }
  });
}

// 在HOC中使用
const wrappedComponent = /* ... */;
copyStaticProperties(component, wrappedComponent);

类型定义最佳实践表格

模式类型类型定义使用场景注意事项
基础HOC<P>(Component: ComponentType<P>) => ComponentType<P>简单逻辑复用保持属性类型不变
属性注入<T extends U>(Component: ComponentType<T>) => ComponentType<Omit<T, keyof U>>注入额外属性需要类型断言
条件包装<P>(Component: ComponentType<P>, condition: boolean) => ComponentType<P>条件性包装注意性能影响
组合HOC组合多个HOC类型复杂逻辑复用类型推导可能复杂

类型安全验证流程图

mermaid

通过以上模式,我们可以创建类型安全的高阶组件,确保在开发过程中能够获得完整的类型检查和智能提示支持。正确的类型定义不仅提高了代码的可靠性,也大大改善了开发体验。

属性排除与类型传播技术

在React高阶组件(HOC)的开发中,属性排除与类型传播是确保类型安全的关键技术。当HOC需要向包装组件注入特定属性时,必须从外部接口中排除这些已注入的属性,以避免重复定义和类型冲突。

核心类型工具解析

TypeScript提供了几个强大的工具类型来处理属性排除:

// 基础工具类型定义
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type Exclude<T, U> = T extends U ? never : T;

这些工具类型协同工作,实现了精确的属性操作:

mermaid

实际应用场景

场景1:属性注入HOC

考虑一个向组件注入用户信息的HOC:

interface UserInfo {
  userId: string;
  userName: string;
}

function withUserInfo<P extends UserInfo>(userInfo: UserInfo) {
  return function(Component: React.ComponentType<P>) {
    return function(props: Omit<P, keyof UserInfo>): React.JSX.Element {
      const mergedProps = { ...props, ...userInfo } as P;
      return <Component {...mergedProps} />;
    };
  };
}
场景2:多属性排除

当需要排除多个属性时:

type AuthProps = {
  token: string;
  userId: string;
  permissions: string[];
};

function withAuth<P extends AuthProps>(auth: AuthProps) {
  return function(Component: React.ComponentType<P>) {
    return function(props: Omit<P, keyof AuthProps>): React.JSX.Element {
      const mergedProps = { ...props, ...auth } as P;
      return <Component {...mergedProps} />;
    };
  };
}

类型传播的最佳实践

避免类型断言的安全方法

虽然类型断言(as P)在某些情况下是必要的,但我们可以通过更安全的方式处理:

function withOwner<P extends { owner: string }>(owner: string) {
  return function(Component: React.ComponentType<P>) {
    return function(props: Omit<P, 'owner'> & { owner?: never }): React.JSX.Element {
      const newProps = { ...props, owner };
      return <Component {...newProps} />;
    };
  };
}

这种方法通过添加 { owner?: never } 确保外部无法再次传入已注入的属性。

通用注入解决方案

创建可重用的通用注入HOC:

function withInjectedProps<InjectedProps extends Record<string, unknown>>(
  injectedProps: InjectedProps
) {
  return function<P extends InjectedProps>(Component: React.ComponentType<P>) {
    return function(props: Omit<P, keyof InjectedProps>): React.JSX.Element {
      const newProps = { ...props, ...injectedProps } as P;
      return <Component {...newProps} />;
    };
  };
}

// 使用示例
const withConfig = withInjectedProps({
  apiUrl: 'https://api.example.com',
  timeout: 5000
});

复杂类型操作技巧

条件属性排除

根据条件动态排除属性:

type ConditionalOmit<T, K extends keyof T, Condition> = 
  Condition extends true ? Omit<T, K> : T;

function withConditionalProps<P, K extends keyof P>(
  condition: boolean,
  propsToOmit: K[]
) {
  return function(Component: React.ComponentType<P>) {
    return function(props: ConditionalOmit<P, K, typeof condition>): React.JSX.Element {
      return <Component {...props as P} />;
    };
  };
}
类型安全的属性合并

确保属性合并的类型安全:

function safeMergeProps<Base, Additional>(
  base: Base,
  additional: Additional
): Base & Additional {
  return { ...base, ...additional };
}

// 在HOC中使用
const mergedProps = safeMergeProps(
  props as Omit<P, 'injectedProp'>,
  { injectedProp: 'value' }
);

常见问题与解决方案

问题1:类型收缩不足

TypeScript有时无法正确推断合并后的类型:

// 解决方案:使用明确的类型守卫
function isCompleteProps<P extends { owner: string }>(
  props: Omit<P, 'owner'> & { owner?: string },
  injectedOwner: string
): props is P {
  return props.owner === undefined;
}
问题2:嵌套HOC的类型传播

处理多层HOC时的类型传播:

function composeHOCs<HOC1 extends Function, HOC2 extends Function>(
  hoc1: HOC1,
  hoc2: HOC2
): Function {
  return (Component: React.ComponentType<any>) => hoc1(hoc2(Component));
}

// 类型安全的组合
type ComposedProps<P, Injected1, Injected2> = 
  Omit<Omit<P, keyof Injected1>, keyof Injected2>;

性能优化考虑

记忆化类型操作

对于频繁使用的类型操作,可以使用类型别名进行记忆化:

// 预定义常用排除类型
type WithoutAuth<P> = Omit<P, keyof AuthProps>;
type WithoutUser<P> = Omit<P, keyof UserInfo>;

// 在HOC工厂中使用
function createAuthHOC() {
  return function<P extends AuthProps>(Component: React.ComponentType<P>) {
    return function(props: WithoutAuth<P>): React.JSX.Element {
      // 实现逻辑
    };
  };
}
编译时类型检查

利用TypeScript的编译时检查确保类型正确性:

// 验证排除操作的正确性
type ValidateOmit<T, K extends keyof T> = 
  keyof Omit<T, K> extends Exclude<keyof T, K> ? true : false;

// 使用示例
type TestValidation = ValidateOmit<{ a: string; b: number }, 'a'>;
// ^? type TestValidation = true

实际代码示例

完整的类型安全HOC实现
import React from 'react';

// 定义注入属性类型
interface ThemeProps {
  theme: 'light' | 'dark';
  primaryColor: string;
}

// 类型安全的HOC工厂函数
export function withTheme<P extends ThemeProps>(themeConfig: ThemeProps) {
  return function(Component: React.ComponentType<P>): React.ComponentType<Omit<P, keyof ThemeProps>> {
    
    const ThemedComponent: React.FC<Omit<P, keyof ThemeProps>> = (props) => {
      const mergedProps = { ...props, ...themeConfig } as P;
      
      return (
        <div className={`theme-${themeConfig.theme}`}>
          <Component {...mergedProps} />
        </div>
      );
    };

    // 设置显示名称用于调试
    ThemedComponent.displayName = `withTheme(${Component.displayName || Component.name})`;

    return ThemedComponent;
  };
}

// 使用示例
interface ButtonProps extends ThemeProps {
  label: string;
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({ label, onClick, theme, primaryColor }) => (
  <button 
    onClick={onClick}
    style={{ backgroundColor: primaryColor }}
    className={`btn-${theme}`}
  >
    {label}
  </button>
);

// 应用HOC - 自动排除theme和primaryColor属性
const ThemedButton = withTheme({
  theme: 'dark',
  primaryColor: '#007acc'
})(Button);

// 正确用法 - 只需要传入label和onClick
<ThemedButton label="Click me" onClick={() => console.log('clicked')} />

// 错误用法 - TypeScript会报错
<ThemedButton 
  label="Click me" 
  onClick={() => console.log('clicked')}
  theme="light" // 错误:theme属性已被注入
  primaryColor="#ff0000" // 错误:primaryColor属性已被注入
/>

通过这种模式,我们确保了HOC的类型安全性,避免了属性冲突,同时提供了优秀的开发体验和编译时类型检查。

复合HOC的类型组合策略

在复杂的React应用中,单一的高阶组件往往无法满足所有需求。我们需要将多个HOC组合使用,每个HOC负责不同的功能增强。这种复合HOC的模式在TypeScript中需要精心设计类型系统,以确保类型安全和良好的开发体验。

基础复合模式

最简单的复合HOC方式是通过函数嵌套调用:

// 单个HOC定义
const withAuth = <P extends object>(Component: React.ComponentType<P>) => {
  return (props: P) => {
    const isAuthenticated = useAuth();
    if (!isAuthenticated) return <LoginPage />;
    return <Component {...props} />;
  };
};

const withLogger = <P extends object>(Component: React.ComponentType<P>) => {
  return (props: P) => {
    useEffect(() => {
      console.log('Component rendered with props:', props);
    }, [props]);
    return <Component {...props} />;
  };
};

// 复合使用
const EnhancedComponent = withLogger(withAuth(MyComponent));

这种模式虽然简单,但在类型推导上存在局限性,特别是当多个HOC都需要修改props时。

类型安全的复合工具函数

为了更好的类型支持,我们可以创建一个compose工具函数:

type HOC = <P extends object>(
  Component: React.ComponentType<P>
) => React.ComponentType<any>;

function compose(...hocs: HOC[]): HOC {
  return hocs.reduce((acc, hoc) => (Component) => hoc(acc(Component)));
}

// 使用示例
const withAll = compose(withAuth, withLogger, withTheme);
const FullyEnhancedComponent = withAll(MyComponent);

多HOC props合并策略

当多个HOC都需要注入不同的props时,我们需要设计更精细的类型系统:

interface AuthProps {
  isAuthenticated: boolean;
  user: User | null;
}

interface LoggerProps {
  logEvent: (event: string, data?: any) => void;
}

interface ThemeProps {
  theme: Theme;
  toggleTheme: () => void;
}

// 定义HOC类型
type HOCWithProps<InjectedProps> = <P extends InjectedProps>(
  Component: React.ComponentType<P>
) => React.ComponentType<Omit<P, keyof InjectedProps>>;

const withAuth: HOCWithProps<AuthProps> = (Component) => (props) => {
  const auth = useAuth();
  return <Component {...props} {...auth} />;
};

const withLogger: HOCWithProps<LoggerProps> = (Component) => (props) => {
  const logger = useLogger();
  return <Component {...props} {...logger} />;
};

// 复合类型推导
type ComposedProps<T extends any[]> = T extends [infer First, ...infer Rest]
  ? First & ComposedProps<Rest>
  : {};

const composeHOCs = <T extends HOCWithProps<any>[]>(
  ...hocs: T
): HOCWithProps<ComposedProps<Parameters<T[number]>[0]>> => {
  return hocs.reduce((acc, hoc) => (Component) => hoc(acc(Component)));
};

使用泛型约束的复合模式

为了更好的类型安全性,我们可以使用更严格的泛型约束:

type ExtractInjectedProps<H> = H extends HOCWithProps<infer P> ? P : never;

function composeHOCs<H1, H2, H3>(
  hoc1: H1,
  hoc2: H2,
  hoc3: H3
): HOCWithProps<
  ExtractInjectedProps<H1> & ExtractInjectedProps<H2> & ExtractInjectedProps<H3>
> {
  return (Component) => hoc3(hoc2(hoc1(Component)));
}

// 使用示例
const withAll = composeHOCs(withAuth, withLogger, withTheme);

处理ref转发

在复合HOC中处理ref转发需要特别注意:

const withRefForwarding = <P extends object, RefType>(
  hoc: (Component: React.ComponentType<P>) => React.ComponentType<P>
) => {
  return (Component: React.ComponentType<P>) => {
    const EnhancedComponent = hoc(Component);
    return React.forwardRef<RefType, P>((props, ref) => (
      <EnhancedComponent {...props} ref={ref} />
    ));
  };
};

// 复合ref转发
const withAllAndRef = compose(
  withAuth,
  withLogger,
  withRefForwarding<MyComponentProps, HTMLDivElement>(withTheme)
);

条件复合策略

在某些场景下,我们需要根据条件动态组合HOC:

function conditionalCompose(condition: boolean, ...hocs: HOC[]): HOC {
  return condition 
    ? compose(...hocs)
    : (Component) => Component;
}

// 根据环境变量决定是否启用日志
const withConditionalLogger = conditionalCompose(
  process.env.NODE_ENV === 'development',
  withLogger
);

性能优化考虑

复合多个HOC时需要注意性能影响:

mermaid

最佳实践表格

场景推荐策略类型安全级别性能影响
简单功能增强直接嵌套调用中等
多个props注入使用compose工具
需要ref转发专用ref处理HOC
条件性增强条件复合函数
生产环境优化条件禁用某些HOC

错误处理模式

在复合HOC中添加统一的错误处理:

const withErrorBoundary = <P extends object>(
  Component: React.ComponentType<P>
) => {
  return class ErrorBoundaryWrapper extends React.Component<P> {
    state = { hasError: false };
    
    static getDerivedStateFromError() {
      return { hasError: true };
    }
    
    render() {
      if (this.state.hasError) {
        return <ErrorFallback />;
      }
      return <Component {...this.props} />;
    }
  };
};

// 在复合链的最外层添加错误边界
const withAll = compose(
  withAuth,
  withLogger,
  withTheme,
  withErrorBoundary
);

通过精心设计的类型组合策略,我们可以在保持TypeScript类型安全的同时,实现灵活且强大的高阶组件复合模式。这种模式特别适合于大型企业级应用,其中组件需要多个不同方面的功能增强。

实际项目中的HOC最佳实践案例

在实际的企业级React项目中,高阶组件(HOC)仍然是实现横切关注点(cross-cutting concerns)和代码复用的重要模式。虽然React Hooks提供了更简洁的解决方案,但在许多现有代码库和特定场景中,HOC仍然发挥着不可替代的作用。以下是几个经过实战验证的HOC最佳实践案例:

认证与权限控制HOC

在企业级应用中,认证和权限控制是最常见的横切关注点。以下是一个类型安全的认证HOC实现:

interface WithAuthProps {
  isAuthenticated: boolean;
  userRoles: string[];
  hasPermission: (permission: string) => boolean;
}

function withAuthentication<P extends WithAuthProps>(
  WrappedComponent: React.ComponentType<P>,
  requiredPermissions?: string[]
) {
  const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';

  const ComponentWithAuth = (props: Omit<P, keyof WithAuthProps>) => {
    const { isAuthenticated, user } = useAuth(); // 假设有认证Hook
    
    if (!isAuthenticated) {
      return <Redirect to="/login" />;
    }

    if (requiredPermissions && requiredPermissions.length > 0) {
      const hasRequiredPermissions = requiredPermissions.every(permission => 
        user.permissions.includes(permission)
      );
      
      if (!hasRequiredPermissions) {
        return <AccessDenied />;
      }
    }

    const authProps: WithAuthProps = {
      isAuthenticated,
      userRoles: user.roles,
      hasPermission: (permission: string) => user.permissions.includes(permission)
    };

    return <WrappedComponent {...authProps} {...props as P} />;
  };

  ComponentWithAuth.displayName = `withAuthentication(${displayName})`;
  return ComponentWithAuth;
}

// 使用示例
interface DashboardProps extends WithAuthProps {
  dashboardData: DashboardData;
}

const Dashboard: React.FC<DashboardProps> = ({ dashboardData, hasPermission }) => {
  return (
    <div>
      <h1>仪表板</h1>
      {hasPermission('view_analytics') && <AnalyticsSection data={dashboardData} />}
    </div>
  );
};

export default withAuthentication(Dashboard, ['view_dashboard']);

数据获取与状态管理HOC

对于复杂的数据获取逻辑,HOC可以提供清晰的抽象层:

interface WithDataProps<T> {
  data: T | null;
  loading: boolean;
  error: Error | null;
  refetch: () => void;
}

function withData<T, P extends WithDataProps<T>>(
  WrappedComponent: React.ComponentType<P>,
  dataFetcher: () => Promise<T>,
  config?: { 
    refreshInterval?: number;
    initialData?: T;
  }
) {
  return function ComponentWithData(props: Omit<P, keyof WithDataProps<T>>) {
    const [data, setData] = useState<T | null>(config?.initialData || null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<Error | null>(null);

    const fetchData = useCallback(async () => {
      setLoading(true);
      setError(null);
      try {
        const result = await dataFetcher();
        setData(result);
      } catch (err) {
        setError(err as Error);
      } finally {
        setLoading(false);
      }
    }, [dataFetcher]);

    useEffect(() => {
      fetchData();
      
      if (config?.refreshInterval) {
        const interval = setInterval(fetchData, config.refreshInterval);
        return () => clearInterval(interval);
      }
    }, [fetchData, config?.refreshInterval]);

    const dataProps: WithDataProps<T> = {
      data,
      loading,
      error,
      refetch: fetchData
    };

    return <WrappedComponent {...dataProps} {...props as P} />;
  };
}

// 使用示例
interface UserListProps extends WithDataProps<User[]> {
  filter?: string;
}

const UserList: React.FC<UserListProps> = ({ data, loading, error, filter }) => {
  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  
  const filteredData = data?.filter(user => 
    filter ? user.name.includes(filter) : true
  );

  return (
    <div>
      <h2>用户列表</h2>
      {filteredData?.map(user => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
};

export default withData(UserList, () => api.getUsers(), { refreshInterval: 30000 });

国际化HOC

对于多语言应用,HOC可以优雅地处理翻译和本地化:

interface WithI18nProps {
  t: (key: string, params?: Record<string, any>) => string;
  currentLanguage: string;
  changeLanguage: (lang: string) => void;
}

function withI18n<P extends WithI18nProps>(
  WrappedComponent: React.ComponentType<P>,
  namespace?: string
) {
  const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';

  const ComponentWithI18n = (props: Omit<P, keyof WithI18nProps>) => {
    const { t, i18n } = useTranslation(namespace);
    
    const i18nProps: WithI18nProps = {
      t: (key: string, params?: Record<string, any>) => t(key, params),
      currentLanguage: i18n.language,
      changeLanguage: i18n.changeLanguage
    };

    return <WrappedComponent {...i18nProps} {...props as P} />;
  };

  ComponentWithI18n.displayName = `withI18n(${displayName})`;
  return ComponentWithI18n;
}

// 使用示例
interface WelcomeProps extends WithI18nProps {
  userName: string;
}

const Welcome: React.FC<WelcomeProps> = ({ userName, t, currentLanguage }) => {
  return (
    <div>
      <h1>{t('welcome.title')}</h1>
      <p>{t('welcome.message', { name: userName })}</p>
      <span>{t('common.current_language', { language: currentLanguage })}</span>
    </div>
  );
};

export default withI18n(Welcome, 'welcome');

性能优化HOC

对于需要性能优化的组件,HOC可以提供记忆化和条件渲染控制:

interface WithPerformanceProps {
  shouldRender: boolean;
  logRender: (componentName: string, props: any) => void;
}

function withPerformance<P extends WithI18nProps>(
  WrappedComponent: React.ComponentType<P>,
  options?: {
    shouldComponentUpdate?: (prevProps: P, nextProps: P) => boolean;
    logRenders?: boolean;
  }
) {
  const displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';

  const ComponentWithPerformance = memo((props: P) => {
    const [shouldRender, setShouldRender] = useState(true);
    
    useEffect(() => {
      // 基于视口或其他条件的渲染控制
      const observer = new IntersectionObserver(([entry]) => {
        setShouldRender(entry.isIntersecting);
      });
      
      // 这里需要实际的DOM引用
      return () => observer.disconnect();
    }, []);

    const performanceProps: WithPerformanceProps = {
      shouldRender,
      logRender: (componentName: string, props: any) => {
        if (options?.logRenders) {
          console.log(`[RENDER] ${componentName}`, props);
        }
      }
    };

    if (!shouldRender) {
      return <div style={{ height: '100px' }}>Loading...</div>;
    }

    return <WrappedComponent {...performanceProps} {...props} />;
  }, options?.shouldComponentUpdate);

  ComponentWithPerformance.displayName = `withPerformance(${displayName})`;
  return ComponentWithPerformance;
}

错误边界HOC

结合错误边界模式的HOC可以提供组件级的错误处理:

interface WithErrorBoundaryProps {
  error: Error | null;
  resetError: () => void;
}

function withErrorBoundary<P extends WithErrorBoundaryProps>(
  WrappedComponent: React.ComponentType<P>,
  fallback?: React.ComponentType<{ error: Error; resetError: () => void }>
) {
  return class ComponentWithErrorBoundary extends React.Component<
    Omit<P, keyof WithErrorBoundaryProps>,
    { error: Error | null }
  > {
    state = { error: null };

    static getDerivedStateFromError(error: Error) {
      return { error };
    }

    resetError = () => {
      this.setState({ error: null });
    };

    render() {
      if (this.state.error) {
        const FallbackComponent = fallback || DefaultErrorFallback;
        return <FallbackComponent error={this.state.error} resetError={this.resetError} />;
      }

      const errorBoundaryProps: WithErrorBoundaryProps = {
        error: null,
        resetError: this.resetError
      };

      return <WrappedComponent {...errorBoundaryProps} {...this.props as P} />;
    }
  };
}

const DefaultErrorFallback: React.FC<{ error: Error; resetError: () => void }> = ({
  error,
  resetError
}) => (
  <div className="error-boundary">
    <h2>Something went wrong</h2>
    <details>
      <summary>Error details</summary>
      <pre>{error.message}</pre>
    </details>
    <button onClick={resetError}>Try again</button>
  </div>
);

这些实际案例展示了HOC在企业级应用中的强大能力,特别是在类型安全的TypeScript环境中。每个HOC都遵循单一职责原则,提供了清晰的抽象边界,并且通过泛型类型参数确保了完整的类型安全。

mermaid

通过合理的HOC组合,我们可以构建出高度可复用、类型安全且易于维护的React组件体系。这些最佳实践案例为实际项目开发提供了可靠的参考模式。

总结

高阶组件在TypeScript环境中提供了强大的类型安全组件复用能力。通过正确的泛型类型参数定义、属性排除技术和复合策略,可以构建出高度可复用、类型安全且易于维护的React组件体系。虽然React Hooks提供了替代方案,但HOC在企业级应用中仍然发挥着不可替代的作用,特别是在处理横切关注点如认证、数据获取、国际化和错误处理等方面。本文提供的实践案例和模式为开发类型安全的HOC提供了可靠的参考,有助于提升代码质量和开发体验。

【免费下载链接】react Cheatsheets for experienced React developers getting started with TypeScript 【免费下载链接】react 项目地址: https://gitcode.com/gh_mirrors/rea/react

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值