【发布时间】: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