如何区分React上下文的并集

时间:2019-07-14 06:21:03

标签: reactjs typescript react-context

我有两种类型的React上下文,我需要能够确定它们的类型(移动和桌面)。如何以类型安全的方式这样做?

我尝试编写利用以下属性的用户定义类型防护:React.Context<LayoutContextType>等效于React.Context<DesktopLayoutContextType> | React.Context<MobileLayoutContextType>

但是,在假定它们相等时,我似乎是错误的。

interface ILayoutStateBase {
  nightMode: boolean
}

interface ILayoutContextBase<
  StateType extends ILayoutStateBase,
  Kind extends 'desktop' | 'mobile'
> {
  kind: Kind
  state: StateType
}

interface IDesktopState extends ILayoutStateBase {
  modalOpen: boolean
}

interface IMobileState extends ILayoutStateBase {
  sidebarOpen: boolean
}

type DesktopLayoutContextType = ILayoutContextBase<IDesktopState, 'desktop'>
type MobileLayoutContextType =  ILayoutContextBase<IMobileState, 'mobile'>

type LayoutContextType =
  | DesktopLayoutContextType
  | MobileLayoutContextType


// below results in:
/**
 * Type 'Context<ILayoutContextBase<IDesktopState, "desktop">>' 
 *   is not assignable to type 'Context<LayoutContextType>'.
 */
const isDesktopLayout = (
  ctx: React.Context<LayoutContextType>
): ctx is React.Context<DesktopLayoutContextType> => {
  return true // how can I do this?
}

我希望TypeScript能够识别React.Context<LayoutContextType>等同于React.Context<DesktopLayoutContextType> | React.Context<MobileLayoutContextType>,并允许我使用displayName属性来区分它们。

但是,我在提供的代码中收到错误消息:

Type 'Context<ILayoutContextBase<IDesktopState, "desktop">>' is not assignable to type 'Context<LayoutContextType>'.

1 个答案:

答案 0 :(得分:0)

对不起,因为我的回答可能并不完整,但可能会有所帮助。

  1. 为什么下面的代码无法编译

    const isDesktopLayout = (
        ctx: React.Context<LayoutContextType>
    ): ctx is React.Context<DesktopLayoutContextType> => {
        return true // how can I do this?
    }
    

    让我们看看React.Context<T>类型。它具有ProviderConsumer,它们都使用类型T并将其用作参数props的类型。 T除了作为参数类型之外,没有在其他任何地方使用。

    这导致编译错误。要了解原因,请查看下面的简化示例。这是一个接受回调的函数。

    function f1 (callback: ((props: string | boolean) => void)) {}
    

    尝试在回调后用f1调用callback会出错

    f1((props: string) => {});  // Type string | boolean is not assignable to type string
    

    基本上,当您尝试编写类型后卫时,会发生相同的情况。 React.Context<LayoutContextType>已将ProviderConsumer定义为接受类型为props的{​​{1}}的函数(经过一些修改,但这并不适用)。并且您无法将上下文LayoutContextType分配为React.Context<DesktopLayoutContextType>Provider定义为接受类型为Consumer的{​​{1}}的函数。

    幸运的是,通过使用上下文联合(如下所示)可以轻松解决此问题

    props
  2. 第二个问题很难解决。类型防护在运行时中用于查找DesktopLayoutContextType的类型。但是const isDesktopLayout = ( ctx: React.Context<DesktopLayoutContextType> | React.Context<MobileLayoutContextType> ): ctx is React.Context<DesktopLayoutContextType> => { return true // how can I do this? } ctx之间的唯一区别是参数的类型。在运行时,无法找到想要接受的参数回调函数类型。由于JavaScript是动态语言,因此可以使用任何类型的任意数量的参数来调用回调。而且我们无法事先知道参数的类型和数量。

    因此,在运行时区分上下文的一种方法可以是React.Context<DesktopLayoutContextType>属性(如您所建议)。应该在上下文创建期间设置它,之后不要更改它,并签入类型保护。可以这样做

    React.Context<MobileLayoutContextType>

    但是此解决方案不限于类型,而是绑定到diplayName的值,该值可以在代码中的任何位置更改,这可能会导致错误。