如何使用 Context API 将单个 Socket IO 实例传递给页面?

Posted

技术标签:

【中文标题】如何使用 Context API 将单个 Socket IO 实例传递给页面?【英文标题】:How to pass single Socket IO Instance to pages with Context API? 【发布时间】:2019-12-02 10:22:23 【问题描述】:

所以我试图在整个应用程序中使用一个 Socket IO 实例并从多个组件访问它。

我已经搜索了很多,也尝试了很多......没有任何成功。

现在我正在使用 React Context API。

我在SocketProvider.js 中初始化我的SocketContext

import React from 'react'

const SocketContext = React.createContext()

export default SocketContext

打开 Socket 并在App.js 中提供它:

import React from 'react';
import Main from './components/Main/Main';
import io from 'socket.io-client';
import SocketContext from './services/SocketProvider'


function App() 
  const socket = io("http://localhost:8000")
  return (
    <div className="App">
      <SocketContext.Provider value=socket>
      <MuiPickersUtilsProvider>
        <MuiThemeProvider >
          <Main />
        </MuiThemeProvider>
      </MuiPickersUtilsProvider>
      </SocketContext.Provider>
    </div>
  );


export default App;

Main.js中的路由:


function Main() 

  return (
    <main>
      <Switch>
        <Route exact path="/" component=Menu />

        <Route path="/panorama" component=Panorama />
        <Route path="/movie" component=Movie />

        <Route
          path="/timelapse"
          render=( match:  url  ) => (
            <>
              <Route path=`$url/` component=Timelapse exact />
              <Route path=`$url/running` render=(props) => <Running ...props type='timelapse' /> />

            </>
          )
        />

      </Switch>
    </main>
  );



export default Main;

并在这两个组件中使用它,Timelapse.js:

import React from 'react';
import SocketContext from '../../services/SocketProvider';
import TimelapsePoints from '../../components/TimelapsePoints/TimelapsePoints';
import TimelapseCamControls from '../../components/TimelapseCamControls/TimelapseCamControls'


class Timelapse extends React.Component 
    constructor(props) 
        super(props);

        let socket = props.socket;

        this.state = 
            hasCamera: false,
            cameraActive: false,
            brightnessControl: false,
            camControlsOk: false,
            pointsOk: false,
            interval: 10,
            recordingTime: 6000,
            movieTime: 12
        ;

        socket.on('hasCamera', data => 
            this.setState(
                cameraActive: data.hasCamera
            )
            this.setState(
                hasCamera: data.hasCamera
            )
        )
    


    render() 
        return (<div >
            
                this.state.cameraActive &&
                <TimelapseCamControls brightnessControl=
                        this.state.brightnessControl
                    
                    toggleBrightnesscontrol=
                        this.toggleBrightnesscontrol
                    
                    camControlsOk=
                        this.camControlsOk
                    
                    socket=
                        this.props.socket
                    
                />
             
                (!this.state.brightnessControl || (this.state.brightnessControl && this.state.camControlsOk)) &&


        <TimelapsePoints socket=this.props.socket handlePointsChange=this.pointsOk/>

                this.state.pointsOk && this.camControlsOk &&
                <Button variant="outlined" onClick=this.timelapse href="/timelapse/running">
                Start
              </Button>
                
                </div>
        );
    


const TimelapseWithSocket = (props) => (
    <SocketContext.Consumer>
      socket => <Timelapse ...props socket=socket />
    </SocketContext.Consumer>
)
export default TimelapseWithSocket;

Running.js:

import React from 'react';
import SocketContext from '../../services/SocketProvider';

class Running extends React.Component 
    constructor(props) 
        super(props);

        this.state = 
            progress: 0,
            maxProgress: 100,
            waypoints: []
        ;

    


    componentDidMount() 
        this.props.socket.on('progress', data => 
            this.setState(
                progress: data.value,
                maxProgress: data.max
            )
        )

        this.props.socket.on('timelapseInfo', data => 
            console.log('recived date');
            this.setState(
                waypoints: data.waypoints,
                maxProgress: data.max
            )
        )

        console.log("component mounte");
    



    render() 
        var type = this.props.type;
        var title = type.charAt(0).toUpperCase() + type.slice(1)

        return (<div >

        </div>
        );
    


const RunningWithSocket = (props) => (
    <SocketContext.Consumer>
      socket => <Running ...props socket=socket />
    </SocketContext.Consumer>
)

export default RunningWithSocket;

但是当我到达/timelapse/running 的路线时,我得到了一个新的 Socket 实例,但我不知道为什么以及如何修复它。

感谢您的帮助:)

【问题讨论】:

【参考方案1】:

要达到相同的结果,您不需要 Context API,只需从模块中导出 websocket 连接:

// ws.js
import io from 'socket.io-client'

const ws = io.connect(<host>)

export  ws 

然后你可以在你的组件中使用 useEffect(fn, []) 来添加事件,两个方括号作为第二个参数用于使效果只运行一次:

// wsComponent.js
import React,  useState, useEffect  from 'react'
import  Text  from 'react-native'
import  ws  from 'ws.js'

const WsComponent = () => 
  const [wsState, updateWsState] = useState(false)
  useEffect(() => 
    ws.on('connect', () => 
      updateWsState(true)
    )
    ws.on('disconnect', () => 
      updateWsState(false)
    )
  , [])
  return (
    <Text>wsState ? 'ONLINE' : 'OFFLINE'</Text>
  )


export  WsComponent 

对不起,如果我在代码中犯了错误,我没有测试它。

【讨论】:

socket 在 useEffect 中为空,并且将 socket 添加到相同 useEffect 的依赖数组中没有帮助...【参考方案2】:

这可能有助于使您的 App 组件成为纯组件和/或将您的套接字提供程序提取到它自己的纯组件中以避免重新渲染,这可能会导致实例发生变化。或者,您可以尝试将您的 socket.io 包包装在单例模式中。

【讨论】:

Timelapse.js 中使用href 可能会出现问题,这可能会导致App 的重新加载?【参考方案3】:

错误是在我的 Button 上使用 href 更改路由时使用了 Link 组件,这不会触发应用程序的完全重新加载,因此会初始化一个新的 Socket IO 实例。

所以不要使用:

<Button variant="outlined" onClick=this.timelapse href="/timelapse/running">Start</Button>

我不得不使用:

<Button variant="outlined" onClick=this.timelapse component= Link  to="/timelapse/running">Start</Button>

希望这可以为遇到相同问题的人提供解决方案。

【讨论】:

【参考方案4】:
// create a socket.js where you export the client socket created by io(...)
const io = require('socket.io-client')
module.exports = io('http://localhost:5000') //your port


//when you need to use the socket just import and enjoy
import socket from './socket'
socket.on('some_event',()=> console.log('do something') )

【讨论】:

以上是关于如何使用 Context API 将单个 Socket IO 实例传递给页面?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 React Context API 与 useReducer 一起使用以遵循与 Redux 类似的风格 [关闭]

如何使用 React Context API 跨多个 Routes 拥有状态?

如何使用 React Hooks 和 Context API 正确地将来自 useEffect 内部调用的多个端点的数据添加到状态对象?

React Context API + withRouter - 我们可以一起使用它们吗?

在 React.js 的父组件中使用 react-router 时,如何使用 react context API 将数据从父组件传递到子组件?

React context api,将数据从孩子传递给父母