如何防止在数组更新时重新渲染数组元素?

huangapple go评论83阅读模式
英文:

How to prevent re render of Array element when the array gets updated?

问题

我有一个卡片组件的数组。我经常会在用户操作(比如点击按钮)时添加一张卡片,或者用户可以移除一张卡片。然而,由于使用了useState,当状态改变时,它会重新渲染。但在这种情况下,如果我的数组中有3张卡片,而我添加了第4张卡片,我真的不希望重新渲染其他3张卡片,因为它们没有发生任何变化,只是它们存在于一个由useState创建的数组中。

要求是不管我是添加还是移除数组中的元素,都不要重新渲染现有组件。

我尝试过useState和useRef以及自定义钩子,但都没有成功。使用useRef时,它在需要重新渲染时没有重新渲染,既没有重新渲染现有组件,也没有重新渲染以显示新组件。自定义钩子结合了添加和移除功能,但仍然在内部使用了useState。

这里是一个在sandbox中的问题的较小版本。为了快速示例,我硬编码了删除函数。
在控制台中,您会看到在添加或删除时打印的控制台日志(理想情况下不应该发生)。
https://codesandbox.io/s/no-rerender-array-element-jvu6q5

谢谢任何帮助!

英文:

I have an array of card component. I will often be adding a card on a user action(like click a button) or the user is able to remove a card. However, since it's useState when the state changes it gets re rendered. But In the case I have 3 cards in my array and that I add a 4th one I really don't want to re render the other 3 when no change happened to them but it's just that they are being in an array made from useState.

The requirement is that it doesn't re render existing component whether I add or remove an element from the array.

I've tried useState & useRef and custom hook and no luck there. With useRef it didn't re render when I needed it to re render. Like it didn't re render the existing but also didn't re render to show the new one. The custom hook combine the add and remove feature but still used useState from within.

Here is a smaller version of the issue in a sandbox. For the sake of a quick example, I'm hardcoding the remove function.
In the console, you'll see the console log printing when you add or remove and that's inside the card component(shouldn't happen ideally)
https://codesandbox.io/s/no-rerender-array-element-jvu6q5

Thanks for any help!

import "./styles.css";
import React, { useEffect, useRef, useState, useContext } from "react";

const fakeData1 = {
  Card1: [1, 2, 3, 4]
};
const fakeData2 = {
  Card2: [5, 6, 7, 8]
};

const fakeObject = { fakeData1 };
export default function App() {
  const [cardArray, setCardArray] = useState(fakeObject);

  const addCard = () => {
    setCardArray((entityState) => ({
      ...entityState,
      fakeData2
    }));
  };

  const Card = ({ id, index, item }) => {
    console.log("Rendering Card: ", item);
    const handleRemove = (event: any | MouseEvent) => {
      if (event.type == "click" || event.type == "keydown") {
        setCardArray((entityState) => {
          const updatedData: any = { ...entityState };
          delete updatedData["fakeData2"];
          return updatedData;
        });
      }
    };
    return (
      <div style={{ border: "black solid 2px", padding: "50px 0" }}>
        <h1>Card - {id}</h1>
        <div>Content: {Object.values(item)}</div>
        <button onClick={handleRemove}>Remove</button>
      </div>
    );
  };

  return (
    <div className="App">
      <button onClick={addCard}>Add a Card</button>
      {Object.values(cardArray)
        .flat()
        .map((item: any, index) => {
          return <Card id={index} key={index} item={item} />;
        })}
    </div>
  );
}


答案1

得分: 0

你可以使用 React.memo 来防止组件在没有更新的情况下重新渲染,就像这样:

import "./styles.css";
import React, { useEffect, useRef, useState, useContext, memo } from "react";

const fakeData1 = {
  Card1: [1, 2, 3, 4]
};
const fakeData2 = {
  Card2: [5, 6, 7, 8]
};

const Card = memo(({ id, index, item, setCardArray }) => {
  console.log("Rendering Card: ", item);
  const handleRemove = (event: any | MouseEvent) => {
    if (event.type == "click" || event.type == "keydown") {
      setCardArray((entityState) => {
        const updatedData = { ...entityState };
        delete updatedData["fakeData2"];
        return updatedData;
      });
    }
  };
  return (
    <div style={{ border: "black solid 2px", padding: "50px 0" }}>
      <h1>Card - {id}</h1>
      <div>Content: {Object.values(item)}</div>
      <button onClick={handleRemove}>Remove</button>
    </div>
  );
});


const fakeObject = { fakeData1 };
export default function App() {
  const [cardArray, setCardArray] = useState(fakeObject);

  const addCard = () => {
    setCardArray((entityState) => ({
      ...entityState,
      fakeData2
    }));
  };

  
  return (
    <div className="App">
      <button onClick={addCard}>Add a Card</button>
      {Object.values(cardArray)
        .flat()
        .map((item, index) => {
          return <Card setCardArray={setCardArray} id={index} key={index} item={item} />;
        })}
    </div>
  );
}

我建议组件的键值应该设置为唯一的值,以便组件可以按照我们的预期方式执行。希望这对你有所帮助。

英文:

you can use React.memo to prevent components from re-rendering without any updates.like this:

import &quot;./styles.css&quot;;
import React, { useEffect, useRef, useState, useContext, memo } from &quot;react&quot;;

const fakeData1 = {
  Card1: [1, 2, 3, 4]
};
const fakeData2 = {
  Card2: [5, 6, 7, 8]
};

const Card = memo(({ id, index, item, setCardArray }) =&gt; {
  console.log(&quot;Rendering Card: &quot;, item);
  const handleRemove = (event: any | MouseEvent) =&gt; {
    if (event.type == &quot;click&quot; || event.type == &quot;keydown&quot;) {
      setCardArray((entityState) =&gt; {
        const updatedData: any = { ...entityState };
        delete updatedData[&quot;fakeData2&quot;];
        return updatedData;
      });
    }
  };
  return (
    &lt;div style={{ border: &quot;black solid 2px&quot;, padding: &quot;50px 0&quot; }}&gt;
      &lt;h1&gt;Card - {id}&lt;/h1&gt;
      &lt;div&gt;Content: {Object.values(item)}&lt;/div&gt;
      &lt;button onClick={handleRemove}&gt;Remove&lt;/button&gt;
    &lt;/div&gt;
  );
});


const fakeObject = { fakeData1 };
export default function App() {
  const [cardArray, setCardArray] = useState(fakeObject);

  const addCard = () =&gt; {
    setCardArray((entityState) =&gt; ({
      ...entityState,
      fakeData2
    }));
  };

  
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;button onClick={addCard}&gt;Add a Card&lt;/button&gt;
      {Object.values(cardArray)
        .flat()
        .map((item: any, index) =&gt; {
          return &lt;Card setCardArray={setCardArray} id={index} key={index} item={item} /&gt;;
        })}
    &lt;/div&gt;
  );
}

And i suggest that the key of the component should be set to a unique value, so that the component can be executed in the way we expected.
Hope it helps you.

答案2

得分: 0

你需要的是将状态和函数进行记忆化处理。你可以使用React.memo记忆化(使其不必要地刷新),并使用useCallback来记忆化removeCard函数。此外,你需要将removeCard函数移到卡片组件外部,并将其作为prop传递,因为每次创建新卡片时,它会创建一个新的removeCard函数,导致重新渲染。你可以按照以下方式更新你的代码(基于codeSandbox上的代码):

import "./styles.css";
import React, {
  useEffect,
  useRef,
  useState,
  useContext,
  useCallback
} from "react";

const fakeData1 = {
  Card1: [1, 2, 3, 4]
};
const fakeData2 = {
  Card2: [5, 6, 7, 8]
};
const Card = React.memo(({ id, item, onRemove }) => {
  console.log("Rendering Card: ", item);

  return (
    <div style={{ border: "black solid 2px", padding: "50px 0" }}>
      <h1>Card - {id}</h1>
      <div>Content: {Object.values(item)}</div>
      <button onClick={onRemove}>Remove</button>
    </div>
  );
});

const fakeObject = { fakeData1 };
export default function App() {
  const [cardArray, setCardArray] = useState(fakeObject);

  const addCard = () => {
    setCardArray((entityState) => ({
      ...entityState,
      fakeData2
    }));
  };

  const removeCard = useCallback(() => {
    setCardArray((entityState) => {
      const updatedData = { ...entityState };
      delete updatedData["fakeData2"];
      return updatedData;
    });
  }, []);

  return (
    <div className="App">
      <button onClick={addCard}>Add a Card</button>
      {Object.values(cardArray)
        .flat()
        .map((item, index) => {
          const cardName = `Card${index + 1}`;
          return <Card id={index} key={index} item={item} onRemove={removeCard} />;
        })}
    </div>
  );
}
英文:

What you need is to memo-ize your state and functions, you can use React.memo memoize (make it so that it doesn't refresh unecesarly) and useCallback to memo-ize the removeCard function, you also need to move the removeCard funct to outside of the card component and pass it as a prop as every time a new card is made it creates a new removeCard function causing a re-render you can update your code like so e(Im based it on the codeSandbox):

import &quot;./styles.css&quot;;
import React, {
useEffect,
useRef,
useState,
useContext,
useCallback
} from &quot;react&quot;;
const fakeData1 = {
Card1: [1, 2, 3, 4]
};
const fakeData2 = {
Card2: [5, 6, 7, 8]
};
const Card = React.memo(({ id, item, onRemove }) =&gt; {
console.log(&quot;Rendering Card: &quot;, item);
return (
&lt;div style={{ border: &quot;black solid 2px&quot;, padding: &quot;50px 0&quot; }}&gt;
&lt;h1&gt;Card - {id}&lt;/h1&gt;
&lt;div&gt;Content: {Object.values(item)}&lt;/div&gt;
&lt;button onClick={onRemove}&gt;Remove&lt;/button&gt;
&lt;/div&gt;
);
});
const fakeObject = { fakeData1 };
export default function App() {
const [cardArray, setCardArray] = useState(fakeObject);
const addCard = () =&gt; {
setCardArray((entityState) =&gt; ({
...entityState,
fakeData2
}));
};
const removeCard = useCallback(() =&gt; {
setCardArray((entityState) =&gt; {
const updatedData = { ...entityState };
delete updatedData[&quot;fakeData2&quot;];
return updatedData;
});
}, []);
return (
&lt;div className=&quot;App&quot;&gt;
&lt;button onClick={addCard}&gt;Add a Card&lt;/button&gt;
{Object.values(cardArray)
.flat()
.map((item, index) =&gt; {
const cardName = `Card${index + 1}`;
return (
&lt;Card id={index} key={index} item={item} onRemove={removeCard} /&gt;
);
})}
&lt;/div&gt;
);
}

huangapple
  • 本文由 发表于 2023年5月15日 09:05:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76250306.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定