Обернуть вложенный объект функций в TypeScript? - PullRequest
0 голосов
/ 18 мая 2018

Я использую Redux и разбил данные магазина на «кусочки».Каждый «срез» соответствует области, такой как users или comments.Я объединяю селекторы для каждого из этих срезов в один модуль 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 голосов
/ 22 мая 2018

Необходимо добавить аналогичное ограничение типа к универсальному типу как для IScopedSelectors, так и для INestedScopedSelectors.

Без такого ограничения вы сказали TypeScript, что T в IScopedSelectors<T> может быть любого типа. Так что IScopedSelectors<string> должно быть в силе. Но TypeScript справедливо указывает на то, что для многих типов (таких как string) [K in keyof T]: IScopedSelector<T[K]> не будет работать, поскольку нет гарантии, что 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 на что-то более конкретное в зависимости от вашего конкретного случая использования, но решение по сути то же самое. Просто убедитесь, что вы продвигаете свое ограничение по иерархии.

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...