嵌套对象的状态更新方法问题

时间:2021-01-26 22:00:10

标签: reactjs clone use-state createcontext

主要编辑

我有一个非常大的物体,深度为 3 级。我使用它作为模板在页面上生成组件并存储稍后使用的值,例如:

obj =
 { 
  "group": {
    "subgroup1": {
      "value": {
        "type": "c",
        "values": []
      },
      "fields_information": {
        "component_type": "table",
        "table_headers": [
          "label",
          "size"
        ],
      }
    },
    "subgroup2": {
      "value": {
        "type": "c",
        "values": []
      },
      "fields_information": {
        "component_type": "table",
        "table_headers": [
          "label",
          "size"
        ],
      }
    },
  },
 }

多亏了这一点,我可以动态生成视图,该视图作为模板存储在数据库中。

我正在为两件事而苦苦挣扎。首先,根据用户输入的文本框、复选框等更新值。 我是这样做的:

    const updateObj = (group, subgroup, value) => {
        let tempObj = {...obj}
        tempObj[group][subgroup].value.value = value
        toggleObj(tempObj)
    }

我知道扩展运算符实际上并没有进行深复制。但是,它允许我处理对象并稍后保存。这是一个问题吗?我是否必须 cloneDeep 或者它很好? cloneDeep 会影响性能吗?

下面描述第二种情况

export const ObjectContext = React.createContext({
    obj: {},
    toggleObj: () => {},
});

export const Parent = (props) => {
    const [obj, toggleObj] = useState()
    const value = {obj, toggleObj}

    return (
        <FormCreator />
    )
}

const FormCreator = ({ catalog }) => {
    const {obj, toggleObj} = React.useContext(ObjectContext)

    return (<>
        {Object.keys(obj).map((sectionName, sectionIdx) => {
        const objFieldsInformation = sectionContent[keyName].fields_information
        const objValue = sectionContent[keyName].value
        ...
            if (objFieldsInformation.component_type === 'table') {
            return (
                <CustomTable 
                key={keyName + "id"}
                label={objFieldsInformation.label}
                headers={objFieldsInformation.table_headers}
                suggestedValues={[{label: "", size: ""}, {label: "", size: ""}, {label: "", size: ""}]}
                values={objValue.values}
                sectionName={sectionName}
                keyName={keyName}/>
            )
            }
        ...
        })}
    </>)
}

const CustomTable= (props) => {
    const { label = "", headers = [], suggestedValues = [], values, readOnly = false, sectionName, keyName } = props
    const {obj, toggleObj} = React.useContext(ObjectContext) 
    
    //this one WORKS
    useEffect(() => {
        if (obj[sectionName][keyName].value.type === "complex") {
            let temp = {...obj}
            temp[sectionName][keyName].value.values = [...suggestedValues]
            toggleObj(temp)
        }
    }, [])
    
    //this one DOES NOT
    useEffect(() => {

        if (obj[sectionName][keyName].value.type === "c") {
            let temp = {...obj, [sectionName]: {...obj[sectionName], [keyName]: {...obj[sectionName][keyName], value: {...obj[sectionName][keyName].value, values: [{label: "", size: ""}, {label: "", size: ""}, {label: "", size: ""}]}}}}
            toggleObj(temp)
        }
    }, [])
    
    return (
    //draw the array
    )
}

请参考 CustomTable 组件。 与上面的示例对象一样,我有 2 个要打印的自定义表。不幸的是,一个应该工作的 useEffect 工作不正常。我正在观察,该值字段仅针对 Obj 中的最后一个“表”设置。当我做 obj 的浅拷贝时,它工作正常。但我担心将来可能发生的任何影响。

我对使用 createContext 也完全陌生,也许这就是问题所在。

感谢任何理解这种混乱的人:)

1 个答案:

答案 0 :(得分:1)

主要问题似乎是您没有提供您的上下文。你所拥有的是字面上传递空白对象和 void 返回函数。因此为什么调用它没有实际效果,但改变值却有。

export const ObjectContext = React.createContext({
    obj: {},
    toggleObj: () => {},
});

export const Parent = (props) => {
    const [obj, toggleObj] = useState({})
    const value = {obj, toggleObj}

    return (
        <ObjectContext.Provider value={value}>
           <FormCreator />
        </ObjectContext.Provider>
    )
}

理想情况下,您还可以让上述组件环绕 FormCreator 并将其呈现为 props.children。这是为了防止每次调用 toggleObj 时都重新渲染整个子树。请参阅 first part of this tutorial 以了解典型模式。

关于改变状态的问题,在 React 中保持状态不可变是绝对重要的——至少,如果你正在使用 useState 或某种减速器。 Stack Overflow 上经常会出现由状态突变引起的错误,事实上我最近做了一个 codesandbox 来演示一些更常见的错误。

我也同意@SamuliHakoniemi 的观点,像这样的深度嵌套对象实际上更适合 useReducer 钩子,甚至可能更进一步并建议这里需要像 Redux 这样的适当状态管理库。它将允许您细分 reducer 以针对实际更新的状态片段,这将有助于在深度克隆状态结构成为实际问题时降低性能成本。