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

使用扩展运算符结合嵌套对象与useReducer hook时遇TypeScript类型错误

嵌套对象状态在useReducer中的TypeScript类型错误问题

状态类型定义

interface name {
  firstName: string;
  lastName: string;
}
type NameType = name;

interface employer {
  name: string;
  state: string;
}
type EmployerType = employer;

interface person {
  name: NameType;
  age: number;
  employer: EmployerType;
}
type PersonType = person;

const defaultPerson: PersonType = {
  name: {
    firstName: "The",
    lastName: "Rock"
  },
  age: 25,
  employer: {
    name: "Noone",
    state: "Nowhere"
  }
};

useState中更新嵌套对象正常工作

在useState里使用二级扩展运算符更新嵌套对象完全正常:

export default function App() {
  const [person, setPerson] = useState<PersonType>(defaultPerson);
  function handleInputChange(input: string) {
    setPerson({
      ...person,
      name: {
        ...person.name,
        firstName: input
      }
    });
  }

  return (
    <div className="App">
      <input onChange={(e) => handleInputChange(e.target.value)} />
      <h2>{JSON.stringify(person, null, 4)}</h2>
    </div>
  );
}

useReducer中相同操作触发TypeScript错误

但在useReducer里用同样的扩展运算符更新时,TypeScript会抛出类型错误:

interface action {
  type: string;
  fieldName: string;
  value: string | number;
}
type ActionType = action;

function reducer(state: PersonType, action: ActionType) {
  switch (action.type) {
    case "firstName": {
      return {
        ...state,
        name: {
          ...state.name,
          firstName: action.value
        }
      };
    }
  }
  return state;
}

export default function App() {
  const [person, dispatch] = useReducer(reducer, defaultPerson);

  return (
    <div className="App">
      <input
        onChange={(e) =>
          dispatch({
            type: "test",
            fieldName: "firstName",
            value: e.target.value
          })
        }
      />
      <h2>{JSON.stringify(person, null, 4)}</h2>
    </div>
  );
}

临时解决方法(非最优)

目前可以通过分步复制状态的方式临时绕过错误,但这并非最优实现:

const newState = {...state}
const newName = {...newState.name}
newName.firstName = action.value
newState.name = newName
return newState

问题根源与正确解决思路

问题出在ActionType的类型定义:action.value被宽泛定义为string | number,在firstName分支中,TypeScript无法确认此时的value一定是符合firstName要求的string类型,因此抛出类型不匹配错误。

正确解决方案:使用可辨识联合类型定义Action

通过给Action定义可辨识联合类型,让TypeScript能根据action.type推断出对应value的具体类型:

// 为每个字段的更新动作定义专属类型,type作为辨识符关联value类型
type ActionType = 
  | { type: "firstName"; value: string }
  | { type: "lastName"; value: string }
  | { type: "age"; value: number }
  | { type: "employerName"; value: string }
  | { type: "employerState"; value: string };

function reducer(state: PersonType, action: ActionType) {
  switch (action.type) {
    case "firstName": {
      return {
        ...state,
        name: {
          ...state.name,
          firstName: action.value // 此时TypeScript明确value是string,类型完全匹配
        }
      };
    }
    case "lastName": {
      return {
        ...state,
        name: {
          ...state.name,
          lastName: action.value
        }
      };
    }
    case "age": {
      return {
        ...state,
        age: action.value
      };
    }
    // 其他字段的更新分支同理
    default:
      return state;
  }
}

修改后,TypeScript能准确识别每个分支中action.value的类型,使用扩展运算符更新嵌套对象时就不会再报错,同时类型检查也能正常生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 13:55:20