Проблема в аннотации
У меня есть 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
сам по себе немного грубоват, но его использование теперь (теоретический) бриз:
- Поддерживать отображение всех типов и связанных с ними неизменяемых типов.
- Формируйте неизменяемые типы, используя
Immutalizer
, передавая базовый тип и ImmutalizerMap
, которому он принадлежит
Immutalizer
делает все остальное, используя PrimitiveSwitch
, чтобы определить, нужно ли искать данный тип в ImmutalizerMap
или нет.
Но, как указано в абстрактной версии выше, доступ к IMMUTALIZER_MAP[PROP_TYPE]
в PrimitiveSwitch
вызывает ошибку типа: Type 'PROP_TYPE' cannot be used to index type 'IMMUTALIZER_MAP'.
Вопрос
Можно ли использовать интерфейс (имя) в качестве индексаторов в других интерфейсах? Есть ли лучшее решение для неизменной типизации?