使用 socket.io 通过 React 获取数据的正确方法

Right way to fetch data with react using socket.io

我没有找到任何关于如何使用 react with socket.io 从快速服务器获取数据的示例。

现在我做这样的事情: Server.js

io.on('connection', socket => {
  console.log(socket.id)

  socket.on('disconnect', () => {
    console.log(socket.id + ' disconnected')
  })

  socket.on('load settings', () => {
    socket.emit('settings is here', data)
  })
})

React.js

const [socket] = useState(io())
  const [settings, setSettings] = useState(false)

   useEffect(() => {
    try {
      socket.emit('load settings');

      socket.on('settings is here', (data) => {
        // we get settings data and can do something with it
        setSettings(data)
      })
    } catch (error) {
      console.log(error)
    }
  }, [])

这看起来不错,但有些地方您可以改进,例如在卸载之前断开套接字,并且不使套接字成为状态的一部分(请参阅下面的代码示例)。

如果您对如何将现有代码移植到挂钩感到困惑,请先使用 类 编写组件,然后逐个移植到挂钩。您可以将此 称为备忘单。

使用传统的 类,使用 socket.io 看起来像:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.socket = io();
  }

  componentDidMount() {
    this.socket.open();
    this.socket.emit('load settings');
    this.socket.on('settings is here', (data) => {
      // we get settings data and can do something with it
      this.setState({
        settings: data,
      })
    });
  }

  componentWillUnmount() {
    this.socket.close();
  }

  render() {
    ...
  }
}

然后您可以移植 this.socket 以使用 useRef(它不需要成为 state 的一部分,因为您的 render() 函数不需要它. 所以 useRef 是一个更好的选择(尽管 useState 可能仍然有效)。

Port componentDidMount() 通过使用 useEffect 并传递一个空数组作为第二个参数,使效果回调仅 运行 挂载。

Port componentWillUnmount() 通过在 useEffect 回调中返回一个回调函数,React 将在卸载前调用该回调函数。

function App() {
  const socketRef = useRef(null);
  const [settings, setSettings] = useState(false);

  useEffect(() => {
    if (socketRef.current == null) {
      socketRef.current = io();
    }

    const {current: socket} = socketRef;

    try {
      socket.open();
      socket.emit('load settings');
      socket.on('settings is here', (data) => {
        // we get settings data and can do something with it
        setSettings(data);
      })
    } catch (error) {
      console.log(error);
    }
    // Return a callback to be run before unmount-ing.
    return () => {
      socket.close();
    };
  }, []); // Pass in an empty array to only run on mount.

  return ...;
}

接受的答案有一个缺点,即 useRef() 的初始状态在每次重新渲染时都会被调用。例如,对于文本输入,每次输入更改时都会建立一个新连接。我想出了两个解决方案:

  1. 在useEffect中定义socket
const ChatInput = () => {
  const [chatMessage, setChatMessage] = useState<string>('');
  const socket = useRef<Socket>();

  useEffect(() => {
    socket.current = io('my api');
    socket.current.on('chat message', (message: string) => {
      setChatMessage(message);
    });
    return () => { socket.current?.disconnect(); };
  }, []);

  const inputHandler = (text: string) => {
    socket.current?.emit('chat message', text);
  };

  return (
    <View>
      <Text>{chatMessage}</Text>
      <TextInput onChangeText={(text) => inputHandler(text)} />
    </View>
  );
};
  1. 在 useState()
  2. 中定义 socket.io
const ChatInput = () => {
  const [chatMessage, setChatMessage] = useState<string>('');
  const [socket] = useState(() => io('my api'));

  useEffect(() => {
    socket.on('chat message', (message: string) => {
      setChatMessage(message);
    });
    return () => { socket.disconnect(); };
  }, []);

  const inputHandler = (text: string) => {
    socket.emit('chat message', text);
  };

  return (
    <View>
      <Text>{chatMessage}</Text>
      <TextInput onChangeText={(text) => inputHandler(text)}/>
    </View>
  );
};

export default ChatInput;