Typescript: Как строго набрать функцию, которая преобразует карту функций в аналогичные функции с другим типом param? - PullRequest
0 голосов
/ 29 ноября 2018

Я пытаюсь строго набрать функцию globalizeSelectors, которая преобразует карту функций селектора избыточности так, что они примут тип GlobalState вместо своего типа StateSlice на основе ключа их StateSlice (где StateSliceозначает, что это значение одного из свойств объекта GlobalState).

Хитрость в том, что все типы возвращаемых значений селекторов могут быть разными, и я не совсем знаю, как набирать этот вариант (или еслиэто даже возможно).Основываясь на документах по машинописи, я предполагаю, что это может включать в себя некоторое умное использование оператора infer, но мое машинописное фу пока не совсем на этом уровне.

Вот что у меня получилосьдалеко: (Кстати, для типов с редукторами, не обращайте внимания на тот факт, что эти селекторы не обрабатывают реквизиты или дополнительные аргументы - я убрал это, чтобы упростить это немного)

import { mapValues } from 'lodash'

// my (fake) redux state types
type SliceAState = { name: string }
type SliceBState = { isWhatever: boolean }

type GlobalState = {
  a: SliceAState;
  b: SliceBState;
}

type StateKey = keyof GlobalState

type Selector<TState, TResult> = (state: TState) => TResult

type StateSlice<TKey extends StateKey> = GlobalState[TKey]

type GlobalizedSelector<TResult> = Selector<GlobalState, TResult>

const globalizeSelector = <TKey extends StateKey, Result>(
  sliceKey: TKey,
  sliceSelector: Selector<StateSlice<TKey>, Result>
): GlobalizedSelector<Result> => state => sliceSelector(state[sliceKey])

// an example of a map of selectors as they might be exported from their source file
const sliceASelectors = {
  getName: (state: SliceAState): string => state.name,
  getNameLength: (state: SliceAState): number => state.name.length
}

// fake global state
const globalState: GlobalState = {
  a: { name: 'My Name' },
  b: { isWhatever: true }
}

// so this works...
const globalizedGetName = globalizeSelector('a', sliceASelectors.getName)
const globalizedNameResult: string = globalizedGetName(globalState)

const globalizedGetNameLength = globalizeSelector(
  'a',
  sliceASelectors.getNameLength
)
const globalizedNameLengthResult: number = globalizedGetNameLength(globalState)

/* but when I try to transform the map to globalize all its selectors, 
   I get type errors (although the implementation works as untyped
   javascript):
*/
type SliceSelector<TKey extends StateKey, T> = T extends Selector<
  StateSlice<TKey>,
  infer R
>
  ? Selector<StateSlice<TKey>, R>
  : never

const globalizeSelectors = <TKey extends StateKey, T>(
  sliceKey: TKey,
  sliceSelectors: {
    [key: string]: SliceSelector<TKey, T>;
  }
) => mapValues(sliceSelectors, s => globalizeSelector(sliceKey, s))

const globalized = globalizeSelectors('a', sliceASelectors)
/*_________________________________________^ TS Error:
Argument of type '{ getName: (state: SliceAState) => string; getNameLength: (state: SliceAState) => number; }' is not assignable to parameter of type '{ [key: string]: never; }'.
  Property 'getName' is incompatible with index signature.
    Type '(state: SliceAState) => string' is not assignable to type 'never'. [2345]
*/
const globalizedGetName2: string = globalized.getName(globalState)

1 Ответ

0 голосов
/ 04 декабря 2018

В globalizeSelectors тип sliceSelectors равен {[key: string]: SliceSelector<TKey, T> }.Но кто должен быть в этом случае?В вашей простой версии T будет типом возврата этого конкретного селектора слайса, но при отображении нескольких T не может быть всех типов возврата.

Решение, которое я бы использовал, состоит в том, чтобы использовать T для повторного представления всего типа sliceSelectors с тем ограничением, что все члены должны быть типа SliceSelector<TKey, any>.any там просто означает, что нам все равно, какой тип возврата у селекторов срезов.

Несмотря на то, что нам не важно, какой тип возврата у каждого селектора срезов, T будет захватыватьактуальный тип (то есть типы возврата каждой функции в объекте будут не any, а фактический тип).Затем мы можем использовать T для создания сопоставленного типа, который глобализирует каждую функцию в объекте.

import { mapValues } from 'lodash'

// my (fake) redux state types
type SliceAState = { name: string }
type SliceBState = { isWhatever: boolean }

type GlobalState = {
  a: SliceAState;
  b: SliceBState;
}

type StateKey = keyof GlobalState


type GlobalizedSelector<TResult> = Selector<GlobalState, TResult>

const globalizeSelector = <TKey extends StateKey, Result>(
  sliceKey: TKey,
  sliceSelector: Selector<StateSlice<TKey>, Result>
): GlobalizedSelector<Result> => state => sliceSelector(state[sliceKey])

// an example of a map of selectors as they might be exported from their source file
const sliceASelectors = {
  getName: (state: SliceAState): string => state.name,
  getNameLength: (state: SliceAState): number => state.name.length
}

// fake global state
const globalState: GlobalState = {
  a: { name: 'My Name' },
  b: { isWhatever: true }
}

type Selector<TState, TResult> = (state: TState) => TResult

type StateSlice<TKey extends StateKey> = GlobalState[TKey]

// Simplified selctor, not sure what the conditional type here was trying to achive
type SliceSelector<TKey extends StateKey, TResult> = Selector<StateSlice<TKey>, TResult>

const globalizeSelectors = <TKey extends StateKey, T extends {
    [P in keyof T]: SliceSelector<TKey, any>
  }>(
  sliceKey: TKey,
  sliceSelectors: T
) : { [P in keyof T]: GlobalizedSelector<ReturnType<T[P]>> } => mapValues(sliceSelectors, s => globalizeSelector(sliceKey, s as any)) as any // Not sure about mapValues 

const globalized = globalizeSelectors('a', sliceASelectors)

const globalizedGetName2: string = globalized.getName(globalState)

Единственная небольшая проблема заключается в том, что для работы mapValues требуются утверждения некоторых типов, я не думаю, что mapValues оборудован для работы с этими типами.

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