简体   繁体   English

为什么我的反应元素渲染多次?

[英]Why is my react element rendering many times?

Problem : I have the following react component.问题:我有以下反应组件。 Basically, I'm building a chat application in react with socket.io基本上,我正在构建一个与 socket.io 反应的聊天应用程序

import { io } from 'socket.io-client';

const socket = io(ServerEndURL);
export default function Chat (props) {
    const uuid = props.uuid;
    const classes = useStyles();
    const [open, setOpen] = useState(false);
    const [btnColor, setBtnColor] = useState('white');
    const [activeStatus, setActiveStatus] = useState('Offline');
    const [unreadMsgs, setUnreadMsgs] = useState(0);
    const [msgArr, setMsgArr] = useState([]);
    const chatBtnRef = useRef();
    const chatBoxRef = useRef();
    const msgInputRef = useRef();
    useEffect(() => {
        socket.emit('join', uuid);
        socket.emit('isActive', uuid);
        return () => {
            console.log('removed');
            socket.off('newMsg');
        }
    }, []);

    socket.on('newMsg', msg => {
        console.log('debug');
        let msgs = msgArr.map(msg => msg);
        msgs.push({
            type : 'received',
            msg,
        });
        setMsgArr(msgs);
        if(!open) setUnreadMsgs(unreadMsgs + 1);
        chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
    });

When the server fires the newMsg event on the socket, I want it to update the msgArr state with a new msg, but I see that the console.log('debug') statement runs more times than expected, and things don't work as I expect it to.当服务器在套接字上触发newMsg事件时,我希望它用新的 msg 更新msgArr state,但我看到console.log('debug')语句运行的次数比预期的要多,而且事情不起作用正如我所期望的那样。 I mean, it should only once right.我的意思是,它应该只对一次。 But it doesn't, it runs multiple times, and at some point, the "times" even reaches 40. Being new to react, I don't understand why this is happening.但它没有,它运行多次,在某些时候,“次”甚至达到 40。作为新的反应,我不明白为什么会发生这种情况。 So I would appreciate it if someone would shed some light on this problem, which has kept me awake for days now.因此,如果有人能对这个问题有所了解,我将不胜感激,这让我好几天都保持清醒。 I need help.我需要帮助。 Anyone?任何人?

It looks like you're trying to mutate state directly which you should never do .看起来您正在尝试直接突变 state ,这是您永远不应该做的。 Try this instead:试试这个:

import { io } from 'socket.io-client';

const socket = io(ServerEndURL);
export default function Chat (props) {
    const uuid = props.uuid;
    const classes = useStyles();
    const [open, setOpen] = useState(false);
    const [btnColor, setBtnColor] = useState('white');
    const [activeStatus, setActiveStatus] = useState('Offline');
    const [unreadMsgs, setUnreadMsgs] = useState(0);
    const [msgArr, setMsgArr] = useState([]);
    const chatBtnRef = useRef();
    const chatBoxRef = useRef();
    const msgInputRef = useRef();
    useEffect(() => {
        socket.emit('join', uuid);
        socket.emit('isActive', uuid);
        return () => {
            console.log('removed');
            socket.off('newMsg');
        }
    }, []);

    socket.on('newMsg', msg => {
        console.log('debug');
        
        // Note: This is a no-op if you don't need to extract info from `msg`
        let msgs = msgArr.map(msg => msg);

        // Update state directly with the new message
        setMsgArr([ ...msgs, { type: 'received', msg }]);

        if(!open) setUnreadMsgs(unreadMsgs + 1);
        chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
    });

Towards the second part of your question, you tell React what you want to be rendered into the DOM, and then it's responsible for actually updating the DOM on your behalf.对于问题的第二部分,您告诉 React 您希望将什么呈现到 DOM 中,然后它负责代表您实际更新 DOM。 Because of this, it may re-render your component more than once if it needs to (usually, it's when state has been updated).因此,如果需要,它可能会多次重新渲染您的组件(通常是在 state 已更新时)。 In your case, you define an event handler for newMsg events that updates the state so when this event handler is fired React re-renders your component (which executes the console.log('debug') statement).在您的情况下,您为更新 state 的newMsg事件定义事件处理程序,因此当触发此事件处理程序时,React 会重新呈现您的组件(执行console.log('debug')语句)。

My assumption is that you keep subscribing to your newMsg event every time react renders your component.我的假设是,每次 react 渲染组件时,您都会继续订阅 newMsg 事件。 If your component re-renders 40 times, you will have 40 subscriptions to your 'newMsg' event and it will run your event callback 40 times.如果您的组件重新渲染 40 次,您将有 40 次订阅您的“newMsg”事件,并且它将运行您的事件回调 40 次。 That said:那说:

Define your 'newMsg' event listener in a 'useEffect' hook instead.改为在 'useEffect' 挂钩中定义您的 'newMsg' 事件侦听器。

Either in your existing 'useEffect' as long as you're only proving an empty array ([]) as your second argument:只要您仅证明一个空数组([])作为您的第二个参数,就可以在您现有的“useEffect”中:

useEffect(() => {

  socket.on('newMsg', ...)

  socket.emit('join', uuid);
  socket.emit('isActive', uuid);
  return () => {
    console.log('removed');
    socket.off('newMsg');
  }
}, []);

or if your logic changes and you "monitor" changes in your original 'useEffect' you could simply place it in its own.或者,如果您的逻辑发生变化并且您“监控”原始“useEffect”中的变化,您可以简单地将其放在自己的位置。 (Example includes the message array) (示例包括消息数组)

const [msgArr, setMsgArr] = useState([]);
useEffect(() => {
  socket.on('newMsg', msg => {
    setMsgArr([ ...msgArr, { type: 'received', msg }]);
    //Alternative:
    //setMsgArr(msgArr.concat({ type: 'received', msg }));
  })
}, []);

The second argument in 'useEffect' simply tells react to re-run the effect if something in your second argument changes. 'useEffect' 中的第二个参数只是告诉 react 如果您的第二个参数中的某些内容发生更改,则重新运行效果。 However, you only want to subscribe once, thus providing an empty array so it will never see a change and re-run the effect.但是,您只想订阅一次,因此提供了一个空数组,因此它永远不会看到更改并重新运行效果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM