реквизит перезаписывается при изменении состояния?

#javascript #reactjs #typescript #react-hooks

#javascript #reactjs #typescript #реагирующие хуки

Вопрос:

Я столкнулся со странной ошибкой в своем коде, и я изо всех сил пытаюсь понять причину.

В двух словах, похоже, что реквизит, который я передаю функциональному компоненту, перезаписывается изменением состояния (которое, безусловно, не распространяется на родительский элемент). Я предполагаю, что это потому, что я делаю что-то не так при создании новых объектов, которые затем используются для обновления состояния, но я изо всех сил пытаюсь понять, где.

Я использую react-select, и именно функция изменения в этом вызывает ошибку. Я не думаю, что это само по себе является проблемой, поскольку я использовал его в другом месте проекта и не сталкивался с этой ошибкой.

Вот сокращенная версия моего кода (импорт удален):

 interface IPropertyValue {
    field1: number;
    field2: number;
    field3: number;
    field4: string;        
}

interface INestedPropertyType {
    systemProperty?: IPropertyValue;
}

interface IPropertyType {
    current: INestedPropertyType;
    desired: INestedPropertyType;
}

interface IComponentProps {
    currentProperty: IPropertyType;
}

export function Component(passedProps: IDynamicPropsType) {
    const props = passedProps as IComponentProps;
    const [propName, setPropName] = useState(props.currentProperty);

    const editValue = (typeOfProperty: 'current' | 'desired', newValue: string) => {

        let existingPropName = {...propName} as IPropertyType; // create a new object from existing one
        let alteredPropName = {} as ISystemProperty; // we reassign this later on based on typeOfProperty

        if(typeOfProperty === 'current') {
            if(existingPropName.current.systemProperty) {
                alteredPropname = {...existingPropName.current.systemProperty} as IPropertyValue;
            } else {
                existingPropName.current.systemProperty= {} as IPropertyValue;
                alteredPropName= {} as IPropertyValue;
            }
        } else if (typeOfProperty=== 'desired') {
            if(existingPropName.desired.systemProperty) { // could be undefined...
                // is defined, create a new object for it using existing values
                existingPropName= {...existingPropName.desired.systemProperty} as IPropertyValue;
            } else {
                // not defined, create empty object for it
                existingLineage.desired.systemProperty= {} as IPropertyValue;
                alteredPropName= {} as IPropertyValue;
            }
        }

        const newValues = newValue.split('|'); // this comes from the select option; it's a string separated by '|' symbols. It will always be 4 values long.
        if(newValues.length === 4) {
            alteredPropName = {
                ...alteredPropName,
                field1: parseInt(newValues[0], 10),
                field2: parseInt(newValues[1], 10),
                field3: parseInt(newValues[2], 10),
                field4: newValues[3]
            }
        }

        // update the relevant property with the updated object
        if(typeOfProperty === 'current') {
            existingPropName.current.systemProperty = alteredPropName;
        } else if(typeOfProperty=== 'desired') {
            existingPropName.desired.systemProperty = alteredPropName;
        }

        // update the state
        setPropName(existingLineage);
    }

    // here's the weird part. 'props' is changed after the editValue function is called, even though it should be passed in from the parent component and left unchanged (apart from casting it to IComponentProps)
    console.log(props);

    return (<>
        <Select
            options={<options here>}
            onChange={(selectedOption: ValueType<any>) => {
                editValue('current', selectedOption.value);
            }}
        />
        <Select
            options={<options here>}
            onChange={(selectedOption: ValueType<any>) => {
                editValue('desired', selectedOption.value);
            }}
        />
    </>);
}
 

В коде есть некоторые странные части, такие как приведение passedProps к props , но они существуют по определенной причине — родительский компонент — это оболочка, которая используется совместно несколькими другими компонентами.

Ответ №1:

Да, вы правы. Ваши реквизиты перезаписываются, потому что вы передаете их непосредственно в

 useState(props.currentProperty)
 

В Javascript объекты являются ссылочным типом. Вызов EditValue приведет к обновлению вложенных объектов.

Вы можете создать глубокую копию реквизита, прежде чем передавать его useState . Простой подход заключался бы в использовании JSON.stringify и JSON.parse

 const [propName, setPropName] = useState(JSON.parse(JSON.stringify(props.currentProperty)));
 

Комментарии:

1. Конечно. Я попытался преобразовать его в новый объект (не показанный в моем коде), но я использовал только поверхностную копию, а не глубокую копию. Мой плохой.