尽管对象未更改,但useEffect仍重新渲染我的组件

时间:2020-07-12 18:04:20

标签: javascript reactjs socket.io

我正在制作一个使用socket.io的应用程序。我的后端不断向我的前端发送数据,并且数据很少更改,但是我需要它是实时的,因此它每半秒发送一次到前端,因此套接字事件每半秒触发一次并且每0.5秒将数据设置为我的状态变量。但是,正如我所说,该数据很少更改。由于数据没有变化,所以我期望React不会重新渲染所有内容,但是确实如此!

function App() {
  const [regData, setRegData] = useState();

  useEffect(() => {
    socket.on("Data", (RegData) => {
      setRegData(RegData);
    });
  }, []);

  return (
    <>
      <ABunchOfComponentsRelyingOnRegData>{regData[" 1"].Val}</.....>
    </>
  );

所以这段代码有两个问题:

  1. 正如我已经描述的,每次触发套接字事件时,所有组件都会重新呈现,每次接收的数据都是相同的。
  2. 最初,regData是未定义的,所以如果我按上面代码中的方式使用它,则会收到一条错误消息,指出它无法访问未定义的[“ 1”]。如果我改为使用{regData ? regData[" 1"].Val : null},它会起作用,但是似乎效率很低,因为我可以拥有数百个依赖于与该对象不同数据的组件,在XXX组件中使用三元运算符只是为了避免渲染前几次运行而没有数据似乎是一个糟糕的解决方案。

已解决:

我通过实现useRef()并比较数据并仅在接收到新数据时更新状态来解决了该问题。否则,整个组件(基本上是保存我的整个应用程序的组件)将被重新渲染(在用户端没有实际的重新渲染,因为没有任何改变,但仍然如此)。

跟踪是否需要接收初始数据,因为否则将永远不会设置状态。

在我的情况下,与JSON.Stringify相比就足够了,因为我发送的数据是一个简单的JS对象,只有字符串/整数。

对我来说似乎有太多变通办法,但这并不复杂,并且可以按预期工作。

我想到了比较对象然后才从答案中进行渲染的想法,但是没有一个答案足以解决问题。

const [regData, setRegData] = useState();
const prevRegData = useRef();
let initialDataReceived = false;

useEffect(() => {
  socket.on("DATA", (RegData) => {
    if (!initialDataReceived) {
      setRegData(RegData);
      initialDataReceived = true;
    }

    if (!(JSON.stringify(prevRegData.current) === JSON.stringify(RegData))) {
      setRegData(RegData);
    }
  });
}, []);

useEffect(() => {
  prevRegData.current = regData;
}, [regData]);

if (!regData) return <div>Loading...</div>;

return (
  <>
    <TheRestOfTheApp>..A bunch of nested components that use various data from regData..</TheRestOfTheApp>
  </>
)

}

2 个答案:

答案 0 :(得分:0)

  1. 由于您正在套接字事件侦听器中调用setRegData,因此组件状态将得到更新,因此将重新呈现。如果您不希望在每个“数据”事件上发生这种情况,请尝试在事件监听器首次发出后将其删除。像这样:
...
const updateData = (data) => {
  setRegData(data);
  socket.off("Data", updateData);
}

useEffect(() => {
  socket.on("Data", updateData);
}, [])
...
  1. 您可以将regData初始化为一个空数组,如下所示:
...
const [regData, setRegData] = useState([]);
...

答案 1 :(得分:0)

使用 useEffect 时,该功能仅运行一次。但是,每次事件“ Data”( socket.on(“ Data” )发生时,都会触发您作为 socket.on 中的第二个参数传递的回调。每次(0.5s)数据到达时,它都会执行 setRegData ,它基本上是一个 setState ,但仅用于新的挂钩状态。每次设置状态时,都要做出反应尝试渲染此新属性。这就是为什么要使其不断渲染的原因。

  1. 对于您的问题,我建议您使用此
useEffect(() => {
  socket.on("Data", (RegData) => {
    if (regData[1].Val !== RegData[1].Val) { // or any other difference
      setRegData(RegData);
    }
  });
}, []);

请注意,如果数据已更改,我只会执行 setRegData ,这样可以避免每次数据到达前端时都重新设置 ReData (这避免了重新渲染问题)

  1. 记住useEffect就像一个componentDidMount,它在第一次渲染后触发。这意味着第一次 {regData [“ 1”]。Val} 呈现 useEffect 尚未运行,数据尚未与您的 setRegData 。处理此问题的最佳方法是检查是否未定义

    {regData && regData [“ 1”]。Val}

但是,如果您有更多的情况(例如您的regData是空对象),则应验证更多字段或添加更多验证逻辑。您可以创建类似这样的功能验证器

const isRegDataComplete = () => regData && regData[1] && regData[1].Val && ...;
...
return (
  <>
    { 
      isRegDataComplete() && regData[1].Val} 
    }
  <>
);