我正在使用Redux Toolkit创建通用切片来更新嵌套的initialState
对象,但不幸的是,由于我缺乏一些高级TS知识,我无法找到实现这一目标的方法。下面是我另一次尝试的代码片段:
TS Playground link
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
export enum FIELD_ID {
FAX = 'fax',
HOME = 'homePhone',
WORK = 'workPhone',
CONTACT = 'contact',
OTHER = 'other'
}
type GenericFetchState<Data> = {
data: Data
isLoading: boolean
error?: {
name?: string
message?: string
code?: string
stack?: string
}
dataDidInvalidate: boolean
formId?: string
}
type DefaultState = {
value: any // We can leave any, not necessary for this issue
isDirty: boolean
isValid: boolean
error: string
}
export type Form = {
[FIELD_ID.CONTACT]: {
[FIELD_ID.HOME]: DefaultState
[FIELD_ID.WORK]: DefaultState
}
[FIELD_ID.OTHER]: {
[FIELD_ID.FAX]: DefaultState
}
}
type InitialState = GenericFetchState<Form>
export const DEFAULT_INPUT_STATE: DefaultState = {
value: '',
isDirty: false,
isValid: true,
error: ''
} as const
export const initialState: InitialState = {
data: {
[FIELD_ID.CONTACT]: {
[FIELD_ID.HOME]: DEFAULT_INPUT_STATE,
[FIELD_ID.WORK]: DEFAULT_INPUT_STATE
},
[FIELD_ID.OTHER]: {
[FIELD_ID.FAX]: DEFAULT_INPUT_STATE
}
},
dataDidInvalidate: true,
isLoading: false,
error: null,
formId: null
}
type SectionKeys = keyof Form
type VarNameMap<T extends SectionKeys> = Form[T]
// I've ended up with creation of Map of section/varName pairs, to let Typescript know the deeper nesting types, but without a success.
type SectionVarNamesMap = {
[K in SectionKeys]: {
[J in keyof VarNameMap<K>]: [section: K, varName: J] //Eg. [section: FIELD_ID.CONTACT, varName: FIELD_ID.HOME]
}[keyof VarNameMap<K>]
}[SectionKeys]
type PayloadObject<T extends SectionVarNamesMap> = { // This is what I am struggling with
section: T[0]
varName: T[1]
value: any // value property doesn't matter for this example
error?: string
}
const formSlice = createSlice({
name: 'form',
initialState,
reducers: {
updateValue: (state, action: PayloadAction<PayloadObject<SectionVarNamesMap>>) => {
switch (action.payload.section) {
case FIELD_ID.CONTACT: {
const varName = action.payload.varName // It should be narrowed here by switch case, expected output: FIELD_ID.HOME | FIELD_ID.WORK
// previously I did this, but I have a feeling that's not the right way:
// const varName = action.payload.varName as keyof Form[FIELD_ID.CONTACT]
const oldValue = state.data[action.payload.section][varName].value // Error : Property '[FIELD_ID.FAX]' does not exist on type 'WritableDraft<{ homePhone: DefaultState; workPhone: DefaultState; }>'.ts(7053)
state.data[action.payload.section][varName] = {
value: action.payload.value ?? oldValue,
isDirty: true,
isValid: !action.payload.error,
error: !!action.payload.error ? action.payload.error : ''
}
break
}
case FIELD_ID.OTHER: {
const oldValue = state.data[action.payload.section][action.payload.varName].value // It should be narrowed here by switch case, expected output: FIELD_ID.FAX
state.data[action.payload.section][action.payload.varName] = {
value: action.payload.value ?? oldValue,
isDirty: true,
isValid: !action.payload.error,
error: !!action.payload.error ? action.payload.error : ''
}
break
}
default: {
break
}
}
}
}
})
export const { updateValue } = formSlice.actions
export default formSlice.reducer
字符串
现在我已经在每个switch case中使用了as
关键字来明确地告诉TS它应该期望什么类型,但我觉得这是一种错误的方式。
任何帮助是热烈欢迎的,谢谢!
1条答案
按热度按时间ig9co6j11#
我其实已经接近解决方案了,它是这样的:
字符串
TS Playground Link