如何使用钩子更新嵌套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】:
问题
我认为主要问题是在App
的Container
组件上使用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]);
解决方案
删除App
中Container
上的React 键。
<Container
current=state.items
path=[]
onChange= handleContainerChange
/>
【讨论】:
谢谢!现在一切正常!以上是关于如何使用钩子更新嵌套json对象的状态的主要内容,如果未能解决你的问题,请参考以下文章