总的来说,根据您的需要,我认为这种方法没有任何问题。我将在这里提供一个可操作的示例。我的示例将假设 TypeScript,因为它比其他方式更容易转换为 JavaScript(如果您不使用 TypeScript)。
关于您的第一个问题,我建议您在应用程序的任何地方都使用 Websocket 连接并将其作为上下文传递,并创建自定义挂钩以在任何地方使用该连接:
import React, { createContext, FunctionComponent, ReactNode, useContext, useEffect, useMemo, useState } from 'react';
import io from 'socket.io-client';
export const WebsocketContext = createContext<SocketIOClient.Socket | null>(null);
const WebsocketProvider: FunctionComponent<{ children: ReactNode }> = ({ children }: { children: ReactNode }) => {
const [connection, setConnection] = useState<SocketIOClient.Socket | null>(null);
const options: SocketIOClient.ConnectOpts = useMemo(() => ({}), []);
useEffect(() => {
try {
const socketConnection = io(process.env.BASE_URL || '127.0.0.1', options);
setConnection(socketConnection);
} catch (err) {
console.log(err);
}
}, [options]);
return <WebsocketContext.Provider value={connection}>{children}</WebsocketContext.Provider>;
};
export const useWebsocket = (): SocketIOClient.Socket | null => {
const ctx = useContext(WebsocketContext);
if (ctx === undefined) {
throw new Error('useWebsocket can only be used inside WebsocketContext');
}
return ctx;
};
export default WebsocketProvider;
在上面我们创建了类型为SocketIOClient.Socket 并且默认为null 的上下文,因为当连接尚未准备好时,我们必须分配默认值。然后我们将 Websocket 提供程序创建为FunctionComponent,它接受子级并保持连接状态,useState 钩子最终返回提供 Websocket 连接的提供程序。我还提到SocketIOClient.ConnectOpts,根据您的需要,您可能希望提供连接选项;使用钩子时静态或动态。此外,useEffect 钩子会尝试建立连接或抛出错误。唯一会重新运行此钩子的依赖项是连接选项,以防它们动态更改。
最后我们有了自定义钩子useWebsocket,我们可以将其导入任何组件并在上下文提供程序中使用。只需使用上下文提供程序包装您的根组件(或任何其他层次结构级别)以提供上下文,如下例所示:
import React, { FunctionComponent } from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import { v4 as uuid } from 'uuid';
import routes from './App.routes';
import WebsocketProvider from './websocket.context';
const App: FunctionComponent = () => {
return (
<WebsocketProvider>
<Router>
<Switch>
{routes.map((route) => (
<Route key={uuid()} {...route} />
))}
</Switch>
<Redirect to='/' />
</Router>
</WebsocketProvider>
);
};
export default App;
关于您的第二个问题,例如,您可以使用“useEffect”钩子在连接发出时做出反应并更新您的 Redux(或其他全局状态管理)存储。在这里,我还使用 Elvis 运算符来检查连接是否还没有准备好(如果它还没有准备好,因为null useEffect 钩子将在 socket 连接准备就绪时重新呈现):
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useWebsocket } from './websocket.context';
const Foo: FunctionComponent = () => {
const dispatch = useDispatch();
const socket = useWebsocket();
useEffect(() => {
socket?.on('myEmitEvent', (data: myEmitData) => {
dispatch(myStoreAction(data));
});
return () => {
socket?.off('myEmitEvent');
};
}, [socket, dispatch]);
return ...
};
export default Foo;
关于您的第三个问题,正如您提到的,您可以使用 useEffect 钩子或更简单的 useSelector 钩子来自 react-redux 包,它会自动捕获您的状态更改,触发必要时重新渲染元素。
简而言之,您的想法恰到好处,我希望通过这个简短的可操作示例,您将能够改进适合您的解决方案。