【问题标题】:Wrap nested object of functions in TypeScript?在 TypeScript 中包装函数的嵌套对象?
【发布时间】:2018-05-23 00:27:29
【问题描述】:

我正在使用 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 () =&gt; any,则会收到关于 ReturnType 的错误:

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

理想情况下,我也会保持选择器参数的类型(而不是 ...args: any[]),仅将第一个参数覆盖为全局存储状态。

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

【问题讨论】:

    标签: javascript typescript redux


    【解决方案1】:

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

    如果没有这样的约束,你已经告诉 TypeScript IScopedSelectors&lt;T&gt; 中的 T 可以是任何类型。所以IScopedSelectors&lt;string&gt; 应该是有效的。但是 TypeScript 正确地指出,对于许多类型(例如 string),[K in keyof T]: IScopedSelector&lt;T[K]&gt; 将不起作用,因为不能保证 T[K] 遵守 IScopedSelector 施加的约束。

    所以解决方案就是给两个接口都添加一个约束,这样 TypeScript 就有了保证。为此,内置的 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.
    };
    

    您可能希望根据您的具体用例将Record 类型替换为更具体的类型,但解决方案基本相同。只需确保将约束一直向上转发。

    希望有帮助!

    【讨论】:

    • 感谢您的回复!我认为这在理论上是有道理的。但是,当我尝试将ISelectors 接口传递给INestedScopedSelectors 时出现错误:Type 'ISelectors' does not satisfy the constraint 'Record&lt;string, Record&lt;string, () =&gt; any&gt;&gt;'. Index signature is missing in type 'ISelectors'. 我想我需要Record 类型在ISelectors 的某处,但不确定它是什么样子,因为接口是只是一个简单的映射?
    • 您可以通过使用内联映射类型来消除对索引签名的需要。所以分别是T extends {[K in keyof T]: () =&gt; any}R extends {[S in keyof R]: {[K in keyof R[S]]: () =&gt; any}}。但随后打字稿指出了另一个问题,即:属性'getCurrentUser'的类型不兼容,类型(state: IUsersState) =&gt; IUser | undefined不可分配给() =&gt; any。您可以通过使用(...args: any[]) =&gt; any 而不是() =&gt; any 来解决这个问题,但我不确定这对于您的用例是否过于允许。 LMK。
    • 成功了!在globalState 之后,我确实丢失了所有参数的输入,但除非你有任何解决这个问题的想法,否则这仍然比不输入要好得多。谢谢!
    猜你喜欢
    • 2017-09-24
    • 1970-01-01
    • 1970-01-01
    • 2021-08-02
    • 2017-09-10
    • 2018-01-27
    • 2020-05-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多