You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在TypeScript中为高阶类组件及withAppContext正确设置类型?

嘿,这两个TypeScript高阶类组件的类型问题我太熟了,给你一步步讲清楚!

1. 如何在TypeScript中为高阶类组件正确设置类型?

核心思路是用泛型来保留被包装组件的props类型,避免丢失类型信息,同时明确区分「使用者需要传入的props」和「HOC注入的props」。

举个实际例子,比如我们做一个注入主题样式的HOC:

// 先定义HOC要注入的props类型
interface WithThemeProps {
  theme: { primaryColor: string; fontSize: number };
}

// 定义高阶组件:用泛型T代表被包装组件的完整props(必须包含注入的WithThemeProps)
function withTheme<T extends WithThemeProps>(
  Component: React.ComponentType<T>
): React.ComponentType<Omit<T, keyof WithThemeProps>> {
  // 返回的包装组件只需要接收「使用者需要传的props」(去掉HOC会注入的部分)
  return function ThemedComponent(props: Omit<T, keyof WithThemeProps>) {
    // 模拟从上下文/全局状态获取主题
    const theme = { primaryColor: "#2196F3", fontSize: 16 };
    // 把传入的props和注入的theme合并,传给原组件
    return <Component {...props as T} theme={theme} />;
  };
}

// 使用示例:定义一个依赖theme的组件
interface CardProps extends WithThemeProps {
  title: string;
  content: string;
}

class Card extends React.Component<CardProps> {
  render() {
    return (
      <div style={{ color: this.props.theme.primaryColor, fontSize: `${this.props.theme.fontSize}px` }}>
        <h2>{this.props.title}</h2>
        <p>{this.props.content}</p>
      </div>
    );
  }
}

// 用HOC包装后,组件只需要接收title和content,不用传theme
const ThemedCard = withTheme(Card);

// 使用时类型提示完全正确,不会报错
<ThemedCard title="Hello TS" content="高阶组件类型搞定!" />;

这里的关键是Omit<T, keyof WithThemeProps>——它帮我们剔除了HOC会自动注入的props,使用者只需要关注组件自身需要的参数。

2. 带options参数的HOC:保留原组件类型+额外上下文类型,解决IntrinsicAttributes错误

首先,你的工具函数需要先定义options类型(哪怕暂时不用,也要给TypeScript明确的类型提示),然后返回一个泛型HOC,确保类型能正确传递。

针对你提到的「IntrinsicAttributes不包含传递的props」错误,本质是因为你没有明确指定组件的props类型——当你只写React.Component而不加泛型参数时,TypeScript会默认它的props是空对象{},这时候传入任何额外props都会报错。解决方法就是明确组件的完整props类型,把自身需要的props和HOC注入的上下文props合并。

看具体实现:

// 定义HOC要注入的上下文props类型
interface AppContextProps {
  currentUser: { id: string; nickname: string };
  appSettings: { darkMode: boolean; language: string };
}

// 定义工具函数的options类型(可以加你需要的配置项)
interface WithAppContextOptions {
  enableDebug?: boolean;
}

// 工具函数:接收options,返回真正的HOC
function createWithAppContext(options: WithAppContextOptions) {
  // 这里返回的HOC用泛型T代表「使用者需要传入的props」
  return function withAppContext<T extends {}>(
    // 被包装的组件需要接收「使用者传入的props + 上下文props」
    Component: React.ComponentType<T & AppContextProps>
  ): React.ComponentType<T> {
    return function AppContextWrapper(props: T) {
      // 模拟从Context/全局状态获取上下文数据
      const contextData: AppContextProps = {
        currentUser: { id: "u_123", nickname: "小李" },
        appSettings: { darkMode: false, language: "zh-CN" }
      };
      // 合并props和上下文数据,传给原组件
      return <Component {...props} {...contextData} />;
    };
  };
}

// 创建HOC实例(可以传配置,比如{ enableDebug: true })
const withAppContext = createWithAppContext({});

// 使用示例:定义组件,明确它的props是自身需要的 + 上下文props
interface UserProfileProps {
  showEditButton: boolean;
}

class UserProfile extends React.Component<UserProfileProps & AppContextProps> {
  render() {
    const { showEditButton, currentUser, appSettings } = this.props;
    return (
      <div>
        <h3>{currentUser.nickname}</h3>
        <p>语言:{appSettings.language}</p>
        {showEditButton && <button>编辑资料</button>}
      </div>
    );
  }
}

// 包装组件
const WrappedUserProfile = withAppContext(UserProfile);

// 使用时只需要传UserProfile自身的props,上下文props由HOC自动注入
// 这里类型提示完全正常,不会出现IntrinsicAttributes错误
<WrappedUserProfile showEditButton={true} />;

如果你希望能在代码中快速查看被包装组件的原始类型(包括上下文props),可以加个类型工具:

// 类型工具:获取组件的props类型
type GetComponentProps<C> = C extends React.ComponentType<infer P> ? P : never;

// 这样就能拿到UserProfile的完整props类型
type UserProfileFullProps = GetComponentProps<typeof UserProfile>;

关键总结

  • 永远不要省略组件的泛型参数(比如React.Component<PropsType>而不是React.Component),否则TypeScript会默认props为空对象;
  • 用泛型传递类型信息,确保HOC不会丢失被包装组件的props类型;
  • 用交叉类型T & ContextProps定义被包装组件的完整props,用React.ComponentType<T>定义包装后组件的props(使用者需要传的部分)。

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

火山引擎 最新活动