리 액트에서 렌더 루프 제한 수정 방법

Nov 28 2020

현재 react 및 Firebase 실시간 데이터베이스를 사용하여 채팅 앱을 구축하고 있습니다.

usecallback 후크 및 사용자 상태로 인해 최대 제한 렌더링에 문제가 있습니다 (후크는 addOnConnectLister addOnDisConnectListner입니다).

이것은 코드입니다.

import React, { useEffect, useState, useCallback, useRef } from "react";
import { Icon, Menu } from "semantic-ui-react";
import firebase from "../../firebase";

const DirectMessages = (props) => {
  const [usersRef] = useState(firebase.database().ref("users"));
  const [connectedRef] = useState(firebase.database().ref(".info/connected"));
  const [presenceRef] = useState(firebase.database().ref("presence"));
  const [users, setUsers] = useState([]);

  //add presence listner
  //create a presence record for current user
  const addPressenceListner = useCallback(
    (currentUserID) => {
      connectedRef.on("value", (snap) => {
        console.log("connecting snap value", snap.val());
        if (snap.val()) {
          const ref = presenceRef.child(currentUserID);
          ref.set(true);
       ref.onDisconnect().remove((err) => {
            if (err) console.log(err);
          });
        }
      });
    },
    [connectedRef, presenceRef]
  );


  //init connected users
  const initUsers = useCallback(
    (currentUserID) => {
      const loadedUsers = [];
      usersRef.on("child_added", (snap) => {
        if (currentUserID !== snap.key) {
          let user = snap.val();
          user["uid"] = snap.key;
          user["status"] = "offline";
          loadedUsers.push(user);
          setUsers([...loadedUsers]);
        }
      });
    },
    [usersRef]
  );

  //update user status when go online
  const addOnConnectListner = useCallback(() => {
    presenceRef.on("child_added", (snap) => {
      const updatedUsers = users.map((user) => {
        if (user.uid === snap.key) {
          console.log("user connected", user);
          user["status"] = "online";
        }
        return user;
      });
      setUsers(...[updatedUsers]);
    });
  }, [presenceRef, users]);

  //update user status when go offline
  const addOnDisconnectListner = useCallback(() => {
    presenceRef.on("child_removed", (snap) => {
      const updatedUsers = users.map((user) => {
        if (user.uid === snap.key) {
          console.log("user disconnected", user);
          user["status"] = "offline";
        }
        return user;
      });
      setUsers(updatedUsers);
    });
  }, [users, presenceRef]);

  const addListners = useCallback(
    (currentUserID) => {
      addPressenceListner(currentUserID);
      initUsers(currentUserID);
      addOnConnectListner();
      addOnDisconnectListner();
    },
    [
      addOnConnectListner,
      addOnDisconnectListner,
      addPressenceListner,
      initUsers,
    ]
  );

  useEffect(() => {
    if (props.currentUser) {
      addListners(props.currentUser.uid);
    }
    return () => {
      presenceRef.off();
      usersRef.off();
      connectedRef.off();
    };
  }, [props.currentUser, addListners, connectedRef, presenceRef, usersRef]);

  const isUserOnline = (user) => user.status === "online";

  return (
    <Menu.Menu className="menu" style={{ paddingTop: "2em" }}>
      <Menu.Item>
        <span>
          <Icon name="mail" /> Direct Messages
        </span>{" "}
        ({users.length})
      </Menu.Item>

      {users.map((user) => (
        <Menu.Item key={user.uid}>
          <Icon name="circle" color={isUserOnline(user) ? "green" : "red"} />@
          {user.username}
        </Menu.Item>
      ))}
    </Menu.Menu>
  );
};

export default DirectMessages;

검색 후 문제를 피하기 위해 사용자 상태에 대한 참조를 얻기 위해 useRef를 사용하는 솔루션을 찾았지만 올바르게 작동하지 않는 콜백은 한 번만 실행됩니다

import React, { useEffect, useState, useCallback, useRef } from "react";
import { Icon, Menu } from "semantic-ui-react";
import firebase from "../../firebase";

const DirectMessages = (props) => {
  const [usersRef] = useState(firebase.database().ref("users"));
  const [connectedRef] = useState(firebase.database().ref(".info/connected"));
  const [presenceRef] = useState(firebase.database().ref("presence"));
  const [users, setUsers] = useState([]);

  //users state ref
  const getUsersRef = useRef(users);

  useEffect(() => {
    getUsersRef.current = users;
  }, [users]);

  //add presence listner
  //create a presence record for current user
  const addPressenceListner = useCallback(
    (currentUserID) => {
      connectedRef.on("value", (snap) => {
        console.log("connecting snap value", snap.val());
        if (snap.val()) {
          const ref = presenceRef.child(currentUserID);
          ref.set(true);
          ref.onDisconnect().remove((err) => {
            if (err) console.log(err);
          });
        }
      });
    },
    [connectedRef, presenceRef]
  );


  //init connected users
  const initUsers = useCallback(
    (currentUserID) => {
      const loadedUsers = [];
      usersRef.on("child_added", (snap) => {
        if (currentUserID !== snap.key) {
          let user = snap.val();
          user["uid"] = snap.key;
          user["status"] = "offline";
          loadedUsers.push(user);
          setUsers([...loadedUsers]);
        }
      });
    },
    [usersRef]
  );

  //update user status when go online
  const addOnConnectListner = useCallback(() => {
    presenceRef.on("child_added", (snap) => {
      const updatedUsers = getUsersRef.current.map((user) => {
        if (user.uid === snap.key) {
          console.log("user connected", user);
          user["status"] = "online";
        }
        return user;
      });
      setUsers(...[updatedUsers]);
    });
  }, [presenceRef, getUsersRef]);

  //update user status when go offline
  const addOnDisconnectListner = useCallback(() => {
    presenceRef.on("child_removed", (snap) => {
      const updatedUsers = getUsersRef.current.map((user) => {
        if (user.uid === snap.key) {
          console.log("user disconnected", user);
          user["status"] = "offline";
        }
        return user;
      });
      setUsers(updatedUsers);
    });
  }, [getUsersRef, presenceRef]);

  const addListners = useCallback(
    (currentUserID) => {
      addPressenceListner(currentUserID);
      initUsers(currentUserID);
      addOnConnectListner();
      addOnDisconnectListner();
    },
    [
      addOnConnectListner,
      addOnDisconnectListner,
      addPressenceListner,
      initUsers,
    ]
  );

  useEffect(() => {
    if (props.currentUser) {
      addListners(props.currentUser.uid);
    }
    return () => {
      presenceRef.off();
      usersRef.off();
      connectedRef.off();
    };
  }, [props.currentUser, addListners, connectedRef, presenceRef, usersRef]);

  const isUserOnline = (user) => user.status === "online";

  return (
    <Menu.Menu className="menu" style={{ paddingTop: "2em" }}>
      <Menu.Item>
        <span>
          <Icon name="mail" /> Direct Messages
        </span>{" "}
        ({users.length})
      </Menu.Item>

      {users.map((user) => (
        <Menu.Item key={user.uid}>
          <Icon name="circle" color={isUserOnline(user) ? "green" : "red"} />@
          {user.username}
        </Menu.Item>
      ))}
    </Menu.Menu>
  );
};

export default DirectMessages;

답변

SomeoneSpecial Nov 29 2020 at 13:08

첫째, 상태로 필요하지 않습니다.

  const [usersRef] = useState(firebase.database().ref("users"));
  const [connectedRef] = useState(firebase.database().ref(".info/connected"));
  const [presenceRef] = useState(firebase.database().ref("presence"));

구성 요소 외부에 설정하십시오.

const usersRef = firebase.database().ref("users")
const connectedRef = firebase.database().ref(".info/connected")
const presenceRef = firebase.database().ref("presence")

const DirectMessages = (props) => {
......

둘째, 청취자를 제거하는 올바른 방법입니다.

 useEffect(() => {
    if (props.currentUser) {
      addListners(props.currentUser.uid);
    return () => {
      presenceRef.off('child_added');
      presenceRef.off('child_removed');
      usersRef.off('child_added');
      connectedRef.off('value');
    };
    }

  }, [props.currentUser, addListners, connectedRef, presenceRef, usersRef]);

다음을 변경하겠습니다

  const [users, setUsers] = useState([]);

  //users state ref
  const getUsersRef = useRef(users);

  useEffect(() => {
    getUsersRef.current = users;
  }, [users]);

...에

  const [users, _setUsers] = useState([]);
  const getUsersRef = useRef(users);

  const setUsers = (array) => {
        getUsersRef.current = array;
        _setUsers(array)
  }

마지막으로 usecallback은 불필요하므로 일반 함수로 만들어야합니다.

  const addOnDisconnectListner = () => {
    presenceRef.on("child_removed", (snap) => {
      const updatedUsers = getUsersRef.current.map((user) => {
        if (user.uid === snap.key) {
          console.log("user disconnected", user);
          user["status"] = "offline";
        }
        return user;
      });
      setUsers(updatedUsers);
    }
B.Fateh Nov 29 2020 at 14:51

이것을 변경 :

  const [users, setUsers] = useState([]);

  //users state ref
  const getUsersRef = useRef(users);

  useEffect(() => {
    getUsersRef.current = users;
  }, [users]);

이에:

  //users state ref
  const getUsersRef = useRef(users);

  useEffect(() => {
    getUsersRef.current = users;
  }, [users]);

후크 종속성을 업데이트 한 후 :

import React, { useEffect, useState, useCallback, useRef } from "react";
import { Icon, Menu } from "semantic-ui-react";
import firebase from "../../firebase";

const usersRef = firebase.database().ref("users");
const connectedRef = firebase.database().ref(".info/connected");
const presenceRef = firebase.database().ref("presence");

const DirectMessages = (props) => {
  const [users, _setUsers] = useState([]);

  //get users ref
  const getUsersRef = useRef(users);

  const setUsers = (array) => {
    getUsersRef.current = array;
    _setUsers(array);
  };

  //add presence listner
  //create a presence record for current user
  const addPressenceListner = useCallback((currentUserID) => {
    connectedRef.on("value",(snap) => {
      console.log("connecting snap value", snap.val());
      if (snap.val()) {
        const ref = presenceRef.child(currentUserID);
        ref.set(true);
        ref.onDisconnect().remove((err) => {
          if (err) console.log(err);
        });
      }
    });
  }, []);

  //init connected users
  const initUsers = useCallback((currentUserID) => {
    const loadedUsers = [];
    usersRef.on("child_added", (snap) => {
      if (currentUserID !== snap.key) {
        let user = snap.val();
        user["uid"] = snap.key;
        user["status"] = "offline";
        loadedUsers.push(user);
        setUsers([...loadedUsers]);
      }
    });
  }, []);

  //update user status when go online
  const addOnConnectListner = useCallback(() => {
    presenceRef.on("child_added", (snap) => {
      const updatedUsers = getUsersRef.current.map((user) => {
        if (user.uid === snap.key) {
          user["status"] = "online";
        }
        console.log("user", user);
        return user;
      });
      setUsers(...[updatedUsers]);
    });
  }, []);

  //update user status when go offline
  const addOnDisconnectListner = useCallback(() => {
    presenceRef.on("child_removed", (snap) => {
      const updatedUsers = getUsersRef.current.map((user) => {
        if (user.uid === snap.key) {
          console.log("user disconnected", user);
          user["status"] = "offline";
        }
        return user;
      });
      setUsers(updatedUsers);
    });
  }, [getUsersRef]);

  const addListners = useCallback(
    (currentUserID) => {
      addPressenceListner(currentUserID);
      initUsers(currentUserID);
      addOnConnectListner();
      addOnDisconnectListner();
    },
    [
      addOnConnectListner,
      addOnDisconnectListner,
      addPressenceListner,
      initUsers,
    ]
  );

  useEffect(() => {
    if (props.currentUser) {
      addListners(props.currentUser.uid);
    }
    return () => {
      presenceRef.off();
      usersRef.off();
      connectedRef.off();
    };
  }, [props.currentUser, addListners]);

  const isUserOnline = (user) => user.status === "online";

  return (
    <Menu.Menu className="menu" style={{ paddingTop: "2em" }}>
      <Menu.Item>
        <span>
          <Icon name="mail" /> Direct Messages
        </span>{" "}
        ({users.length})
      </Menu.Item>

      {users.map((user) => (
        <Menu.Item key={user.uid}>
          <Icon name="circle" color={isUserOnline(user) ? "green" : "red"} />@
          {user.username}
        </Menu.Item>
      ))}
    </Menu.Menu>
  );
};

export default DirectMessages;