为何React setState有时需扩展现有状态,有时无需?
核心原因:TypeScript对对象类型的推断差异
第一个场景(LightsOutGameState):明确指定已知属性
当你写this.setState({ grid: newGrid })时,TypeScript能直接识别出grid是LightsOutGameState的已知合法属性,因此这个对象会被推断为Pick<LightsOutGameState, 'grid'>类型——这完全符合React setState允许的「部分状态更新」类型要求,所以不会报错。哪怕newGrid是全新生成的数组,只要它的类型匹配boolean[][],TypeScript就会认可这个更新对象的合法性。
第二个场景(BoxState):计算属性的类型模糊
而使用[evt.target.name]: evt.target.value时,TypeScript默认会把evt.target.name推断为string类型(而非keyof BoxState),这就导致生成的对象类型是{ [x: string]: any }。这个类型和BoxState的部分更新类型(Pick<BoxState, keyof BoxState>)不兼容,因此触发了类型错误。
当你加上...this.state后,整个对象的类型被「锚定」为BoxState(因为你扩展了完整的状态对象,再覆盖单个属性),TypeScript会认为你是在修改已有状态的某个属性,而非创建一个任意键的对象,所以错误消失。
另一种解决方案:不需要复制整个状态
其实你不用每次都扩展this.state,只要让TypeScript确定evt.target.name是BoxState的合法键即可,比如通过类型断言:
handleInputChange(evt: React.ChangeEvent<HTMLInputElement>) { const fieldName = evt.target.name as keyof BoxState; this.setState({ [fieldName]: evt.target.value, }); }
这样TypeScript就能识别出fieldName是BoxState的属性之一,生成的对象类型也会匹配合法的部分更新类型,同样可以解决问题。
内容的提问来源于stack exchange,提问作者eephyne

