【问题标题】:Use type name for indexer in interface在接口中使用索引器的类型名称
【发布时间】:2018-10-30 00:00:33
【问题描述】:

摘要中的问题

我想将 2 组类型相互关联。

// Group A
interface Hello { ... }
interface Foo { ... }

// Group B
interface World { ... }
interface Bar { ... }

为此,我正在构建第三个接口,用作从一种类型到另一种类型的查找:

interface LookupMap {
  Hello: World;
  Foo: Bar;
}

最终我希望能够使用一个接口作为LookupMap 的索引器从一种类型转换到另一种类型(类似于如何使用字符串和数字在索引对象文字中查找类型):

type AltType<T> = LookupMap<T>;

const altHello: AltType<Hello> = ...; // should be of type World
const altFoo: AltType<Foo> = ...;     // should be of type Bar

这不起作用 - 似乎类型不能用作那样的索引器。


实际用例

我正在尝试向 Immutable.js 添加一些更好的类型。

在这点下面有一些看起来很粗糙的代码。如果您已经有了解决方案,那么您可以不用阅读所有这些内容...

不可变对象有很多有用的函数,为了论证,我们试着给Map.get添加类型。

只要你所有的值都是原始的,其实很简单:

interface Immutalizer<MUTABLE_TYPE> extends Immutable.Map<keyof MUTABLE_TYPE, MUTABLE_TYPE[keyof MUTABLE_TYPE]> {
  get<PROP_NAME extends keyof MUTABLE_TYPE>(prop: PROP_NAME, notSetValue?: MUTABLE_TYPE[PROP_NAME]): MUTABLE_TYPE[PROP_NAME];
}

interface MyInterface {
  hello: boolean;
  world: string;
  foo: number;
  bar: symbol;
}

interface MyImmutableInterface extends Immutalizer<MyInterface> {};

const myObject: MyImmutableInterface = Immutable.fromJS(...);
myObject.get("hello"); // boolean
myObject.get("world"); // string
myObject.get("foo");   // number
myObject.get("bar");   // symbol

深入了解,如果我们的一些道具是复杂的对象,我们必须为Immutalizer 提供第二种类型以赋予它一些上下文:

interface Immutalizer<
  MUTABLE_TYPE,
  COMPLEX_OBJECT_KEYMAP extends { [PROP_NAME in keyof MUTABLE_TYPE]: any } = MUTABLE_TYPE
> extends Immutable.Map<
  keyof MUTABLE_TYPE,
  COMPLEX_OBJECT_KEYMAP[keyof MUTABLE_TYPE]
> {
  get<PROP_NAME extends keyof MUTABLE_TYPE>(prop: PROP_NAME, notSetValue?: COMPLEX_OBJECT_KEYMAP[PROP_NAME]): COMPLEX_OBJECT_KEYMAP[PROP_NAME];
}


interface Hello {
  foo: string;
  bar: World;
}

interface World {
  a: number;
  b: symbol;
}

interface ImmutableHello extends Immutalizer<Hello, {
  foo: string;
  bar: ImmutableWorld;
}> {};

interface ImmutableWorld extends Immutalizer<World> {}; // this one is all primitives so the default type will cover it

const myObject: ImmutableHello = Immutable.fromJS(...);
myObject.get("bar");          // ImmutableWorld
myObject.get("foo");          // string
myObject.get("bar").get("a"); // number
myObject.get("bar").get("b"); // symbol

这是一项繁重的工作,而且随着对象树的深入,情况只会变得更糟 - 所以我制定了一个替代解决方案,哦,如此接近可以工作,但并不完全还没有:

type Primitive = string | number | boolean | symbol | String | Number | Boolean | Symbol;
type PrimitiveSwitch<PROP_TYPE, IMMUTALIZER_MAP extends ImmutalizerMap> =
  PROP_TYPE extends Primitive ?
  PROP_TYPE :
  IMMUTALIZER_MAP[PROP_TYPE]; // TYPE ERROR: Type 'PROP_TYPE' cannot be used to index type 'IMMUTALIZER_MAP'.
interface ImmutalizerMap { [mutableType: string]: Immutalizer<any, this> }

interface Immutalizer<MUTABLE_TYPE, IMMUTALIZER_MAP extends ImmutalizerMap> {
  get<PROP_NAME extends keyof MUTABLE_TYPE>(
    prop: PROP_NAME,
    notSetValue?: PrimitiveSwitch<MUTABLE_TYPE[PROP_NAME], IMMUTALIZER_MAP>
  ): PrimitiveSwitch<MUTABLE_TYPE[PROP_NAME], IMMUTALIZER_MAP>;
}


export interface Hello {
  foo: string;
  bar: World;
}

export interface World {
  a: number;
  b: symbol;
}

interface ImmutableHello extends Immutalizer<Hello, ImmutalizerMap> { }
interface ImmutableWorld extends Immutalizer<World, ImmutalizerMap> { }

interface MyImmutalizerMap {
  Hello: ImmutableHello;
  World: ImmutableWorld;
}

const hello: ImmutableHello = Immutable.fromJS(...);

hello.get("foo"); // string
hello.get("bar"); // unknown (should be ImmutableWorld)

Immutalizer 位本身读起来有点粗糙,但现在使用它(理论上)轻而易举:

  • 维护所有类型及其关联的不可变类型的映射。
  • 通过传入基类型和它所属的ImmutalizerMap,使用Immutalizer 形成不可变类型
  • Immutalizer 完成其余工作,使用 PrimitiveSwitch 确定是否需要在 ImmutalizerMap 中查找任何给定类型。

但正如上面抽象版本中所说,在PrimitiveSwitch中访问IMMUTALIZER_MAP[PROP_TYPE]会触发类型错误:Type 'PROP_TYPE' cannot be used to index type 'IMMUTALIZER_MAP'.


问题

一个接口(名称)可以在其他接口中用作索引器吗?对于不可变类型有更好的解决方案吗?

【问题讨论】:

    标签: typescript interface indexer


    【解决方案1】:

    好吧,不是索引器问题的答案,我确实找到了一个更完整的解决方案来使用递归向 Immutable.js 添加类型:

    type Primitive = string | number | boolean | symbol | String | Number | Boolean | Symbol;
    
    type PrimitiveSwitch<MUTABLE_TYPE, PROP_NAME extends keyof MUTABLE_TYPE> = MUTABLE_TYPE[PROP_NAME] extends Primitive ? MUTABLE_TYPE[PROP_NAME] : Immutalizer<MUTABLE_TYPE[PROP_NAME]>
    
    interface Immutalizer<MUTABLE_TYPE> extends Immutable.Map<keyof MUTABLE_TYPE, Immutalizer<MUTABLE_TYPE[keyof MUTABLE_TYPE]> | MUTABLE_TYPE[keyof MUTABLE_TYPE]> {
      get<PROP_NAME extends keyof MUTABLE_TYPE>(prop: PROP_NAME, notSetValue?: PrimitiveSwitch<MUTABLE_TYPE, PROP_NAME>): PrimitiveSwitch<MUTABLE_TYPE, PROP_NAME>
    }
    
    interface Hello {
      foo: string;
      bar: World;
    }
    
    interface World {
      a: number;
      b: symbol;
    }
    
    interface ImmutableHello extends Immutalizer<Hello> { };
    interface ImmutableWorld extends Immutalizer<World> { };
    
    let hello: ImmutableHello = Immutable.fromJS({});
    let world: ImmutableWorld = hello.get("bar");
    
    hello.get("bar").get("b"); // symbol
    world.get("b");            // symbol
    

    您丢失了接口的名称(hello.get("bar") 返回 Immutalizer&lt;World&gt; 而不是 ImmutableWorld)但类型仍然兼容。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-22
      • 2020-02-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多