reactjs 创建通用redux切片以更新嵌套的initialState对象

qxsslcnc  于 12个月前  发布在  React
关注(0)|答案(1)|浏览(103)

我正在使用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它应该期望什么类型,但我觉得这是一种错误的方式。
任何帮助是热烈欢迎的,谢谢!

ig9co6j1

ig9co6j11#

我其实已经接近解决方案了,它是这样的:

type SectionVarNamesMap = {
  [K in SectionKeys]: {
    [J in keyof VarNameMap<K>]: [section: K, varName: J]  
  }[keyof VarNameMap<K>]
} // ! Removed [SectionKeys] from here

type PayloadObject<T extends SectionVarNamesMap> = { 
 [K in SectionKeys]: {
  section: T[K][0]
  varName: T[K][1]
  value: any 
  error?: string
 }
}[SectionKeys] // Added [SectionKeys] here

字符串
TS Playground Link

相关问题