如何区分 React 上下文的联合
Posted
技术标签:
【中文标题】如何区分 React 上下文的联合【英文标题】:How to discriminate the union of React contexts 【发布时间】:2019-11-23 06:56:49 【问题描述】:我有两种类型的 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】:抱歉,我的回答可能不完整,但可能会有所帮助。
为什么下面的代码无法编译?
const isDesktopLayout = (
ctx: React.Context<LayoutContextType>
): ctx is React.Context<DesktopLayoutContextType> =>
return true // how can I do this?
让我们看看React.Context<T>
类型。它有Provider
和Consumer
,它们都使用T
类型并将其用作props
的参数类型。 T
不在任何地方使用,除了作为参数类型。
这会导致编译错误。要了解原因,请查看下面的简化示例。这是一个接受回调的函数。
function f1 (callback: ((props: string | boolean) => void))
尝试使用callback
调用f1
以下回调会出错
f1((props: string) => ); // Type string | boolean is not assignable to type string
当您尝试编写类型保护时,基本上会发生相同的情况。 React.Context<LayoutContextType>
将 Provider
和 Consumer
定义为接受 props
类型为 LayoutContextType
的函数(进行了一些修改,但这并不重要)。并且您不能将上下文React.Context<DesktopLayoutContextType>
与Provider
和Consumer
定义为接受props
类型DesktopLayoutContextType
的函数。
幸运的是,通过使用上下文联合可以轻松解决此问题,如下所示
const isDesktopLayout = (
ctx: React.Context<DesktopLayoutContextType> |
React.Context<MobileLayoutContextType>
): ctx is React.Context<DesktopLayoutContextType> =>
return true // how can I do this?
第二个问题更难解决。类型保护在运行时用于查找ctx
的类型。但是React.Context<DesktopLayoutContextType>
和React.Context<MobileLayoutContextType>
之间的唯一区别是参数的类型。在运行时,无法找到回调函数想要接受的参数类型。由于 javascript 是动态语言,因此可以使用任意数量的任意类型的参数调用回调。而且我们无法提前知道参数的类型和数量。
因此,在运行时区分上下文的一种方法可以是 diplayName
属性(如您所建议的那样)。它应该在上下文创建期间设置,之后不要更改,并在类型保护中检查。可以这样完成
const isDesktopLayout = (
ctx: React.Context<DesktopLayoutContextType> |
React.Context<MobileLayoutContextType>
): ctx is React.Context<DesktopLayoutContextType> =>
if (ctx.displayName === undefined) throw 'context displayName is undefined!'
return ctx.displayName === 'desktop'
但是这个解决方案不绑定类型,而是绑定displayName
的值,可以在代码中的任何地方更改,这可能会导致错误。
【讨论】:
以上是关于如何区分 React 上下文的联合的主要内容,如果未能解决你的问题,请参考以下文章
如何区分antMatchers与路径变量与上下文路径的其余部分[重复]