在接口

时间:2018-10-30 00:00:33

标签: typescript interface indexer

抽象问题

我有两组要彼此关联的类型。

// 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本身本身有点难读,但是使用它现在(理论上)很容易:

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

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


问题

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

1 个答案:

答案 0 :(得分:0)

不是索引器问题的答案,我确实找到了使用递归将类型添加到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<World>而不是ImmutableWorld),但是类型仍然兼容。