我正在尝试使用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
组件的容器。但是,我发现这样做效率不高,因为它导致了太多的渲染,并且很难确定循环何时结束。经过大量搜索之后,我最终在Context Provider中声明了useRef({})
并将其用作refs容器(我猜这似乎符合创建它的实际目的)。 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'})
答案 0 :(得分:2)
这是您的组件中逐步发生的事情。
container
和setContainer
使用useState
钩子进行初始化myData
上的效果,由于您作为第二个参数传递的空数组[]
,它在组件的整个生命周期中也只会运行一次。(key 1 value 'apple'); container {}
(key 2 value 'banana'); container {}
(key 3 value 'citrus'); container {}
等等,容器不应该在每次迭代中进行更新吗?
是的,但不是您期望的那样。 container
中引用的useEffect
在{}
的每次迭代中保留其原始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>
答案 1 :(得分:0)
是的,这是由于setState
钩子的异步特性,它基本上是批量运行的。
来自react docs:
setState()不会立即使this.state突变,而是创建一个挂起的状态转换。调用此方法后访问this.state可能会返回现有值。无法保证对setState调用的同步操作,并且可能会批量调用以提高性能。
您可以先创建一个对象,然后在循环完成后对其进行设置。另外,您不应该在这里使用map。由于您没有创建新的数组。
useEffect(() => {
const dataObj = {};
myData.forEach(data => {
dataObj[data.pk] = data.name;
});
setContainer(dataObj);
}, []);