React TypeScript中useReducer可选Action Key引发类型不兼容问题
问题原因
你定义的action类型是{ type: string; nextName?: string },其中nextName是可选字段。但在update_name分支中,你直接将action.nextName赋值给state的name字段(state的name是必填string类型)。
TypeScript无法通过action.type === "update_name"这个条件,推断出此时nextName一定存在——因为当前的action类型允许所有type为string的对象都可以不带nextName。所以TypeScript会认为action.nextName可能是undefined,导致返回的state中name类型变成string | undefined,和原state的{ name: string; age: number }类型不兼容,触发报错。
当你把nextName设为必填时,TypeScript能确定action.nextName一定是string,返回的state类型就和原state匹配,自然不会报错。
解决方法
方法1:用联合类型定义Action(推荐,类型最安全)
给不同类型的action定义专属的结构,让TypeScript能根据type字段做类型窄化,明确每个action对应的必填字段:
import React from "react"; import logo from "./logo.svg"; import "./App.css"; import { useReducer } from "react"; // 定义联合类型的Action type ProfileAction = | { type: "update_name"; nextName: string } | { type: "increment" } | { type: "decrement" }; function profileReducer( state: { name: string; age: number }, action: ProfileAction // 使用联合类型 ) { switch (action.type) { case "update_name": { // 此时TypeScript明确知道action.nextName是string return { name: action.nextName, age: state.age }; } case "increment": { return { name: state.name, age: state.age + 1 }; } case "decrement": { return { name: state.name, age: state.age - 1 }; } } throw Error("Unknown Action !!!"); } function App() { function handleTextChange(e: React.ChangeEvent<HTMLInputElement>) { dispatch({ type: "update_name", nextName: e.target.value }); } function handleAgeChange(shouldIncrement: boolean) { shouldIncrement ? dispatch({ type: "increment" }) : dispatch({ type: "decrement" }); } const [state, dispatch] = useReducer(profileReducer, { name: "", age: 42 }); return ( <div className="App"> <p /> Name: <input type="text" value={state.name} onChange={handleTextChange} /> <p /> Age: {state.age} <p /> <button onClick={() => handleAgeChange(true)}>+</button> <button onClick={() => handleAgeChange(false)}>-</button> </div> ); } export default App;
方法2:兜底或非空断言(应急用,不推荐)
如果不想修改action类型,可以在update_name分支给action.nextName加兜底值,或者用非空断言告诉TypeScript它一定存在:
// 兜底写法(推荐,避免运行时问题) case "update_name": { return { name: action.nextName ?? state.name, age: state.age }; } // 非空断言写法(仅确定不会传undefined时使用) case "update_name": { return { name: action.nextName!, age: state.age }; }
这种方法虽然能解决类型报错,但无法从类型层面约束update_name类型的action必须携带nextName,存在运行时风险(比如如果不小心dispatch了不带nextName的update_name action,会导致name变成undefined)。
内容的提问来源于stack exchange,提问作者Saran

