Why is Websocket associated with public state management

We all know that in single-page componentization projects such as VUE and React, socket connection can encounter problems such as repeated connections, page switching, broken connections, and state loss. In addition, if you want to receive information from the socket on any page, Therefore, when establishing socket connections, we should consider whether connection instantiation should be managed in public state, so that it is convenient to call socket methods in any component. This section introduces the scheme that socket connects with Vuex and Redux to receive information change data in real time.

This solution fundamentally solves the problem:

① Fundamentally solve the problems of repeated connection of single page components, interruption of connection of switching page components, state loss and so on. (2) Unified status management and unified dispatch center. Any page to share the data source, any page to push data. (3) The coupling between the socket connection layer and the component layer is minimized.

Websocket and common state management logic diagram

conclusion

The general idea of this solution is as shown in the figure above. Now, when the page is initialized, it initiates the initialization method to Vuex or Redux according to the need. During initialization, the WebSocket, JS constructor or class instance is triggered. The commit parameter is passed to the socket instance, and the actual method to establish the socket connection is done in the WeboSocket instance. The WebSocket instance exposes two methods, Subscribe is used to listen to the message sent by the server to change the management state. Of course, the emit method is triggered by calling commit function, and the emit method is also called by any component to pass the message to the server, thus realizing two-way communication and putting the communication receipt content in the public state management. Avoid information loss, reconnection, or connection loss of switchover components. The vuEX example will be used to explain the process in detail.

Successful cases (WebSocket and Vue and VUex as examples)

Schema structure and initialization process

Directory file

This is the file format (simplified here), websocket.js is the socket scheduling center (the heart of the solution), which integrates subscribers, publishers, failure scheduling, heartbeat mechanisms, etc. Socket. js is a vuex module, which can be understood as a model in DVA. Socket. vue is the component that needs to use socket connection.

Class in the page component

First let’s look at socket initialization

 if(! socket.ws) {// Initialize the socket connection in socket.vue
     this.$store.dispatch('socketInit')}Copy the code

This is just triggering a separate dispatch, calling a socketInit method, and then looking at the socketInit method in socket.js in Vuex.

import Socket from '.. /websocket' / / socket method
import socketAction from '.. /.. /config/socket' // This is a middleware function for server-side data processing, which can be ignored here

export default {
    state: {
        ws: null./ / websorket instance
    
    },
    mutations: {
        subscribe_socket (state,{data}){
            // Data is the data returned from the socket connection
        },
        contentSocket (state, { commit }) {
            state.ws = new Socket(commit, socketAction)
        }
    },
    actions: {
        // Create an instance
        socketInit ({commit, state}) {
            commit('contentSocket', { commit }) // take commit as an argument}}}Copy the code

Call the initialization method in vuex’s asynchronous actions, then trigger the contentSocket method to create the instance and bind it to ws on state, where commit must be used as an argument. While socket instances can trigger methods that change state, now that we know how socket instances bind and commit are passed, let’s look at how websocket.js’s entire core schedule works.

Socket core scheduling

function socket (commit, actions) {
    if(isType(commit) ! = ='Function') {
        throw new Error('commit must be a function')}this.commit = commit // trigger commit on VUex
    this.actions = actions || null
    this.timer = null
    this.errorResetNumber = 0      // Error reconnection interval
    this.closeWs = false
    this.errorFrom = 0             // The socket is disconnected from the source
    this.errorResetTimer = null    // Error reconnection polling
    this.errorDispatchOpen = true  // Enable error scheduling
    this.heartSocketOpen = false   / / the heart
    isSocketContent()
    this.$soctket_init() //
}

Copy the code

So we see that the websocket function is a constructor that does initialization, isSocketContent() is a constructor that gets tokens and so on and so forth and you don’t have to worry about that, it triggers a socketinit() method, so let’s look at the socket_init() method, So let’s look at the socketinit() method, and then let’s look at the soctket_init() method

socket.prototype.$socket_init = function (callback) {
    const _this = this
    if (_this.closeWs) {
        throw new Error('socket is closed ,$socker_init is fail , all methods is invalid')}const token = window.localStorage.getItem('token') | |window.sessionStorage.getItem('token') | |null

    if(! token) {throw new Error('token is underfined')}const handerErrorMachine = () = > { 
        if (_this.errorResetNumber === 4) {
            _this.errorResetNumber = 0
            _this.errorResetTimer = null
            _this.errorFrom = 0
            _this.errorDispatchOpen = false
            _this.ws = null
            console.log('Socket connection failed')
            return
        }
        _this.errorResetTimer = setTimeout(() = > {
            /* Failed to reconnect */
            _this.$soctket_init()
            _this.errorResetNumber++
        }, _this.errorResetNumber * 2000)}const errorDispatch = (eventment) = > { // Error scheduling
        let event = eventment
        return function () {
            if (_this.errorFrom === 0 && _this.errorDispatchOpen) {
                _this.errorFrom = event
            }
            event === 1 ? console.log('web socket has failed from closeState ') : console.log('web socket has failed from errorState ')
            if(_this.errorFrom === event && ! _this.closeWs) { _this.errorResetTimer &&clearTimeout(_this.errorResetTimer)
                handerErrorMachine()
            }   
        }
    }
    if (this.timer) clearTimeout(this.timer)

    _this.ws = new WebSocket(socketUrl + '? token=' + token) // The actual socket connection is made here

    _this.ws.onopen = function () {
        callback && callback()
        _this.errorResetNumber = 0
        _this.errorResetTimer = null
        _this.errorFrom = 0
        _this.errorDispatchOpen = true
        /* Accept the message, change the state */
        _this.$soctket_subscribe()
        _this.$soctket_heartSoctket()
        console.log('web socket has connected ')
    }

    _this.ws.onclose = errorDispatch(1)
    _this.ws.onerror = errorDispatch(2)}Copy the code

Here’s the real socket connection and some error handling, there’s the connection connection and ws in the constructor, there’s a connection failure scheduling mechanism, and there’s a method that we’ve been talking about, socketsubscribe(), that’s it, Socket_subscribe () : socket_subscribe() : vuex,commit () : vuex,commit () : vuex,commit () : vuex,commit () : vuex Socketsubscribe (), that’s it, listens for the message from the back end, and triggers the vuex,commit method, changes the state, notifies the view to update, Socket_heartSoctket () is a heartbeat mechanism, and we know that a socket connection will automatically disconnect if there is no call for a long time, so there is a heartbeat mechanism. So let’s look at the socket_subscribe method.

Subscribe to receive information and change the state

/** * Accept broadcast -> urge view update */

socket.prototype.$socket_subscribe = function () {
    const _this = this
    _this.ws.onmessage = function (res) {
        if (_this.actions) {
            if(isType(_this.actions) ! = ='Function') {
                throw new Error('actions')}else{ _this.commit(... _this.actions(res.data)) } }else {
            _this.commit(res.data)
            
        }    
        _this.$soctket_heartSoctket()
    }
}
Copy the code

We see that the commit that vuex sent in earlier is at work here, triggering mutations to change the data in state to rerender the attempt. Next we’ll look at the emit trigger.

Emit any component that passes information

 /** * Trigger -> publish information *@param Callback state processing *@param Value Data processing */
socket.prototype.$socket_emit = function (value, callback) {
    const _this = this
    const poll = function () {
        return _this.ws.readyState
    }
    if(callback && isType(callback) ! = ='Function') {
        throw new Error('$socket_emit arugment[1] must be a function')}if(! _this.ws) {throw new Error('$socket dispatch is fail please use $socket_open method')}if (_this.ws.readyState === 1) { // Connection status is successful
        _this.ws.send(value)
        _this.$soctket_heartSoctket()
        callback && callback()
    }
    else if (_this.ws.readyState === 0) { // Connection status, polling query connection
        eventPoll(poll, 1.500.() = > {
            _this.ws.send(value)                                                             
            _this.$soctket_heartSoctket()
            callback && callback()
        })
    }
    else { // Failed to reconnect
        _this.$soctket_init(() = > {
            _this.$soctket_emit(value, callback)
        })
    }
}
Copy the code

This is the emit trigger that is called in VUE to initiate data communication to the server. Two-way data communication is realized. There is a poller in it to poll whether the state of eventPoll and WebSocket is connected. How to call emit in Vue file is simply to call wX in vuEX’s previously bound state.

In any component

 const { ws } = this.$store.state.socket
   ws.$soctket_emit(JSON.stringify({
                data: 'hello , world'
    }), () = > {
        console.log('Sent successfully')})Copy the code

That’s how it’s triggered. The whole mechanism above has been explained one side, then there is the heartbeat mechanism, let me introduce you.

Heart mechanism

/** * Heart beating mechanism -> prevents disconnection */
socket.prototype.$soctket_heartSoctket = function () {  
    if (this.timer) clearTimeout(this.timer)
    console.log(this.timer)
    this.timer = setTimeout(() = > {
        if (this.ws.readyState === 1 || this.ws.readyState === 0) {
            this.ws.send('heart , socket')
            this.$soctket_heartSoctket()
        } else {
            this.$soctket_init()
        }
    }, 59000)
Copy the code

Is to continuously send messages to the server to prevent disconnection. There are two more methods to control connection and closure of WS.

/** * Open, close socket */
/** * Close the socket connection */
socket.prototype.$soctket_close = function () {
    if (this.timer) clearTimeout(this.timer)
    if (this.errorResetTimer)clearTimeout(this.errorResetTimer)
    this.closeWs = true
    this.ws.close()
}
/** * Restart socket connection */
socket.prototype.$soctket_open = function () {
    if (!this.closeWs) {
        throw new Error('socket is connected')}this.timer = null
    this.errorResetNumber = 0
    this.closeWs = false
    this.errorFrom = 0
    this.errorResetTimer = null
    this.errorDispatchOpen = true
    this.heartSocketOpen = false
    this.closeWs = false
    this.$soctket_init()
}

Copy the code

Socket connection of applets,

The socket connection of applets is almost the same system as that of H, which is also connected by this scheme. Under the influence of different applets framework, the transfer mode of COMMIT is slightly different from that of H5, which will not be explained here. This system is relatively stable in the project, welcome to gitHub to download the source code.

Like you can pay attention to the author and the author’s public number: front-end Sharing