You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何减少类型转换并修复Object.keys.reduce的TypeScript类型错误

React表单状态转POST请求体的TypeScript类型错误解决

问题背景

我有一个React表单组件,会将表单值传入函数,把表单状态中的所有值格式化为POST请求体所需的结构,但当前代码存在TypeScript类型错误,无法正常编译。

最简可复现代码

import type { AxiosError } from 'axios';

export interface OrgAddress {
  line1: string
  postCode: string
  country: string
  city: string
  name: string
  comment: string
}
export type SecurityRole = 'superadmin' | 'protectedadmin' | 'admin' | 'manager'

export interface User {
  id: string
  username: string
  firstName: string
  lastName: string
  securityRole: SecurityRole
  organisationId: Organisation['id']
}

export type OrgAdminUser = Pick<User, 'firstName' | 'lastName' | 'username'>

export interface Organisation {
  id: string
  name: string
  contactNumber: string
  category: string
  registeredCharityNumber: string
  address: OrgAddress
  adminUser: OrgAdminUser
  createdAt: string
  updatedAt: string
}

// 补全缺失的PostOrgBody类型,对应POST请求体结构
type PostOrgBody = Omit<Organisation, 'id' | 'createdAt' | 'updatedAt'>

export type NestedKey = 'address' | 'adminUser'
export interface StatePropObj {
  value: string
  error: string
  nestedKey: NestedKey | boolean
  touched: boolean
  hasError: boolean
}

export interface FormState {
  name: StatePropObj
  registeredCharityNumber: StatePropObj
  contactNumber: StatePropObj
  category: StatePropObj
  address: Address
  adminUser: AdminUser
  isFormValid: boolean
  submitErrors: AxiosError | false
}

export interface Address {
  line1: StatePropObj
  postCode: StatePropObj
  country: StatePropObj
  city: StatePropObj
  name: StatePropObj
  comment: StatePropObj
  nested: boolean
}

export interface AdminUser {
  firstName: StatePropObj
  lastName: StatePropObj
  email: StatePropObj // 注意:这里对应OrgAdminUser的username字段
  nested: boolean
}

// 原存在类型错误的转换函数
const getFormPayload = (formState: FormState) => {
  return Object.keys(formState).reduce((acc, key: keyof typeof formState) => {
    const formStateProp = formState[key]
    if (typeof formStateProp === 'boolean') return acc
    if (formStateProp && 'nested' in formStateProp) {
      acc[key] = Object.keys(formStateProp).reduce(
        (nestedAcc, nestedKey: keyof typeof formStateProp) => {
          const prop = formStateProp[nestedKey]
          if (typeof prop === 'boolean') return nestedAcc
          if ('value' in prop) {
            nestedAcc[nestedKey] = prop.value
            return nestedAcc
          }
          return nestedAcc
        },
        {} as OrgAddress | OrgAdminUser
      )
    }
    if (formStateProp && 'value' in formStateProp) {
      acc[key] = formStateProp.value
    }
    return acc
  }, {} as PostOrgBody)
}

核心困惑

reduce方法要返回PostOrgBody类型的对象,但遍历的是FormState的键。二者键名大部分重合,但TypeScript不允许用FormState的键直接给PostOrgBody类型的累加器赋值,同时嵌套结构的联合类型也导致推断模糊。

错误原因

  1. 键类型不精确:Object.keys()返回的是string[],TypeScript无法确认这些字符串是PostOrgBody的有效键,直接赋值会触发类型不兼容错误。
  2. 嵌套类型推断模糊:嵌套reduce的累加器用OrgAddress | OrgAdminUser联合类型,TypeScript无法确定当前处理的是哪种结构,导致赋值时类型校验失败。
  3. 字段名不匹配:FormState中adminUser的email字段对应PostOrgBody里的username,原代码未处理这个映射,导致类型不匹配。

修复后的代码

const getFormPayload = (formState: FormState): PostOrgBody => {
  // 手动指定需要转换的键,排除不需要的表单状态字段
  const transformableKeys: Array<keyof Omit<FormState, 'isFormValid' | 'submitErrors'>> = [
    'name',
    'registeredCharityNumber',
    'contactNumber',
    'category',
    'address',
    'adminUser'
  ];

  return transformableKeys.reduce((acc, key) => {
    const formProp = formState[key];

    // 处理顶层非嵌套字段
    if ('value' in formProp) {
      acc[key] = formProp.value as PostOrgBody[typeof key];
      return acc;
    }

    // 处理嵌套字段:区分address和adminUser的类型
    if ('nested' in formProp) {
      type TargetNestedType = typeof key extends 'address' ? OrgAddress : OrgAdminUser;
      
      const nestedPayload = Object.keys(formProp).reduce((nestedAcc, nestedKey) => {
        const nestedProp = formProp[nestedKey as keyof typeof formProp];
        if ('value' in nestedProp) {
          // 处理adminUser的email映射到username的逻辑
          const targetField = key === 'adminUser' && nestedKey === 'email' 
            ? 'username' 
            : nestedKey;
          
          (nestedAcc as Record<string, string>)[targetField] = nestedProp.value;
        }
        return nestedAcc;
      }, {} as TargetNestedType);

      acc[key] = nestedPayload as PostOrgBody[typeof key];
      return acc;
    }

    return acc;
  }, {} as PostOrgBody);
};

解决思路

  1. 精确控制遍历键:手动列出需要转换的键,避免遍历到isFormValid、submitErrors这类不需要的字段,同时保证键的类型是PostOrgBody的有效键子集。
  2. 分支处理嵌套类型:根据外层键(address/adminUser)分别指定嵌套累加器的具体类型,避免联合类型带来的推断模糊。
  3. 字段映射处理:针对adminUser中email到username的字段名差异,手动添加映射逻辑,确保最终结构符合PostOrgBody要求。
  4. 类型断言辅助:在安全的场景下使用类型断言,帮助TypeScript完成类型推断,同时保证代码逻辑的正确性。

内容的提问来源于stack exchange,提问作者HelloWorld

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 04:50:29