React 在事件上重复渲染

React renders repeatedly on event

我在我的网站上使用套接字,并且有一个事件,其中一个用户可以向服务器发送一个词,服务器向每个人发出(art-addpic)一个与该词相对应的图像 URL ,但只有具有 isArtist=true 的用户才能响应该事件。 艺术家的页面应该使用收到的 URL 更新现有图像列表 URLs (optionImages)。但是当接收到事件时,列表中的所有图像都被接收到的 URL 替换。此外,呈现图像列表 ArtBoard 的组件不会使用更新后的 URL 重新呈现。 我是 React 的新手。我哪里错了?

我已经检查过服务器,事件 art-addpic 只广播了一次。

Arena.js:(发生这种情况的网页):

import React, { useEffect, useState } from "react";
import Leaderboard from "../comps/Leaderboard";
import { io } from "socket.io-client";
import Service from "../Service";
import DetBoard from "../comps/DetBoard";
import ArtBoard from "../comps/ArtBoard";
const username = "Nick"
const roomkey="abc"
let userid;
if(localStorage.getItem('userid')){
    userid = localStorage.getItem('userid')
}
else{
    userid = Service.makeid(5);
    localStorage.setItem('userid', userid);
}
function useForceUpdate(){
    const [value, setValue] = useState(0); // integer state
    return () => setValue(value => value + 1); // update the state to force render
}
// const [userid,setUserId] = 
const socket = io('http://localhost:3001', {query:"username="+username+"&roomkey="+roomkey+"&userid="+userid});
const Arena = (props)=>{
    const [isArtist, setIsArtist] = useState(false);
    const [focusImage, setFocusImage] = useState('https://i.imgur.com/61HsZCU.jpeg')
    const [players, setPlayers] = useState([]);
    const [optionImages, setOptionImages] = useState([
        'https://i.imgur.com/61HsZCU.jpeg',
        'https://i.imgur.com/61HsZCU.jpeg',
        'https://i.imgur.com/61HsZCU.jpeg',
        'https://i.imgur.com/61HsZCU.jpeg',
        'https://i.imgur.com/61HsZCU.jpeg'
    ])
    useEffect(()=>{
        socket.on('connect',()=>{
            console.log("connected")
        })
        socket.on('players', (data)=>{
            data = JSON.parse(data)
            console.log(data)
            setPlayers(data)
        })
        socket.on('artist', (data)=>{
            if(data===userid){
                console.log('You are an artist, Mr White.')
                setIsArtist(true);
            }
            else{
                setIsArtist(false);
            }
        })    
        socket.on('art-addpic', (data)=>{
            data = JSON.parse(data)
            console.log(data)
            let tempOps =optionImages;
            tempOps.splice(0, 1);
            tempOps.push(data.url)
            console.log(tempOps)
            setOptionImages(tempOps);
        })
    }, [
        optionImages
    ]);
    if(isArtist){
        return(
            <div>
            <Leaderboard players={players}></Leaderboard>
            {/* <ArtBoard></ArtBoard> */}
            <ArtBoard socket={socket} focusImage={focusImage} optionImages={optionImages} setOptionImages={setOptionImages}/>         
        </div>
        );
    }
    else{
        return (
            <div>
            <Leaderboard players={players}></Leaderboard>
            {/* <ArtBoard></ArtBoard> */}
            <DetBoard socket={socket} focusImage={focusImage}/>         
        </div>
        );
    }
}
export default Arena;

你至少有几个问题:

  1. useEffect 挂钩没有返回清理函数来取消订阅套接字连接,因此它们保持打开状态。
  2. optionImages 状态突变。
  3. 更新 optionImages 状态会重新触发 useEffect 回调,从而创建更多订阅。

挂钩代码

useEffect(() => {
  socket.on('connect', () => {
    console.log("connected");
  });

  socket.on('players', (data) => {
    data = JSON.parse(data);
    console.log(data);
    setPlayers(data);
  });

  socket.on('artist', (data) => {
    if (data === userid) {
      console.log('You are an artist, Mr White.');
      setIsArtist(true);
    } else {
      setIsArtist(false);
    }
  });

  socket.on('art-addpic', (data) => {
    data = JSON.parse(data);
    console.log(data);
    let tempOps = optionImages; // (2) tempOps is reference to optionImages state
    tempOps.splice(0, 1);   // (2) mutation!
    tempOps.push(data.url); // (2) mutation!
    console.log(tempOps);
    setOptionImages(tempOps); // (2,3) saved state reference back into state
  });

  // (1) missing cleanup function
}, [optionImages]); // (3) state updated in hook

据我所知,主要问题出在 'art-addpic' 事件上。似乎您想从 optionImages 状态中删除第一个元素并在末尾添加一个新的 URL。

如果是这样的话我有以下建议:

  1. Return 取消订阅套接字连接的清理函数。
  2. 删除所有 useEffect 钩子依赖项,以便在组件安装时钩子 运行 一次以建立套接字订阅,并在卸载时清除它们。
  3. 使用 optionImages 的功能状态更新来删除作为外部依赖项的状态。

挂钩代码

useEffect(() => {
  socket.on('connect', () => {
    console.log("connected");
  });

  socket.on('players', (data) => {
    const parsedData = JSON.parse(data);
    console.log(parsedData);
    setPlayers(parsedData);
  });

  socket.on('artist', (data) => {
    setIsArtist(data === userid);
  });

  socket.on('art-addpic', (data) => {
    const parsedData = JSON.parse(data);
    console.log(parsedData);

    setOptionImages(optionImages => 
      // Shallow copy into array, append URL, slice & keep last 4 elements
      [...optionImages, parsedData.url].slice(-4),
    );
  });

  return () => {
    socket.removeAllListeners();
  }
}, []);

useEffect(() => {
  if (isArtist) {
    console.log('You are an artist, Mr White.');
  }
}, [isArtist]);