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

时间:2020-06-26 14:56:30

标签: javascript reactjs react-hooks

我正在尝试使用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'})

2 个答案:

答案 0 :(得分:2)

这是您的组件中逐步发生的事情。

  1. containersetContainer使用useState钩子进行初始化
  2. 您注册了myData上的效果,由于您作为第二个参数传递的空数组[],它在组件的整个生命周期中也只会运行一次。
  3. 您在效果中注册的回调已运行。如果您使用console.log'd容器以及键值对,则应该是这样
(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);
  }, []);