在TypeScript中包装函数的嵌套对象?

时间:2018-05-18 01:01:54

标签: javascript typescript redux

我使用Redux并将商店数据分解为"切片"。每个"切片"对应于userscomments等区域。我将每个切片的选择器组合成一个顶层selectors模块,我在整个应用程序中访问该模块。

切片的格式为:

export interface IUsersSelectors {
  getCurrentUser(state: IUsersState): IUser | undefined;
}

const selectors: IUsersSelectors = {
  getCurrentUser(state: IUsersState) {
    return state.currentUser;
  }
};

export default {
  getInitialState,
  reducer,
  selectors
};

然后全部导入,选择器合并:

export const selectors = Object.keys(slices).reduce((combinedSelectors: any, sliceKey: string) => {
  const sliceSelectors = slices[sliceKey].selectors;

  combinedSelectors[sliceKey] = Object.keys(sliceSelectors).reduce((selectorsMap: object, selectorKey: string) => {
    const localizedSelector = sliceSelectors[selectorKey];

    selectorsMap[selectorKey] = (globalState, ...args: any[]): any => {
      return localizedSelector(globalState[sliceKey], ...args);
    };

    return selectorsMap;
  }, {});

  return combinedSelectors;
}, {});

然后在整个应用程序中使用:

selectors.users.getCurrentUser(store.getState());

这意味着选择器在检索数据时只需要它们的切片状态,但它们实际上是调用的全局存储状态。我基本上只是将它们包装在管理范围的另一个函数中。

我最接近为此定义泛型类型的是:

type IScopedSelector<T extends () => any> = (globalState: IStoreState, ...args: any[]) => ReturnType<T>;

type IScopedSelectors<T> = {
  [K in keyof T]: IScopedSelector<T[K]>;
};

type INestedScopedSelectors<R> = {
  [S in keyof R]: IScopedSelectors<R[S]>;
};

export const selectors: INestedScopedSelectors<ISelectors>...

其中ISelectors是形状的简单接口:

export interface ISelectors {
  users: IUsersSelectors;
}

但是,在尝试将T[K]传递给IScopedSelector时出现错误,因为它必须是一个函数:

[ts] Type 'T[K]' does not satisfy the constraint '() => any'.

如果我删除extends () => any,则会收到有关ReturnType的错误:

[ts] Type 'T' does not satisfy the constraint '(...args: any[]) => any'.

理想情况下,我也会维护选择器参数的输入(而不是...args: any[]),只会覆盖第一个参数作为全局存储状态。

有没有更好的方法来处理这样的嵌套泛型?这甚至可能吗?

1 个答案:

答案 0 :(得分:2)

您需要为IScopedSelectorsINestedScopedSelectors的通用类型添加类似的类型约束。

如果没有这样的约束,您已经告诉TypeScript T中的IScopedSelectors<T>可以是任何类型。所以IScopedSelectors<string>应该有效。但TypeScript正确地指出,对于许多类型(例如string),[K in keyof T]: IScopedSelector<T[K]>不会起作用,因为无法保证T[K]遵守{{1}强加的约束}}

所以解决方案只是为两个接口添加一个约束,以便TypeScript具有这种保证。为此,内置的IScopedSelector类型可能会有所帮助。如下所示:

Record

您可能希望根据具体用例将type IScopedSelectors<T extends Record<string, () => any>> = { [K in keyof T]: IScopedSelector<T[K]>; // T[K] is now guaranteed to adhere to () => any }; type INestedScopedSelectors<R extends Record<string, Record<string, () => any>>> = { [S in keyof R]: IScopedSelectors<R[S]>; // Similarly, R[S] is now guaranteed to adhere to Record<string, () => any>, exactly what IScopedSelectors is expecting. }; 类型替换为更具体的类型,但解决方案基本相同。只需确保在层次结构中一直转发约束。

希望有所帮助!