React UseEffect setState 不会重新渲染

时间:2021-04-14 07:25:59

标签: reactjs use-effect server-sent-events

我正在 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 :(得分: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 <>...</>;
}