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)
では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]
コメントを追加