【问题标题】:derived generic class cannot be assigned to base generic class in typescript派生的泛型类不能分配给打字稿中的基泛型类
【发布时间】:2021-06-21 16:59:53
【问题描述】:

我有一张存储各种元素的地图。定义如下:

const map = new Map<string, IComponent<IBase>>();

我想用这个映射来存储IComponent&lt;IBase&gt;IComponent&lt;INumber&gt;IComponent&lt;IText&gt;等的实例。

基本接口定义如下:

// base interface
interface IBase {}
// base component type
export type IComponent<T extends IBase> = React.FC<T>;

现在我想添加一个NumberComponent

// derived interface
interface INumber extends IBase {
    someNumber: number;
}
// some component
const NumberComponent: IComponent<INumber> = (props) => {
    return (
        <div>{props.someNumber}</div>
    );
}

我使用map.set('number', NumberComponent) 将其存储到地图中,但我得到了

Argument of type 'FC<INumber>' is not assignable to parameter of type 'FC<IBase>'.
  Types of parameters 'props' and 'props' are incompatible.
    Type 'PropsWithChildren<IBase>' is not assignable to type 'PropsWithChildren<INumber>'.
      Property 'someNumber' is missing in type 'PropsWithChildren<IBase>' but required in type 'INumber'.

我知道我可以使用map.set('number', NumberComponent as IComponent&lt;IBase&gt;) 来转换类型。但是,我不想使用as。有没有其他方法可以解决这个问题?

demo on ts playground

【问题讨论】:

  • 我不确定你想用地图实现什么。我想你基本上希望能够调用map.get('number'); 并接收一个类型化的数字组件,但是由于只输入了值,TypeScript 不会知道键“数字”后面有 NumberComponent。
  • /play?ts=3.9.7#code/JYWwDg9gTgLgBAJQKYEMDG8BmUIjgcilQ3wG4AocgeirgCMUBnJOYAOxiSk3SXPc7decAJIAhJiwDeAX2q00uSGyQc4MAJ5g+SAB6RY6rSxEBhJRBUcAPABU4ezmwAmjUROZwAvHFkA+b0RiGAA6ADFTOz8KSho4Zy5gADckZ1YOLh40PgFM4REAOQBXEDouB10nV3dJX3I4BrhGXCRi0q4ALjg2ErKoCjl+DKFs0VtHCqq3cVqpesbOSq7GGCh2AHMB+SaWuEVwS1UYckU2Fbg2vvMDqxguswtb60LergCfAAowHDBGAEpvAE5o04EQYEUoGw4B95iCGtZnMk-FJvhBfiFmiBWq8oDJrFREUk-LC4H8tidLOdxpVrsojvdaYcbCJqTB3tDUb8AV4gSSwRCoTC4Y0EUiUT9GCFFjA8QSkSS-uRBqdziAUGBAsDGgAiHrtKDarqXLiM24AGhJ2ulhrgrNNRwtMjgTD2lJgMQpZ3gCAgEBg9o4XWQ6FCEUCH25vJBKvgAfgPjVYCljhiIP5kLg1jj6kcXm1AAkkAAbIsQOAAdWgRec2rgVGiSqAA

标签: reactjs typescript


【解决方案1】:

这是可以做到的。你只需要超载你的Map

import React from 'react'

interface IBase { }

export type IComponent<T extends IBase> = React.FC<T>;

interface INumber extends IBase {
  someNumber: number;
}


type MapOverloads = Map<string, IComponent<IBase>> & Map<string, IComponent<INumber>>
const map:MapOverloads  = new Map<string, IComponent<IBase>>();


const NumberComponent: IComponent<INumber> = (props) => {
  return (
    <div>{props.someNumber}</div>
  );
}

const BaseComponent: IComponent<IBase> = (props) => {
  return null
}

map.set('number', NumberComponent)

map.set('base', BaseComponent)

Playground

主要思想被@jcalz的this answer无耻窃取

但是,因为map 是可变的,并且我假设您希望它是可变的,所以getter 存在问题。


const expectNumber = map.get('number') // IComponent<IBase> | undefined

附:由于逆变,您无法将IComponent&lt;INumber&gt; 分配给IComponent&lt;IBase&gt;

【讨论】:

  • 感谢您的想法。此解决方案有效,但不容易扩展。现在我只有NumberComponent,但将来我会有DateComponentTextComponent 和许多其他组件。而且MapOverLoads的定义会越来越长。
  • 你想让map可变还是不可变?
  • 抱歉,我的时间不多了,不过你可以尝试按照stackoverflow.com/questions/65891135/…这个例子重构你的类型
  • 我有点困惑为什么可变或不可变很重要。就我而言,我希望这张地图不可变。我使用map 而不是object,因为我希望它可以在许多文件中使用。例如,我在NumberComponent.tsx 中使用map.set('number', NumberComponent)。其他人在TextComponent.tsx 中使用map.set('text', TextComponent)。我尽量避免object,因为我想分散注册。
  • 如果你希望它是不可变的,你不能使用set
猜你喜欢
  • 1970-01-01
  • 2014-03-22
  • 2019-06-08
  • 1970-01-01
  • 2018-05-06
  • 2012-09-08
  • 2019-08-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多