我正在尝试使用函数内部的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'})
以下是组件中逐步发生的事情。
container
和setContainer
使用useState
挂钩进行初始化- 您映射
myData
的效果已注册,并且由于您作为第二个参数传递的空数组[]
它在组件的整个生命周期中只会运行一次 - 将在效果中注册回调运行。 这就是控制台.log容器以及键值对时 的样子
(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 }), {}))
},[])