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

如何在TypeScript中安全组合带Value属性的React Provider?

类型安全地组合React Context Provider(避免嵌套层级)

我想避免在应用组件外层嵌套大量Provider导致的「侧山状」组件层级问题,打算通过组合方式整合这些Provider。我的Provider大多带有value属性,已经找到了JS或用TSany类型的实现方式,但希望在不使用any的前提下,在TypeScript中安全组合带或不带value的Provider。

以下是我编写的示例代码,尝试组合两个带value的Provider,但遇到了类型错误:

import React from "react"
import RefreshablePlayersContext from "contexts/RefreshablePlayersContext"
import RefreshableGamesContext from "contexts/RefreshableGamesContext"
import type { RefreshablePlayersContext as RefreshablePlayersContextType } from "types/RefreshablePlayersContext"
import type { RefreshableGamesContext as RefreshableGamesContextType } from "types/RefreshableGamesContext"
import type { Player } from "types/Player"
import type { Game } from "types/Game"
import type { RefreshableRequest } from "types/RefreshableRequest"

interface ProviderWithValue<ContextType> {
  provider: React.Provider<ContextType>
  value: ContextType
}

interface ProvidersProps {
  children: React.ReactElement
}

const Providers = ({ children }: ProvidersProps): React.ReactElement => {
  const [refreshableGames, setRefreshableGames] = React.useState<
    RefreshableRequest<Game[]>
  >({
    status: "Not Started",
  })

  const [refreshablePlayers, setRefreshablePlayers] = React.useState<
    RefreshableRequest<Player[]>
  >({
    status: "Not Started",
  })

  const providersWithValues: [
    ProviderWithValue<RefreshableGamesContextType>,
    ProviderWithValue<RefreshablePlayersContextType>,
  ] = [
    {
      provider: RefreshableGamesContext.Provider,
      value: { refreshableGames, setRefreshableGames },
    },
    {
      provider: RefreshablePlayersContext.Provider,
      value: { refreshablePlayers, setRefreshablePlayers },
    },
  ]

  return providersWithValues.reduceRight((acc, CurrentProviderWithValue) => {
    return (
      // TypeScript错误出在这里:传递给`value`属性的值不符合类型要求
      <CurrentProviderWithValue.provider value={CurrentProviderWithValue.value}>
        {acc}
      </CurrentProviderWithValue.provider>
    )
  }, children)
}

export default Providers

对应的TypeScript错误信息:

Type 'RefreshableGamesContext | RefreshablePlayersContext' is not assignable to type 'RefreshableGamesContext & RefreshablePlayersContext'.
Type 'RefreshableGamesContext' is not assignable to type 'RefreshableGamesContext & RefreshablePlayersContext'.
Type 'RefreshableGamesContext' is missing the following properties from type 'RefreshablePlayersContext': refreshablePlayers, setRefreshablePlayers


问题原因

TypeScript处理数组元素的联合类型时,会错误地将provider和value的类型做交叉推断,误以为所有元素的provider都需要接受两种value的交集类型,最终导致类型不匹配。

解决方案:泛型函数+类型区分实现安全组合

我们可以通过泛型函数为每个Provider单独推断类型,同时扩展支持无value的Provider:

1. 定义通用Provider类型

先区分带value和不带value的Provider类型:

// 带value的Provider类型
type ProviderWithValue<T> = {
  provider: React.Provider<T>
  value: T
}

// 不带value的Provider类型(仅需要children属性)
type ProviderWithoutValue = {
  provider: React.ComponentType<{ children: React.ReactNode }>
} & { value?: never } // 约束:不允许传入value属性

// 合并两种类型
type AnyProvider = ProviderWithValue<unknown> | ProviderWithoutValue

2. 创建泛型渲染函数

用泛型函数单独处理每个Provider的渲染,确保value和provider类型严格匹配:

const renderProvider = <T>(
  providerConfig: ProviderWithValue<T> | ProviderWithoutValue,
  children: React.ReactNode
) => {
  // 类型守卫:判断是否为带value的Provider
  if ('value' in providerConfig) {
    return <providerConfig.provider value={providerConfig.value}>{children}</providerConfig.provider>
  }
  return <providerConfig.provider>{children}</providerConfig.provider>
}

3. 修改组合逻辑

在Providers组件中使用这个函数替代直接渲染:

const Providers = ({ children }: { children: React.ReactNode }): React.ReactElement => {
  const [refreshableGames, setRefreshableGames] = React.useState<
    RefreshableRequest<Game[]>
  >({
    status: "Not Started",
  })

  const [refreshablePlayers, setRefreshablePlayers] = React.useState<
    RefreshableRequest<Player[]>
  >({
    status: "Not Started",
  })

  const providers: AnyProvider[] = [
    {
      provider: RefreshableGamesContext.Provider,
      value: { refreshableGames, setRefreshableGames },
    },
    {
      provider: RefreshablePlayersContext.Provider,
      value: { refreshablePlayers, setRefreshablePlayers },
    },
    // 示例:添加一个不带value的Provider(比如默认主题的ThemeProvider)
    { provider: ThemeProvider }
  ]

  return providers.reduceRight((acc, provider) => {
    return renderProvider(provider, acc)
  }, children)
}

效果说明

  • TypeScript会为每个Provider单独验证value的类型是否匹配对应的provider,不会再出现联合类型交叉的错误
  • 支持不带value的Provider,且如果不小心给这类Provider传了value,TypeScript会直接抛出错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 22:24:51