如何使用钩子更新嵌套json对象的状态

Posted

技术标签:

【中文标题】如何使用钩子更新嵌套json对象的状态【英文标题】:How to update the state of nested json object in react using hooks 【发布时间】:2021-08-29 21:22:18 【问题描述】:

我有这样一个格式的全局状态:


  type: "container",
  items: [
    
      type: "box"
    ,
    
      type: "container",
      items: [
        type: "container",
        items: [
          
            type: "box",
            color: "green"
          ,
          
            type: "box",
            color: "red"
          ,
          
            type: "box"
          
        ]
      ]
    
  ]


上面的状态会产生下面的输出: output

如您所见,我们只有两个元素:容器和盒子。 容器有按钮,可以包含容器或盒子。 盒子只能改变它们的颜色。 了解更好看this demo。

我已经能够复制这种行为,但我希望能够提取应用程序的整个状态并能够恢复它。

恢复状态没有问题。如果我像上面那样传递一个json,它会起作用。 但是当谈到保存我当前的状态时,问题就出现了。 你看,当我尝试恢复我的状态时,我将嵌套对象作为道具传递,然后它成为该子容器的本地状态。但是当我改变那个子容器的状态时,它显然不会改变全局状态(上面的json)。

所以我尝试了两种方法来改变全局状态:

这是我的代码:

    创建onChange 方法,该方法将在某些本地状态更改时触发。然后将该方法作为道具传递,并通过将更改的状态作为参数传递来调用该方法。 (您可以在我的代码中看到这一点) 将嵌套对象的路径(索引列表)作为 prop 传递以恢复为 state.items[path[0]].items[path[1]].items[path[n]] 形式,然后通过 setState 挂钩回调手动更改。

在这两种情况下,我都会得到

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

它只是冻结了。

容器.js

import React,  useState  from 'react'
import  v4 as uuidv4  from 'uuid'
import Box from './Box'

function Container( current, path, onChange ) 
  const styles = ... some styles ...
  
  const [popoverVisible, setPopoverVisibility] = useState(false)

  const [state, setState] = useState(current || [])

  React.useEffect(() => onChange(state, path), [state])

  const handleButtonMouseOver = () => setPopoverVisibility(true)
  const handleButtonMouseLeave = () => setPopoverVisibility(false)
  const handlePopoverMouseOver = () => setPopoverVisibility(true)
  const handlePopoverMouseLeave = () => setPopoverVisibility(false)

  const props = 
    container:  style: styles.container ,
    wrapper:  style: styles.wrapper ,
    popover: 
      style: styles.popover,
      onMouseOver: handlePopoverMouseOver,
      onMouseLeave: handlePopoverMouseLeave
    ,
    buttonAdd: 
      style: styles.button,
      onMouseOver: handleButtonMouseOver,
      onMouseLeave: handleButtonMouseLeave
    ,
    buttonBox: 
      style: styles.button,
      onClick: () => setState([...state, type: 'box'])
    ,
    buttonContainer: 
      style: styles.button,
      onClick: () => setState([...state, type: 'container', items: []])
      //onClick: () => setState(...state, items: [...state.items, type: 'container', items: []])
    
  

  return (
    <>
      <div ...props.container>
        state.map((x, i) => x.type === 'container' ? <Container key=uuidv4() current=x.items onChange= onChange  path=[...path, i]></Container> : <Box key=uuidv4()></Box>)
        <div ...props.wrapper>
          <div>
            <div ...props.popover>
              popoverVisible && <button ...props.buttonBox>Box</button>
              popoverVisible && <button ...props.buttonContainer>Container</button>
            </div>
            <button ...props.buttonAdd>Add</button>
          </div>
        </div>
      </div>
    </>
  )


export default Container

App.js

import  useState, useEffect  from 'react'
import Container from './Container.js'

function App() 
  const styles = 
    button: 
      margin: ".5em",
      width: "7em",
      height: "3em",
      border: "1px solid black",
      borderRadius: ".25em"
    
  

  const [state, setState] = useState(type: "container", items: [])
  const [newState, setNewState] = useState("")
  const [generatedJson, setGeneratedJson] = useState("")

  const handleContainerChange = (s, p) => setState(...state, items: s)
  const handleTextareaChange = e => setNewState(e.target.value)
  const handleBuildButtonClick = () => setState(JSON.parse(newState))
  const handleCreateJsonButtonClick = () => setGeneratedJson(JSON.stringify(state))

  //useEffect(() => alert(state), [state])

  return (
    <>
      <div style= display: "flex" >
        <Container key=state current=state.items path=[] onChange= handleContainerChange >
        </Container>
      </div>
      <textarea cols="30" rows="5" onChange= handleTextareaChange ></textarea><br/>
      <button style=styles.button onClick= handleBuildButtonClick >Build</button><br/>
      <button style=styles.button onClick= handleCreateJsonButtonClick >Create JSON</button>
      <label key=generatedJson>generatedJson</label>
    </>
  )


export default App

所以我想要的是在本地状态发生变化时更新全局状态。 示例:

globalState = 
  type: "container",
  items: [
     type: "container", items: [] 
  ]


// that inner container from the global state
localState =  type: "container", items: [] 

// so when it changes to

localState =  
  type: "container", 
  items: [
    type: "box"
  ]


// i want the global state to become

globalState = 
  type: "container",
  items: [
     
      type: "container", 
      items: [
        type: "box"
      ]
    
  ]

我该怎么做?

【问题讨论】:

将传递的 props 存储到本地状态是 React 中的反模式。 @DrewReese 我才刚刚开始学习反应。在这种情况下,你将如何传递嵌套的 json 状态? 一般情况下你会直接消费传递过来的props。我看到你似乎在做一些递归渲染。这有点棘手,但是您可以将嵌套的状态块传递给Container 的每个子版本,并通过回调来更新整体状态结构,但是您还需要指定“状态树”中的位置你正在更新。当我发表评论时,我没有立即看到递归。如果在该级别存储本地状态缓存对您有用,那么我认为没问题。 【参考方案1】:

问题

我认为主要问题是在AppContainer 组件上使用state 作为React 键。每次 state 更新时,您都在指定一个新的 React 键,React 将通过卸载以前的版本并安装新版本的 Container 来处理这个问题。

<Container
  key=state // <-- new key each state update
  current=state.items
  path=[]
  onChange= handleContainerChange 
>
</Container>

每次Container 挂载时,它都会实例化state 并运行useEffect,它会调用传递的onChange 属性。

const [state, setState] = useState(current || []);

React.useEffect(() => onChange(state, path), [state]);

解决方案

删除AppContainer 上的React 键。

<Container
  current=state.items
  path=[]
  onChange= handleContainerChange 
/>

【讨论】:

谢谢!现在一切正常!

以上是关于如何使用钩子更新嵌套json对象的状态的主要内容,如果未能解决你的问题,请参考以下文章

如何设置映射输入的嵌套 JSON 数组对象的状态

使用 prevState 和 useState 钩子更新对象内部数组中的对象

ReactJS:如何使用动态键访问和更新嵌套状态对象?

如何使用Java更新JSON文件中的嵌套JSON对象?

如何在反应钩子中创建一个新的 JSON 对象?

如何正确更新反应钩子状态中的数组