TypeScript对state对象使用展开运算符报ts(2461)类型错误
问题根因
两个TS报错和运行时错误的核心原因完全一致:展开运算符...仅在两种场景下合法:一是在{}包裹的对象字面量中展开普通对象做属性拷贝,二是在数组/函数参数位展开实现了可迭代接口(Iterable)的值(比如数组、字符串、Map、Set)。普通对象没有内置迭代器,直接在函数参数位(比如你写的console.log参数位)展开普通对象时,JS会尝试遍历迭代器,自然会抛出「不可迭代」的运行时错误,TS也会提前报类型不匹配的错。
对应代码的具体问题点:
- 你写的三个TS接口本身没有语法错误,编辑器不报接口相关提示是正常的。从接口定义就能看到
courseQuiz是普通对象结构,不是数组类型:
不管是interface innerQuiz1 { question: string; sectionId: number; attachmentId: number; options: string[]; answers: string[]; } interface innerQuiz { attempts: string; passing: string; quiz: innerQuiz1[]; quizTitle: string; } interface courseQuiz { quiz: innerQuiz[]; passing: number; attempts: number; quizTitle: string; }useState声明的courseQuiz | undefined类型,还是courseQuizCheck()返回的联合类型,本质都是普通对象,不满足可迭代要求,不能直接在参数位展开。 - 你之前在
setCourseQuiz里写的...e没有报错,是因为该展开逻辑写在{}包裹的对象字面量内部,属于合法的对象属性拷贝语法,不依赖迭代器接口。 - 现有
courseQuizCheck存在类型不匹配问题:接口定义中passing、attempts字段为number类型,但兜底返回值给的是null,TS会自动推导返回值为联合类型,后续使用时会持续产生类型提示问题。
修复方案
- 移除错误的直接展开普通对象的写法。如果只是需要打印对象内容,直接将对象作为
console.log的参数传入即可,console.log原生支持打印普通对象:const getCourseFromForm = () => { console.log("1.from getCourseFromForm", courseQuiz); console.log( "2.from getCourseFromForm after running the coursequizCheck function", courseQuizCheck() ); // 删除以下两行错误代码 // console.log("4.coursequiz is spreded", ...courseQuiz); // console.log( // "3.from getCourseFromForm after spreading the coursequizCheckfunction", // ...courseQuizCheck() // ); } - 如果是需要拷贝/合并
courseQuiz的属性,将展开逻辑放在{}包裹的对象字面量内部即可,这是合法的对象展开写法:const courseQuizCopy = { ...courseQuizCheck(), // 此处可新增/覆盖属性 }; - 修复
courseQuizCheck的类型不一致问题,二选一即可:- 调整接口定义,让字段支持null类型,匹配兜底返回值
interface courseQuiz { quiz: innerQuiz[]; passing: number | null; attempts: number | null; quizTitle: string | null; }- 调整兜底返回值,匹配接口的类型要求,同时给函数加明确的返回类型标注
const courseQuizCheck = (): courseQuiz => { console.log("From course quiz check", courseQuiz); if (courseQuiz) return courseQuiz; return { quiz: [], passing: 0, attempts: 0, quizTitle: "", }; }; - 更推荐直接给
useState传入符合类型要求的初始值,从根源上消除undefined类型,后续可以直接使用courseQuiz变量,不需要额外写判空兜底函数:const [courseQuiz, setCourseQuiz] = useState<courseQuiz>({ quiz: [], passing: 0, attempts: 0, quizTitle: "", });
特殊场景说明:如果确实需要把对象的属性值按顺序作为独立参数传入函数,可以先用
Object.values(目标对象)拿到属性值组成的数组,再展开这个数组,但这类场景极少,常规业务开发几乎不会用到。
内容的提问来源于stack exchange,提问作者Susant Patel
相关产品推荐
相关产品推荐

