【问题标题】:The correct way to store complex form data in React app在 React 应用中存储复杂表单数据的正确方法
【发布时间】:2020-10-05 01:05:32
【问题描述】:

我在 React 中有一个包含十几个子组件的复杂表单。它还包括我自己的表单组件,这些组件不是由单个值操作的。表单的某些部分使用对象树作为数据。

我有一个主要组件(我们称之为 EditForm),它存储整个数据对象树。我假设这个表单的每个组件都应该使用这个主要对象的特定部分。我应该能够将整个表单数据存储到 JSON 中并从 JSON 中读取(并在 JSON 更改时自动更新所有子组件)。

我的想法是仅刷新我更改主 JSON 时所需的表单部分。但是处理这种行为的代码非常庞大和复杂。我必须在每个子组件中实现比较功能,并将部分主要 JSON 转换为每个子组件的 props。

const [testobj, setTestobj] = useState({'obj1': {......}, 'obj2': {.......}, .......});

function revalidateSub(sub_name, value)
 {
  let t2 = cloneDeep(testobj);
  t2[sub_name] = value;
  setTestobj(t2);
 }

 return (<MySubComponent subobj={testobj['sub1']} ident={"sub1"} onUpdateData={v => revalidateSub('sub1', v)}/>)

subobj是由特定子组件控制的数据,onUpdateData是一个处理程序,每次对受控数据进行任何更改时都会在子组件内部调用)

但是,此方案无法按预期工作。因为 revalidateSub() 函数存储了自己的 testobj 副本和每个组件的 onUpdateData 重写其他子组件的更改...

说实话,我不太了解如何为这种类型的 React 应用正确存储数据。你有什么建议?也许我应该将数据存储从 React 组件中移出?或者也许 useMemo?

感谢您分享的经验和示例!

更新1:我已经在沙箱中编写了示例,请检查 https://codesandbox.io/s/thirsty-browser-xf4u0

要查看问题,请点击几次“点击我!”对象 1 中的按钮,您可以看到 obj1 的值正在发生变化,主对象的值也在发生变化(即正确)。然后单击一次到“单击我!” obj2 的。您将看到(BOOM!)obj1 的值被重置为其默认状态。这是不正确的。

【问题讨论】:

    标签: javascript reactjs forms use-state


    【解决方案1】:

    我想我已经自己解决了这个问题。事实证明,setState 函数也可以接收 FUNCTION 作为参数。这个函数应该返回一个新的“状态”值。

    我已将 revalidateSub() 函数从 EditForm 组件中移出,因此它不再复制状态变量“testobj”。它现在按预期工作。

    Codebox Page

    【讨论】:

    • 我想过为什么!我在答案下方添加了更新。读!谢谢,我学到了新知识。谢谢。
    【解决方案2】:

    首先,我认为这个想法是正确的。在 React 组件中,当从 state 或父组件传递过来的 props 的值发生变化时,需要进行重新渲染。被识别为一个值。

    由于这个问题,当我们需要更新多层对象或数组时,我们必须使用 ... 来创建具有现有值的新对象或数组。 复制过程中好像出错了。

    我没有完整的代码,所以无法详细回答,但我认为底部是问题。

    let t2 = cloneDeep(testobj);
    

    还有很多其他的解决方案,但首先尝试 Immutable.js 会帮助您找到问题的原因。

    考虑使用Reducer。


    更新

    export default function EditForm(props) {
      const [testobj, setTestobj] = useState({
        sub1: { id: 5, v: 55 },
        sub2: { id: 7, v: 777 },
        sub3: { id: 9, v: 109 }
      });
    
      function revalidateSub(sub_name, value) {
        let t2 = cloneDeep(testobj);
        t2[sub_name] = value;
        return t2;
      }
      console.log("NEW EditForm instance was created");
    
      function handleTestClick() {
        let t2 = cloneDeep(testobj);
        t2.sub2.v++;
        setTestobj(t2);
      }
    
      return (
        <div>
          <div style={{ textAlign: "left" }}>
            <Button onClick={handleTestClick} variant="contained" color="secondary">
              Click Me to change from main object
            </Button>
            <p>
              Global value:{" "}
              <span style={{ color: "blue" }}>{JSON.stringify(testobj)}</span>
            </p>
            <MyTestObj
              subobj={testobj["sub1"]}
              ident={"sub1"}
              onUpdateData={(v) => setTestobj((p) => revalidateSub("sub1", v))}
            />
            <MyTestObj
              subobj={testobj["sub2"]}
              ident={"sub2"}
              onUpdateData={(v) => setTestobj((p) => revalidateSub("sub2", v))}
            />
            <MyTestObj
              subobj={testobj["sub3"]}
              ident={"sub3"}
              onUpdateData={(v) => setTestobj((p) => revalidateSub("sub3", v))}
            />
          </div>
        </div>
      );
    }
    

    执行上述 setState 时发生爆裂的原因是异步更新本地状态。

    应该将 setState 方法视为单个请求,而不是立即同步执行。也就是说,即使通过 setState 改变了状态,也不会在方法执行后立即应用改变的状态。

    因此,您的代码

    setTestobj((p) => revalidateSub(p, "sub3", v))
    

    这样使用setstate的第一个参数来获取完整状态比较安全!

    多亏了这个,我能够获得新的知识。谢谢。

    【讨论】:

    • 我使用的是 lodash 库的 cloneDeep() 方法,因为 {...obj} 只克隆子对象的顶层。我认为我的想法不太正确,因为附加代码的数量太多(我认为),而且 revalidateSub() 函数的每个副本都会生成它自己的 testobj 值副本。
    • 我说的正确不是代码的美观,而是代码是否有效。 {'obj1': {...obj1}, 'obj2': {...obj2, obj2.a}}, What I said... 意思是下面的代码行,但类似于 lodash 的 deepcopy 行为。我一直在对嵌套对象使用 reducer 或其他东西,因为 setState 直到现在才起作用,但是当我查找时发现我的答案的原因是错误的。 ==> 我会多寻找一点并更正答案。
    • 搜索的结果,如果从t2中复制的值很好,则整个代码行为是正确的。也许是对方的问题。而且上面的评论没有编辑,所以我又写了一遍。我们可以将 useState 用于嵌套对象。请参考以下stackoverflow.com/questions/43040721/…
    • 啊...我想我找到了... ``` subobj={testobj['sub1']} ``` 在这里,尝试将 testobj 本身作为子组件传递。跨度>
    • 请查看初始消息,我添加了一个密码箱链接,您可以自己查看问题并进行操作。
    猜你喜欢
    • 2018-06-20
    • 2021-12-29
    • 1970-01-01
    • 2013-05-08
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    • 1970-01-01
    相关资源
    最近更新 更多