React TS中如何从孙组件更新父组件的单个对象属性?
React+TypeScript跨多层组件更新父组件State属性的类型安全方案
问题背景
父组件NewTicketView的State中存储了TicketRequestDTO类型的对象newTicket,需要从孙组件SelectBox中更新该对象的单个属性,但TypeScript报错严重,核心问题是字符串无法作为索引访问TicketRequestDTO类型。
相关组件代码
// 父组件 NewTicketView const NewTicketView: React.FC = () => { const [newTicket, setNewTicket] = useState<TicketRequestDTO>({ title: '', description: '', priority: '', userId: -1, classificationId: -1, areaId: -1 }); return ( <Grid> <TitleArea /> <OptionsArea newTicketState={[newTicket, setNewTicket]} /> <DescriptionArea /> <ButtonsArea /> </Grid> ); } // 子组件 OptionsArea const OptionsArea: React.FC<UpdateNewTicket> = ({ newTicketState }) => { return ( <Container> <Wrapper> <InputComponentSmall title={REACT_APP_TITLE} placeholder={REACT_APP_TITLE} /> <SelectBox options={prioritiesArray} newTicketState={newTicketState} propertyToUpdate={'priority'} /> <SelectBox options={data?.classification} newTicketState={newTicketState} propertyToUpdate={'classificationId'} /> <SelectBox options={data?.area} newTicketState={newTicketState} propertyToUpdate={'areaId'} /> </Wrapper> </Container> ); } // 孙组件 SelectBox const SelectBox: React.FC<SelectBoxWithUpdate> = ({ title, renderColorBar, options, newTicketState, propertyToUpdate}) => { const [selectedOption, setSelectedOption] = useState("Low"); const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const index = e.target.selectedIndex; const value = e.target[index].innerText; setSelectedOption(value); if(newTicketState !== undefined) { const [newTicket, setNewTicket] = newTicketState; if(propertyToUpdate !== undefined) { // 尝试更新但报错的位置 newTicket[propertyToUpdate] = 'some value coming from select box'; setNewTicket(newTicket); } } } return ( <Select> <Wrapper> <span>{title}</span> {renderColorBar && <ColorBar priorityStatus={selectedOption} />} </Wrapper> <select id={title} onChange={handleChange}> { options && options.map((option, idx) => { return ( <option key={idx} value={option.id ?? idx}>{option.description}</option> ); }) } </select> </Select> ); }
尝试过的方案及报错
- 传递接收
Partial<TicketRequestDTO>参数的更新函数:
// 父组件定义的更新函数 const updateNewTicket = ( updatedProperty: Partial<TicketRequestDTO>): void => { setNewTicket({ ...newTicket, ...updatedProperty }); } // 子组件调用(无效) updateNewTicket({ propertyToUpdate: 'some value' });
- 问题:TypeScript无法识别
propertyToUpdate是TicketRequestDTO的有效键,导致类型不匹配。
- 直接索引访问并断言类型:
- 使用
newTicket[propertyToUpdate as keyof typeof newTicket] = 'some value'时,报错:Type 'string' is not assignable to type 'never' - 直接使用
newTicket[propertyToUpdate] = 'some value'时,报错:Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'TicketRequestDTO'. No index signature with a parameter of type 'string' was found on type 'TicketRequestDTO'
解决方法
1. 定义类型安全的更新函数(父组件)
不要直接传递[newTicket, setNewTicket],而是传递一个类型约束严格的更新函数,确保键和值的类型匹配:
const NewTicketView: React.FC = () => { const [newTicket, setNewTicket] = useState<TicketRequestDTO>({ title: '', description: '', priority: '', userId: -1, classificationId: -1, areaId: -1 }); // 类型安全的更新函数:K约束为TicketRequestDTO的键,值的类型必须匹配对应键的类型 const updateNewTicket = <K extends keyof TicketRequestDTO>(key: K, value: TicketRequestDTO[K]) => { setNewTicket(prev => ({ ...prev, [key]: value })); }; return ( <Grid> <TitleArea /> <OptionsArea updateNewTicket={updateNewTicket} /> <DescriptionArea /> <ButtonsArea /> </Grid> ); }
2. 传递更新函数到孙组件(中间组件)
修改OptionsArea的props类型,接收更新函数并传递给SelectBox:
// 定义OptionsArea的props类型 type OptionsAreaProps = { updateNewTicket: <K extends keyof TicketRequestDTO>(key: K, value: TicketRequestDTO[K]) => void; }; const OptionsArea: React.FC<OptionsAreaProps> = ({ updateNewTicket }) => { return ( <Container> <Wrapper> <InputComponentSmall title={REACT_APP_TITLE} placeholder={REACT_APP_TITLE} /> <SelectBox title="Priority" renderColorBar={true} options={prioritiesArray} updateNewTicket={updateNewTicket} propertyToUpdate="priority" /> <SelectBox title="Classification" options={data?.classification} updateNewTicket={updateNewTicket} propertyToUpdate="classificationId" /> <SelectBox title="Area" options={data?.area} updateNewTicket={updateNewTicket} propertyToUpdate="areaId" /> </Wrapper> </Container> ); }
3. 修复孙组件的类型和更新逻辑
修改SelectBox的props类型,约束propertyToUpdate为TicketRequestDTO的键,并根据属性类型处理选中值:
// 定义SelectBox的props类型 type SelectBoxProps = { title?: string; renderColorBar?: boolean; options?: Array<{ id?: number; description: string }>; updateNewTicket: <K extends keyof TicketRequestDTO>(key: K, value: TicketRequestDTO[K]) => void; propertyToUpdate: keyof TicketRequestDTO; }; const SelectBox: React.FC<SelectBoxProps> = ({ title, renderColorBar, options, updateNewTicket, propertyToUpdate}) => { const [selectedOption, setSelectedOption] = useState("Low"); const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const selectedIdx = e.target.selectedIndex; const selectedText = e.target[selectedIdx].innerText; const selectedValue = e.target.value; setSelectedOption(selectedText); // 根据属性类型处理值:number类型的属性转成数字,string类型直接用文本 let valueToUpdate: TicketRequestDTO[keyof TicketRequestDTO]; if (propertyToUpdate === 'userId' || propertyToUpdate === 'classificationId' || propertyToUpdate === 'areaId') { valueToUpdate = Number(selectedValue); } else { valueToUpdate = selectedText; } // 调用类型安全的更新函数 updateNewTicket(propertyToUpdate, valueToUpdate); } return ( <Select> <Wrapper> <span>{title}</span> {renderColorBar && <ColorBar priorityStatus={selectedOption} />} </Wrapper> <select id={title} onChange={handleChange}> { options && options.map((option, idx) => ( <option key={idx} value={option.id ?? idx}>{option.description}</option> )) } </select> </Select> ); }
核心原理说明
- 通过泛型
K extends keyof TicketRequestDTO约束更新的键必须是TicketRequestDTO的有效属性,解决字符串索引的类型问题。 - 确保更新的值类型与目标属性的类型严格匹配(number类型属性转数字,string类型用文本),避免
Type 'string' is not assignable to type 'never'的报错。 - 使用函数式更新
setNewTicket(prev => ({ ...prev, [key]: value })),避免直接修改state对象,符合React的不可变更新原则。
内容的提问来源于stack exchange,提问作者Ramon Saviato
相关产品推荐
相关产品推荐

