如何让useEffect在数据改变后重新渲染?

How to make useEffect rerender after the data is changed?

我希望 useEffect 获取并呈现 rooms 数据。并在将新房间添加到 rooms

时重新渲染数据

使用 useEffect 和空依赖项获取数据和显示数据的代码。

const [rooms, setRooms] = useState([]);

  const getRoomsData = async () => {
    const querySnapshot = await getDocs(collection(db, "rooms"));
    const data = querySnapshot.docs.map((doc) => ({
      id: doc.id,
      data: doc.data(),
    }));

    setRooms(data);
  };

  useEffect(() => {
    getRoomsData();
    console.log("rerendered");
  }, []);

使用此代码,在我向 rooms 添加新房间后。我需要手动刷新页面才能看到呈现的新数据。在向 rooms 添加新“房间”后,我应该怎么做才能使其重新呈现?

添加房间的代码:

    const roomName = prompt("please enter name for chat room");

    if (roomName) {
      addDoc(collection(db, "rooms"), {
        name: roomName,
      });
    }
  };

我尝试将房间添加到 useEffect 依赖项中,得到了我想要的结果(无需刷新),但这只是因为它正在无限渲染,这很糟糕。正确的做法是什么?

您正在无限重新渲染,因为 useEffect 中的函数正在更新效果依赖数组中的 rooms 的状态。这将始终导致无限重新渲染。

逐字回答您的问题: “如何让它仅在数据更改时重新呈现?”

由于 roomsdata 设置相同,您可以保持 useEffect 原样,然后创建另一个 useEffect 仅在组件挂载调用 getRoomsData().

const [rooms, setRooms] = useState([]);

  const getRoomsData = async () => {
    const querySnapshot = await getDocs(collection(db, "rooms"));
    const data = querySnapshot.docs.map((doc) => ({
      id: doc.id,
      data: doc.data(),
    }));

    setRooms(data);
  };

  useEffect(() = > {
    getRoomsData();
  }, [])

  useEffect(() => {
    console.log("rerendered");
  }, [rooms]);

我认为解决问题的真正关键在于知道何时调用 getRoomsData(),因为根据此,您将更改 useEffect 依赖项数组以满足该需要。

使用 useEffect 获取数据时,需要进行同步调用。从 Firestore 请求数据将是异步的,因此为了解决这个问题,我们可以添加一个函数来包装名为 getRoomsData 的 firestore 调用,如下所示:

const [rooms, setRooms] = useState([]);



  useEffect(() = > {
    const getRoomsData = async () => {
    const querySnapshot = await getDocs(collection(db, "rooms"));
    const data = querySnapshot.docs.map((doc) => ({
      id: doc.id,
      data: doc.data(),
    }));
 setRooms(data);
 
  };
    getRoomsData();
  }, [])

我可以想到两种方法来解决这个问题,而不必使用 useEffect,第一种方法是一种变通方法,您可以在本地更新 rooms 状态,而不必从服务器,也就是说,你基本上是在构造一个对象并将其附加到 rooms 的现有数组中,这只有在你知道对象的结构时才有可能,看看你的 getRoomsData 函数,似乎你正在返回一个数据数组,每个项目都具有以下对象结构。

{
 id: <DOCUMENT_ID>,
 data: {
  name: <ROOM_NAME>
 }
}

所以,当你添加一个房间时,你可以这样做:

   const roomName = prompt("please enter name for chat room");

    if (roomName) {
      addDoc(collection(db, "rooms"), {
        name: roomName,
      });
      let newRoom = {
        id: Math.random(), //random id
        data: {
          name: roomName
        }
      }
      setRooms([...rooms,newRoom])
  };

这样,您还可以减少网络调用的次数,这是一个性能奖励,但唯一的缺点是您必须确定对象结构,以便相应地绘制结果。
如果您不确定对象结构,您可以做的是在将新房间添加到集合后调用 getRoomsData,如下所示:

   const roomName = prompt("please enter name for chat room");

    if (roomName) {
      addDoc(collection(db, "rooms"), {
        name: roomName,
      }).then(res=>{
          getRoomsData();
      })
  };

这将确保获取最新数据(包括最近添加的房间)并使用所有结果呈现您的组件。 在您遵循的任何一种方法中,当组件使用空依赖项数组安装时,您只能使用一次 useEffect

 useEffect(() => {
    getRoomsData();
    //console.log("rerendered");
  }, []);

通过这样做,当用户第一次访问此页面时,您首先基本上会显示数据 time/when 组件是第一次安装。后续添加时,可以将新添加的room作为对象添加到数据库后追加到rooms数组中,也可以将其添加到数据库中并再次获取所有结果,以便最新的添加呈现在屏幕上。