使用 React useState 钩子将键值对添加到循环中的对象



我正在尝试使用函数内部的useState向对象添加键值对map。然而,setContainer({...container, [data.pk]: data.name});的点差操作似乎无效。

下面的代码片段准确地说明了我正在尝试解决的问题。事实上,在我的原始代码中,container是在另一个ContextProvider组件上声明并使用useContext引用的,但为了简单起见,我用useState替换了它。

我想这与setState钩子的异步性质有关,但我并不完全了解引擎盖下发生了什么。

请让我知道导致此问题的原因以及如何产生我预期的结果。 谢谢。

const {useState, useEffect} = React;
const myData = [{pk: 1, name:'apple'},
{pk: 2, name:'banana'},
{pk: 3, name:'citrus'},
]
const Example = () => {
const [container, setContainer] = useState({});

useEffect(()=>{
myData.map(data=>{
console.log(`During this iteration, a key-value pair (key ${data.pk} and value '${data.name}') should be added to container`);
setContainer({...container, [data.pk]: data.name});
})
}, [])
return (
<div>
<div>result I expected: {"{'1': 'apple', '2': 'banana','3': 'citrus'}"}</div>
<div>result: {JSON.stringify(container)}</div>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>

更新

我想在循环中使用useState的主要原因是将状态用作refs组件的容器。但是,我发现这样做效率低下,因为它会导致太多的重新渲染,并且很难确定循环何时完成。经过大量搜索,我最终在上下文提供程序中声明了useRef({})并将其用作 refs 容器(我猜这似乎符合创建它的实际目的(。引用容器可以按如下方式使用:

const refsContainer = useRef({});
import React from 'react';
const Notes = () => {
const setRef = (noteId) => (element) => {
refsContainer.current[noteId] = element
};
return (
<Masonry>                     
{pubData?.map(publication => {                                                                              
return <PublicationCard innerRef={setRef(publication.noteId)}/>   
})}                                                                                                         
</Masonry>                                                                                                      
);
};
export default Notes;
const pubCardRef = refsContainer.current[rank.note.noteId];
window.scrollTo({top: pubCardRef?.offsetTop - 80, behavior: 'smooth'})

以下是组件中逐步发生的事情。

  1. containersetContainer使用useState挂钩进行初始化
  2. 您映射myData的效果已注册,并且由于您作为第二个参数传递的空数组[]它在组件的整个生命周期中只会运行一次
  3. 将在效果中注册回调运行。 这就是控制台.log容器以及键值对时
  4. 的样子
(key 1 value 'apple'); container {}
(key 2 value 'banana'); container {}
(key 3 value 'citrus'); container {}

等等,容器不应该在每次迭代时更新吗?

嗯,是的,但不是以你期望的方式。useEffect中引用的container在每次迭代中保留其原始myData.map{}值。那么有效发生的事情是

setContainer({ ...{}, 1: 'apple ' }) // RERENDER
setContainer({ ...{}, 2: 'banana' }) // RERENDER
setContainer({ ...{}, 3: 'citrus' }) // RERENDER

citrus是自上次重新渲染以来的最终显示。

这是您可以实现所需结果的一种方法

const {useState, useEffect} = React;
const myData = [{pk: 1, name:'apple'},
{pk: 2, name:'banana'},
{pk: 3, name:'citrus'},
]
const Example = () => {
const [container, setContainer] = useState({});

useEffect(()=>{
setContainer(myData.reduce((obj, data) => ({ ...obj, [data.pk]: data.name }), {}))
}, [])
return (
<div>
<div>result I expected: {"{'1': 'apple', '2': 'banana','3': 'citrus'}"}</div>
<div>result: {JSON.stringify(container)}</div>
</div>
);
};
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>

你是对的,这是因为setState钩子的异步性质,它基本上是批量运行的。

从反应文档:

setState(( 不会立即改变 this.state,但会创建一个挂起的状态转换。调用此方法后访问 this.state 可能会返回现有值。无法保证对 setState 的调用的同步操作,并且可以对调用进行批处理以提高性能。

您可以先创建一个对象,然后在循环完成后对其进行设置。此外,您不应该在此处使用地图。由于您没有创建新数组。

useEffect(() => {
const dataObj = {};
myData.forEach(data => {
dataObj[data.pk] = data.name;
});
setContainer(dataObj);
}, []);

const [data,setData]= useState({});
const subData=[{type:'data1'},{type:'data2'}]

// expect result for data = {'data1':false,'data1':false}

useEffect(()=>{
setData(subData?.reduce((obj,data) => ({...obj,[data.type]:false }), {}))
},[])

最新更新