Тьфу, это утомительно.Я не знаю, стоит ли делать такие вещи.Экзистенциальные типы - это «правильное» решение этой проблемы, но способ, которым они кодируются в TS, потребует, чтобы вы изменили свои типы на более обещающую модель (где вместо значения типа T
у вас есть функция, которая принимает обратный вызовкоторый действует на T
).
В любом случае, эта часть не изменилась:
// unchanged:
export type Selector<S, Result> = (state: S) => Result;
export interface SelectorWithValue<S, Result> {
selector: Selector<S, Result>;
value: Result;
}
export class Store<T, S, Result> {
constructor(
public initialState?: T,
public selectorsWithValue?: SelectorWithValue<S, Result>[]
) { }
}
И эта часть определенно изменилась:
// changed:
export interface Config<T, S, Result> {
initialState?: T;
// changed below, prefer inferring tuples over just arrays
selectorsWithValue?: SelectorWithValue<S, Result>[] | [SelectorWithValue<S, Result>];
}
// drill down into a Config, and make sure value R is assignable to the R in Selector<S, R>
type ConfigOkay<C extends Config<any, any, any>> =
C extends { selectorsWithValue?: infer A } ?
A extends SelectorWithValue<any, any>[] ?
{
selectorsWithValue?: { [I in keyof A]: A[I] extends {
selector: Selector<infer S, infer R1>, value: infer R2
} ? { selector: Selector<S, R1>, value: R1 } : never }
} : never : never;
export function createStore<C extends Config<T, S, any>, T = any, S = any>(
config: C & ConfigOkay<C> = {} as any // assertion:
// default value {} is not seen as C & ConfigOkay<C> I guess
): Store<T, S, any> {
return new Store(config.initialState, config.selectorsWithValue);
}
Ивот оно в действии.
const selectBooleanFromString: Selector<string, boolean> = (str) => str === 'true';
const selectNumberFromBoolean: Selector<boolean, number> = (bool) => bool ? 1 : 0;
createStore({
selectorsWithValue: [
{ selector: selectBooleanFromString, value: false },
{ selector: selectNumberFromBoolean, value: 1 } // okay
],
});
createStore({
selectorsWithValue: [
{ selector: selectBooleanFromString, value: false },
{ selector: selectNumberFromBoolean, value: "string" } // error!
],
});
ссылка на игровую площадку
Уууу?Это настолько сложно, что я, возможно, подумаю, что вы либо захотите засунуть это в библиотеку, где смертным не нужно смотреть на это ...
... или вы захотите рефакторинг на что-то другое,Скажите что-нибудь, что пометит SelectorWithValue
как «хороший», а затем примет только «хорошие»:
export type Selector<S, Result> = (state: S) => Result;
export interface SelectorWithValue<S, Result> {
selector: Selector<S, Result>;
value: Result;
}
export interface GoodSelectorWithValue<S> {
knownGood: true
selector: Selector<S, any>;
value: any
}
function vetSV<S, R>(x: SelectorWithValue<S, R>): GoodSelectorWithValue<S> {
return Object.assign({ knownGood: true as true }, x);
}
export interface Config<T, S> {
initialState?: T;
selectorsWithValue?: GoodSelectorWithValue<S>[];
}
export function createStore<T = any, S = any>(
config: Config<T, S> = {}
): Store<T, S> {
return new Store(config.initialState, config.selectorsWithValue);
}
export class Store<T, S> {
constructor(
public initialState?: T,
public selectorsWithValue?: GoodSelectorWithValue<S>[]
) { }
}
const selectBooleanFromString: Selector<string, boolean> = (str) => str === 'true';
const selectNumberFromBoolean: Selector<boolean, number> = (bool) => bool ? 1 : 0;
createStore<any, any>({
selectorsWithValue: [
vetSV({ selector: selectBooleanFromString, value: false }),
vetSV({ selector: selectNumberFromBoolean, value: 1 })
]
}); // okay
createStore<any, any>({
selectorsWithValue: [
vetSV({ selector: selectBooleanFromString, value: false }),
vetSV({ selector: selectNumberFromBoolean, value: "string" }) // error!
]
});
Playground link
Это может быть лучше, где вам нужно, чтобы люди проделали большую работу для создания SelectorWithValue
.Обратите внимание, как я должен был указать <any, any>
для createStore()
... это потому, что он ожидает, что S
будет единственной вещью, такой как string
или boolean
, а не string | boolean
, как это и должно быть,Поэтому вам может понадобиться рефакторинг, чтобы точно указать, что вы пытаетесь ограничить S
.
Надеюсь, это поможет;снова удачи.