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

如何创建可传入内外组件作为Props的React包装器组件?

解决React通用Context包装组件的实现问题

错误原因分析

你之前的代码报错是因为混淆了组件类型和React元素实例:

  • WrappedComponent定义时接收的OutterComponent是React.JSX.ElementType(即组件函数/类),但调用时传入的<Tooltip .../>是已经创建好的React元素(本质是对象),不符合组件类型的要求,因此抛出"Element type is invalid"错误。

正确实现方案

要实现一个能接收任意Context Provider数组、动态包装子元素的通用组件,可按以下步骤编写:

1. 定义TypeScript类型

先明确组件的Props类型,支持传入多个Provider的配置:

// 单个Provider的配置:组件类型 + 可选的组件Props
type ProviderConfig = {
  component: React.JSX.ElementType;
  props?: React.ComponentProps<any>;
};

// Context包装组件的Props
type ContextWrapperProps = {
  providers?: ProviderConfig[];
  children: React.ReactNode;
};

2. 实现通用包装组件

使用reduceRight从内到外依次用每个Provider包裹子元素:

const ContextWrapper: React.FC<ContextWrapperProps> = ({ providers = [], children }) => {
  return providers.reduceRight((wrappedContent, { component: Provider, props }) => {
    return <Provider {...props}>{wrappedContent}</Provider>;
  }, children);
};

3. 使用示例

假设你有两个Context,需要同时包装子组件:

// 示例Context定义
const ThemeContext = React.createContext<'light' | 'dark'>('light');
const AuthContext = React.createContext<{ user: string | null }>({ user: null });

// 使用ContextWrapper
const App = () => {
  return (
    <ContextWrapper
      providers={[
        {
          component: ThemeContext.Provider,
          props: { value: 'dark' },
        },
        {
          component: AuthContext.Provider,
          props: { value: { user: 'test-user' } },
        },
      ]}
    >
      {/* 这里的所有子元素都能访问ThemeContext和AuthContext */}
      <Button>Button text</Button>
    </ContextWrapper>
  );
};

核心逻辑说明

  • reduceRight确保数组中第一个Provider是最外层的包裹组件,最后一个是最内层的,符合React组件嵌套逻辑。
  • 传入的是组件类型(如ThemeContext.Provider)而非元素实例,组件内部可以动态渲染并传入对应的props。

需求适配

如果需要选择性传入Context,只需调整providers数组的内容即可——不传或传空数组时,子元素直接渲染,不会被任何Provider包装。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 23:34:53