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

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>
    );
}

尝试过的方案及报错

  1. 传递接收Partial<TicketRequestDTO>参数的更新函数:
// 父组件定义的更新函数
const updateNewTicket = ( updatedProperty: Partial<TicketRequestDTO>): void => {
   setNewTicket({ ...newTicket, ...updatedProperty });
}

// 子组件调用(无效)
updateNewTicket({ propertyToUpdate: 'some value' });
  • 问题:TypeScript无法识别propertyToUpdate是TicketRequestDTO的有效键,导致类型不匹配。
  1. 直接索引访问并断言类型:
  • 使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 18:40:38