Typescript:関数のマップを異なるタイプパラメータを持つ同様の関数に変換する関数を厳密に型指定するにはどうすればよいですか?

ジェームズネイル

StateSliceのキーに基づいてStateSliceタイプの代わりにタイプglobalizeSelectorsを受け入れるようにreduxセレクター関数のマップを変換する関数を強く型付けしようとしGlobalStateています(StateSliceはGlobalStateの1つの値であることを意味します)オブジェクトのプロパティ)。

トリッキーな部分は、セレクターの戻り値の型がすべて異なる可能性があることです。そのバリエーションを入力する方法がよくわかりません(または可能かどうかさえわかりません)。typescriptのドキュメントに基づくと、これにはinfer演算子の巧妙な使用が含まれる可能性があると思いますが、私のtypescript-fuはまだそのレベルではありません。

これが私がこれまでに得たものです:(ところで、あなたの冗長なタイプのために、これらのセレクターが小道具や追加の引数を処理しないという事実を気にしないでください-これを少し単純化するためにそれを削除しました)

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)
Titian Cernicova-Dragomir

ではglobalizeSelectorsタイプsliceSelectorsです{[key: string]: SliceSelector<TKey, T> }しかし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です。これらのタイプを処理するための設備が整っている思いません

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

Related 関連記事

ホットタグ

アーカイブ