使用嵌套的通用接口仅允许数组中的某些类型

时间:2019-05-06 15:22:22

标签: typescript typescript-generics

所需:对于传递给createStore函数的数组中的每个元素,selector的第二种类型应与value的类型匹配。

Ex:如果selector属性的类型为Selector<boolean, number>,则value属性的类型应该为number,而与数组其他元素的类型无关。

export type Selector<S, Result> = (state: S) => Result;

export interface SelectorWithValue<S, Result> {
  selector: Selector<S, Result>;
  value: Result;
}

export interface Config<T, S, Result> {
  initialState?: T;
  selectorsWithValue?: SelectorWithValue<S, Result>[];
}

export function createStore<T = any, S = any, Result = any>(
  config: Config<T, S, Result> = {}
): Store<T, S, Result> {
  return new Store(config.initialState, config.selectorsWithValue);
}

export class Store<T, S, Result> {
  constructor(
    public initialState?: T,
    public selectorsWithValue?: SelectorWithValue<S, Result>[]
  ) {}
}

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: 'string' } // should error since isn't a number
  ],
});

Typescript Playground

这是我第一次尝试修改Typescript游乐场@jcalz provided for the nested array use case

Attempt Playground


说明:以上是我对数组第二个元素强制执行错误的尝试。但是,它确实出错,但是原因有误。这是我最初的尝试,根本不给出任何错误:

export type Selector<S, Result> = (state: S) => Result;

export interface SelectorWithValue<S, Result> {
  selector: Selector<S, Result>;
  value: Result;
}

export interface Config<T> {
  initialState?: T;
  selectorsWithValue?: SelectorWithValue<any, any>[];
}

export function createStore<T = any>(
  config: Config<T> = {}
): Store<T> {
  return new Store(config.initialState, config.selectorsWithValue);
}

export class Store<T> {
  constructor(
    public initialState?: T,
    public selectorsWithValue?: SelectorWithValue<any, any>[]
  ) {}
}

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: 'string' } // should error unless value is a number.
    //the passed `selector` property is type Selector<boolean, number>, therefor, the `value` should be a number
    //the second type of the selector property should match the type of value
  ],
});

这里是Typescript Playground

1 个答案:

答案 0 :(得分:0)

呃,这很累。我不知道这样做是值得的。存在类型是解决此问题的“正确”解决方案,但是在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!
  ],
});

Playground link

是吗?它是如此复杂,以至于您可能想将其推入一个没有凡人需要看的图书馆中……


...或者您可能想要重构其他内容,说一些将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是像stringboolean这样的单一事物,而不是{ {1}},这是必须的。因此,您可能需要在那里进行一些重构,以准确指定要限制string | boolean的内容。

希望有所帮助;再次祝你好运。