【问题标题】:Why is my react element rendering many times?为什么我的反应元素渲染多次?
【发布时间】:2021-06-04 15:52:05
【问题描述】:

问题:我有以下反应组件。基本上,我正在构建一个与 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;
    });

当服务器在套接字上触发 newMsg 事件时,我希望它用新的 msg 更新 msgArr 状态,但我看到 console .log('debug') 语句运行的次数比预期的要多,而且事情并没有像我预期的那样工作。我的意思是,它应该只对一次。但它没有,它运行多次,在某些时候,“次”甚至达到 40。作为新的反应,我不明白为什么会发生这种情况。因此,如果有人能对这个问题有所了解,我将不胜感激,这让我好几天没睡了。我需要帮助。有人吗?

【问题讨论】:

    标签: javascript node.js reactjs socket.io


    【解决方案1】:

    看起来您正在尝试直接改变状态,而您应该永远不要这样做。试试这个:

    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;
        });
    

    对于问题的第二部分,您告诉 React 您希望将什么呈现到 DOM 中,然后它负责代表您实际更新 DOM。因此,如果需要,它可能会多次重新渲染您的组件(通常是在状态更新时)。在您的情况下,您为 newMsg 事件定义了一个更新状态的事件处理程序,因此当这个事件处理程序被触发时,React 会重新渲染您的组件(它执行 console.log('debug') 语句)。

    【讨论】:

    • 谢谢,我没有改变状态。但是从您的回答中学到了一个很酷的技巧。但我认为它对我的代码没有太大的改变,问题仍然存在。我不知道该怎么办。
    【解决方案2】:

    我的假设是,每次 react 渲染组件时,您都会继续订阅 newMsg 事件。如果您的组件重新渲染 40 次,您将有 40 次订阅您的“newMsg”事件,并且它将运行您的事件回调 40 次。也就是说:

    改为在“useEffect”挂钩中定义“newMsg”事件侦听器。

    在您现有的“useEffect”中,只要您只证明一个空数组 ([]) 作为您的第二个参数:

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

    或者,如果您的逻辑发生变化并且您“监控”原始“useEffect”中的变化,您可以简单地将其放在自己的位置。 (示例包括消息数组)

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

    'useEffect' 中的第二个参数只是告诉 react 如果您的第二个参数中的某些内容发生更改,则重新运行效果。但是,您只想订阅一次,因此提供了一个空数组,因此它永远不会看到更改并重新运行效果。

    【讨论】:

    • 我知道这是问题的一部分。我以前做过,即将它保存在 useEffect 中,它解决了多次重新渲染,但又发生了一个错误。我正在向我的 msgArr 数组添加一个新的 msg,但这现在不起作用。每次触发 newMsg 事件时,似乎都向视图传递了一个新的 msgArr 实例,它不记得以前的 msgs。我想我得再看看它。
    • @Sam Gomena 已经回答了那部分。使用他的示例更新您的数组: setMsgArr([ ...msgArr, { type: 'received', msg }]);在您的事件回调中 -> 我已经编辑了我的答案
    • 不,它没有用。问题仍然存在。每次收到 'newMsg' 事件时,该数组只有一个消息。所有其他消息都清除了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-01
    • 1970-01-01
    • 2018-09-09
    • 1970-01-01
    • 2023-03-21
    • 1970-01-01
    • 2020-10-01
    相关资源
    最近更新 更多