React UseEffect setState不会重新渲染

3

我正在尝试在React上使用SSE,一切都很顺利,但是组件没有重新渲染。

解决方案在下面

以下是代码:

import React, { useEffect, useState } from "react";
import axios from "axios";

function App() {
  const [streamMessages, setStreamMessages] = useState([]);
  const [eventSource, setEventSource] = useState(null);

  const fetchData = async () => {
    const msgs = await axios.get("http://localhost:3008/message/all/abc123");
    setStreamMessages(msgs.data);
  };

  useEffect(() => {
    fetchData();
    setEventSource(new EventSource("http://localhost:3008/message/abc123"));

    return () => {
      if (eventSource) {
        eventSource.close();
        console.log("eventsource closed");
      }
    };
  }, []);

  useEffect(() => {
    if (eventSource) {
      eventSource.onopen = (event) => {
        console.log("connection opened");
      };

      eventSource.onmessage = (event) => {
        console.log("result", event.data);
        setStreamMessages((old) => [...old, event.data]);
      };

      eventSource.onerror = (event) => {
        console.log(event.target.readyState);
        if (event.target.readyState === EventSource.CLOSED) {
          console.log("eventsource closed (" + event.target.readyState + ")");
        }
        eventSource.close();
      };
    }
  }, [eventSource, streamMessages]);

  return (
    <div className="App">
      <input type="text" />
      <button>Envoyer</button>
      <div>
        {streamMessages.map((message, index) => (
          <p key={index}>
            {message.message} <em>{message.createdAt}</em>
          </p>
        ))}
      </div>
    </div>
  );
}

export default App;

第一个 useEffect 用于获取“初始数据”并将其放入状态中。

const fetchData = async () => {
    const msgs = await axios.get("http://localhost:3008/message/all/abc123");
    setStreamMessages(msgs.data);
  };

这个工作正常,我可以看到消息列表。

第二个 useEffect 应该会捕获传入的消息并将它们添加到初始消息数组中。

useEffect(() => {
    if (eventSource) {
      eventSource.onopen = (event) => {
        console.log("connection opened");
      };

      eventSource.onmessage = (event) => {
        console.log("result", event.data);
        setStreamMessages((old) => [...old, event.data]);
      };

      eventSource.onerror = (event) => {
        console.log(event.target.readyState);
        if (event.target.readyState === EventSource.CLOSED) {
          console.log("eventsource closed (" + event.target.readyState + ")");
        }
        eventSource.close();
      };
    }
  }, [eventSource, streamMessages]);

我可以看到"console.log",这意味着事件被成功捕获,但我的消息列表'streamMessages'没有重新渲染。 我把这个状态'streamMessages'作为useEffect的依赖项。

我不知道代码哪里有问题

解决方案如下:

好的,我终于找到解决方案了。 我应用了Dennis Vash的一些修复(感谢您)。 但是主要的问题是我忘记对服务器发送的数据进行JSON解析。

以下是完整的、可工作的代码:

import React, { useEffect, useState } from "react";
import axios from "axios";

function App() {
  const [streamMessages, setStreamMessages] = useState([]);
  const [eventSource, setEventSource] = useState(
    () => new EventSource("http://localhost:3008/message/abc123")
  );

  useEffect(() => {
    const fetchData = async () => {
      const msgs = await axios.get("http://localhost:3008/message/all/abc123");
      setStreamMessages(msgs.data);
    };

    fetchData();

    return () => {
      if (eventSource) {
        eventSource.close();
        console.log("eventsource closed");
      }
    };
  }, []);

  useEffect(() => {
    if (eventSource) {
      eventSource.onopen = (event) => {
        console.log("connection opened");
      };

      eventSource.onmessage = (event) => {
        setStreamMessages((old) => [...old, JSON.parse(event.data)]);
      };

      eventSource.onerror = (event) => {
        console.log(event.target.readyState);
        if (event.target.readyState === EventSource.CLOSED) {
          console.log("eventsource closed (" + event.target.readyState + ")");
        }
        eventSource.close();
      };
    }
  }, [eventSource, streamMessages]);

  return (
    <div className="App">
      <input type="text" />
      <button>Envoyer</button>
      <div>
        {streamMessages.map((message, index) => (
          <p key={index}>
            {message.message} <em>{message.createdAt}</em>
          </p>
        ))}
      </div>
    </div>
  );
}

export default App;
1个回答

0

无法保证其能正常工作,因为服务器上可能存在错误(没有可重现的示例)。

因此,我更正了几个错误,此处有两个片段,一个能正常运行,另一个带有注释的包含错误。

已修复:

function App() {
  const [streamMessages, setStreamMessages] = useState([]);
  const [eventSource, setEventSource] = useState(
    () => new EventSource("http://localhost:3008/message/abc123")
  );

  // Fetch data init
  useEffect(() => {
    const fetchData = async () => {
      const msgs = await axios.get("http://localhost:3008/message/all/abc123");
      setStreamMessages(msgs.data);
    };

    fetchData();
  }, []);

  // Eventsource setup
  useEffect(() => {
    if (eventSource) {
      eventSource.onopen = (event) => {
        console.log("connection opened");
      };

      eventSource.onmessage = (event) => {
        console.log("result", event.data);
        setStreamMessages((old) => [...old, event.data]);
      };

      eventSource.onerror = (event) => {
        console.log(event.target.readyState);
        if (event.target.readyState === EventSource.CLOSED) {
          console.log("eventsource closed (" + event.target.readyState + ")");
        }
        eventSource.close();
      };
    }
  }, [eventSource]);

  return <>...</>;
}

注释中的错误(至少我认为是错误):

function App() {
  const [streamMessages, setStreamMessages] = useState([]);
  const [eventSource, setEventSource] = useState(null);

  // should be in scope of useEffect callback
  // not a mistake in this case, but usually has a closure on some stale data
  const fetchData = async () => {
    const msgs = await axios.get("http://localhost:3008/message/all/abc123");
    setStreamMessages(msgs.data);
  };

  useEffect(() => {
    fetchData();

    // unnecessary render, should be as initial value
    setEventSource(new EventSource("http://localhost:3008/message/abc123"));

    return () => {
      // closure on eventSource == null value
      // Should get lint warning here
      if (eventSource) {
        eventSource.close();
        console.log("eventsource closed");
      }
    };
  }, []);

  useEffect(() => {
    // always truthy on streamMessages change
    // therefore will reinit on every streamMessages render

    // should run only once
    if (eventSource) {
      eventSource.onopen = (event) => {
        console.log("connection opened");
      };

      eventSource.onmessage = (event) => {
        console.log("result", event.data);
        setStreamMessages((old) => [...old, event.data]);
      };

      eventSource.onerror = (event) => {
        console.log(event.target.readyState);
        if (event.target.readyState === EventSource.CLOSED) {
          console.log("eventsource closed (" + event.target.readyState + ")");
        }
        eventSource.close();
      };
    }
    // mistake, no need streamMessages in dep array
    // should get lint warning here
  }, [eventSource, streamMessages]);

  return <>...</>;
}

感谢您的回答和支持。不幸的是,它仍然无法正常工作,没有任何改变。 - barden
1
如何创建一个最小化、可复现的示例,尝试创建一个沙盒。 - Dennis Vash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接