详解vue-socket.io使用教程与踩坑记录

目录
  • 前言
  • 我遇到的问题
  • 使用教程
    • 安装
    • 引入(main.js)
    • 使用(Page.vue)
    • 解决方案
    • 结合connect事件+store+路由守卫实现拦截

请先允许我狠狠吐个槽:vue-socket.io相关中文博客实在太少太少,来来去去就那么几篇,教程也比较零散,版本也比较老,就算我有暴风式搜索还是找不到解决问题的方案,然后我怒了,开始看源码、写测试demo、几乎把相关的issues都看了一遍,折腾1天后终于。。。搞定了,下面总结一下~

考虑到很多小伙伴看完文章还是一头雾水或者无法复现方案,附加demo源码https://github.com/dreamsqin/demo-vue-socket.io一份,耗时一天~满意到话给我个start~感谢

前言

vue-socket.io其实是在socket.io-client基础上做了一层封装,将$socket挂载到vue实例上,同时你可以使用sockets对象轻松实现组件化的事件监听,让你在vue项目中使用起来更方便。我目前用的vue-socket.io:3.0.7,可以在其package.json中看到它依赖于socket.io-client:2.1.1

我遇到的问题

websocket连接地址是从后端动态获取,所以导致页面加载时VueSocketIO实例还未创建,页面中通过this.$socket.emit发起订阅报错,同时无法找到vue实例的sockets对象(写在内部的事件将无法监听到,就算后面已经连接成功)

如果你的websocket连接地址是静态的(写死的),可以只看使用教程,如果你跟我遇到了同样的问题,那就跳跃到解决方案

console报错如下:

使用教程

先抛开可能遇到的问题,按照官网的教程我们走一遍:

安装

npm install vue-socket.io --save

引入(main.js)

import Vue from 'vue'
import store from './store'
import App from './App.vue'
import VueSocketIO from 'vue-socket.io'

Vue.use(new VueSocketIO({
    debug: true,
    connection: 'http://metinseylan.com:1992',
    vuex: {
        store,
        actionPrefix: 'SOCKET_',
        mutationPrefix: 'SOCKET_'
    },
    options: { path: "/my-app/" } //Optional options
}))

new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app')

debug:生产环境建议关闭,开发环境可以打开,这样你就可以在控制台看到socket连接和事件监听的一些信息,例如下面这样:

connection:连接地址前缀,注意!这里只有前缀,我之前被坑过,因为明明后端有给我返回上下文,但莫名其妙的被去除了,vue-socket.io这里用到的是socket.io-clientManager api,关键源码如下(只看我写中文备注的部分就好):

vue-socket.io(index.js)

import SocketIO from "socket.io-client";
export default class VueSocketIO {

    /**
     * lets take all resource
     * @param io
     * @param vuex
     * @param debug
     * @param options
     */
    constructor({connection, vuex, debug, options}){

        Logger.debug = debug;
        this.io = this.connect(connection, options); // 获取到你设定的参数后就调用了connect方法
        this.useConnectionNamespace = (options && options.useConnectionNamespace);
        this.namespaceName = (options && options.namespaceName);
        this.emitter = new Emitter(vuex);
        this.listener = new Listener(this.io, this.emitter);
    }
    /**
   * registering SocketIO instance
   * @param connection
   * @param options
   */
  connect(connection, options) {
    if (connection && typeof connection === "object") {
      Logger.info(`Received socket.io-client instance`);
      return connection;
    } else if (typeof connection === "string") {
      const io = SocketIO(connection, options);// 其实用的是socket.io-client的Manager API
      Logger.info(`Received connection string`);
      return (this.io = io);
    } else {
      throw new Error("Unsupported connection type");
    }
  }

socket.io-client(index.js)

var url = require('./url');
function lookup (uri, opts) {
  if (typeof uri === 'object') {
    opts = uri;
    uri = undefined;
  }

  opts = opts || {};

  var parsed = url(uri); // 通过url.js对connection前缀进行截取
  var source = parsed.source;
  var id = parsed.id;
  var path = parsed.path;
  var sameNamespace = cache[id] && path in cache[id].nsps;
  var newConnection = opts.forceNew || opts['force new connection'] ||
                      false === opts.multiplex || sameNamespace;

  var io;

  if (newConnection) {
    debug('ignoring socket cache for %s', source);
    io = Manager(source, opts);
  } else {
    if (!cache[id]) {
      debug('new io instance for %s', source);
      cache[id] = Manager(source, opts);
    }
    io = cache[id];
  }
  if (parsed.query && !opts.query) {
    opts.query = parsed.query;
  }
  return io.socket(parsed.path, opts);// 实际调用的是解析后的前缀地址
}

options.path: 这里就可以填websocket连接地址的后缀,如果不填会被默认添加/socket.io,关键源码如下(只看我写中文备注的部分就好):

其他的options配置可以参见https://socket.io/docs/client-api/

socket.io-client(manager.js)

function Manager (uri, opts) {
  if (!(this instanceof Manager)) return new Manager(uri, opts);
  if (uri && ('object' === typeof uri)) {
    opts = uri;
    uri = undefined;
  }
  opts = opts || {};

  opts.path = opts.path || '/socket.io'; // 看到没有,如果你不传递options.path参数的话会被默认安一个尾巴"/socket.io"
  this.nsps = {};
  this.subs = [];
  this.opts = opts;
  this.reconnection(opts.reconnection !== false);
  this.reconnectionAttempts(opts.reconnectionAttempts || Infinity);
  this.reconnectionDelay(opts.reconnectionDelay || 1000);
  this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000);
  this.randomizationFactor(opts.randomizationFactor || 0.5);
  this.backoff = new Backoff({
    min: this.reconnectionDelay(),
    max: this.reconnectionDelayMax(),
    jitter: this.randomizationFactor()
  });
  this.timeout(null == opts.timeout ? 20000 : opts.timeout);
  this.readyState = 'closed';
  this.uri = uri;
  this.connecting = [];
  this.lastPing = null;
  this.encoding = false;
  this.packetBuffer = [];
  var _parser = opts.parser || parser;
  this.encoder = new _parser.Encoder();
  this.decoder = new _parser.Decoder();
  this.autoConnect = opts.autoConnect !== false;
  if (this.autoConnect) this.open();
}

vuex: 配置后可以在store.jsmutations或者actions监听到Vue-Socket.io事件(例如:connect、disconnect、reconnect等),这部分目前用得比较少,也挺简单,如果有疑问可以给我留言我再单独提供教程。

使用(Page.vue)

注意:熟悉socket.io-client的应该知道,默认情况下,websocket在创建实例的时候就会自动发起连接了,所以切记不要在组件中重复发起连接。如果你想自己控制发起连接的时机可以将options.autoConnect设置为false

export default {
    name: 'Page',
    sockets: {// 通过vue实例对象sockets实现组件中的事件监听
      connect: function () {// socket的connect事件
        console.log('socket connected from Page')
      },
      STREAM_STATUS(data) {// 后端按主题名推送的消息数据
          console.log('Page:' + data)
      }
    },
    mounted() {
      console.log('page mounted')
      this.$socket.emit('STREAM_STATUS', { subscribe: true })// 在页面加载时发起订阅,“STREAM_STATUS”是你跟后端约定好的主题名
    }
  }

事件除了在sockets对象中默认监听,你还可以在外部单独注册事件监听或取消注册:

this.sockets.subscribe('EVENT_NAME', (data) => {
    this.msg = data.message;
});

this.sockets.unsubscribe('EVENT_NAME');

但这种方式从源码上看是不支持参数传递的,只支持传递事件名及回调函数(部分源码如下):

vue-Socket.io(mixin.js)

beforeCreate(){
        if(!this.sockets) this.sockets = {};

        if (typeof this.$vueSocketIo === 'object') {
            for (const namespace of Object.keys(this.$vueSocketIo)) {
                this.sockets[namespace] = {
                    subscribe: (event, callback) => {
                        this.$vueSocketIo[namespace].emitter.addListener(event, callback, this);
                    },
                    unsubscribe: (event) => {
                        this.$vueSocketIo[namespace].emitter.removeListener(event, this);
                    }
                }
            }
        } else {
            this.$vueSocketIo.emitter.addListener(event, callback, this);
            this.$vueSocketIo.emitter.removeListener(event, this);
        }
    }

解决方案

针对我上面描述的问题,最大原因就在于获取socket连接地址是异步请求,如文章开头的截图,page mounted打印时,this.$socket还是undefined。所以我们要做的就是怎么样让页面加载在VueSocketIO实例创建之后。
我提供两种解决方案,具体怎么选择看你们的需求~

保证拿到socket连接地址后再将vue实例挂载到app

缺点:如果你获取socket地址的请求失败了,整个项目的页面都加载不出来(一般服务器出现问题才会有这种情况产生)
优点:实现简单,一小段代码挪个位置就好

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ParentApi from '@/api/Parent'
import VueSocketIO from 'vue-socket.io'

/* 使用vue-socket.io */
ParentApi.getSocketUrl().then((res) => {
    Vue.use(new VueSocketIO({
        debug: false,
        connection: res.data.path,
        options: { path: '/my-project/socket.io' }
    }))
    new Vue({
        router,
        store,
        render: h => h(App)
    }).$mount('#app')
})

控制台打印如下图:

结合connect事件+store+路由守卫实现拦截

原理:异步请求回调中创建VueSocketIO实例并监听connect事件,监听回调中修改isSuccessConnect参数的值,在Page页面路由中增加beforeEnter守卫,利用setInterval周期性判断isSuccessConnect的值,满足条件则取消定时执行并路由跳转。
缺点:实现起来稍微复杂一点
优点:不会影响其他页面的加载

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ParentApi from '@/api/Parent'
import VueSocketIO from 'vue-socket.io'

ParentApi.getSocketUrl().then((res) => {
  let vueSocketIo = new VueSocketIO({
    debug: false,
    connection: res.data.path,
    options: { path: '/my-project/socket.io' }
  })
  // 监听connect事件,设置isSuccessConnect为true
  vueSocketIo.io.on('connect', () => {
    console.log('socket connect from main.js')
    store.commit('newIsSuccessConnect', true)
  })
  Vue.use(vueSocketIo)
})

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

store.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
export default new Vuex.Store({
  state: {
    // socket连接状态
    isSuccessConnect: false
  },
  mutations: {
    newIsSuccessConnect(state, value) {
      state.isSuccessConnect = value
    }
  },
  getters: {
    getIsSuccessConnect: state => {
      return state.isSuccessConnect
    }
  },
  actions: {
  }
})

router.js

import Vue from 'vue'
import Router from 'vue-router'
import store from './store'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/page',
      name: 'Page',
      component: () => import(/* webpackChunkName: "Page" */ './pages/Page.vue'),
      beforeEnter: (to, from, next) => {
        let intervalId = setInterval(() => {
         // 直到store中isSuccessConnect为true时才能进入/page
          if (store.getters.getIsSuccessConnect) {
            clearInterval(intervalId)
            next()
          }
        }, 500)
      }
    }
  ]
})

控制台打印如下图:

参考资料:

1、vue-socket.io:https://github.com/MetinSeylan/Vue-Socket.io
2、socket.io-client:https://github.com/socketio/socket.io-client
3、vue-router守卫:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html

到此这篇关于详解vue-socket.io使用教程与踩坑记录 的文章就介绍到这了,更多相关vue-socket.io使用教程内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue-socket.io接收不到数据问题的解决方法

    最近公司的一个vue项目用到了vue-socket.io来处理socket数据传输,之前用过socket.io-client,现在知道vue-socket.io是基于socket.io-client的一层封装,将socket挂于全局从而更方便的书写. 于是把代码拉取下来运行: 什么鬼,同样的代码为什么我的就接收不到数据,自己新建一个测试一下吧! 先用express和socket.io搭个小socket服务器: let express = require('express'); let app =

  • vue-socket.io跨域问题有效解决方法

    网友问题: 使用了vue-cli这个脚手架工具.在开发环境中如何配置跨域这个我懂.但是使用npm run build后,里面所有的ajax的跨域请求url都变成了根目录. 这样该如何解决部署的跨域问题? 报错信息: Access to XMLHttpRequest at 'http://192.168.37.130:5050/socket.io/?EIO=3&transport=polling&t=N0oqNsW' from origin 'http://localhost:8080' h

  • 详解vue-socket.io使用教程与踩坑记录

    目录 前言 我遇到的问题 使用教程 安装 引入(main.js) 使用(Page.vue) 解决方案 结合connect事件+store+路由守卫实现拦截 请先允许我狠狠吐个槽:vue-socket.io相关中文博客实在太少太少,来来去去就那么几篇,教程也比较零散,版本也比较老,就算我有暴风式搜索还是找不到解决问题的方案,然后我怒了,开始看源码.写测试demo.几乎把相关的issues都看了一遍,折腾1天后终于...搞定了,下面总结一下~ 考虑到很多小伙伴看完文章还是一头雾水或者无法复现方案,附

  • 详解vue-class迁移vite的一次踩坑记录

    目录 what happen 探究 解决 总结 what happen 最进项目从 vue-cli 迁移到了 vite,因为是 vue2 的项目,使用了 vue-class-component类组件做 ts 支持.当然迁移过程并没有那么一帆风顺,浏览器控制台报了一堆错,大致意思是某某方法为 undefined,无法调用.打印了下当前 this,为 undefined 的方法都来自于 vuex-class 装饰器下的方法.这就是一件很神奇的事,为什么只有 vuex-class 装饰器下的方法才会为

  • vue结合AntV G2的使用踩坑记录

    目录 vue结合AntV G2使用踩坑 AntV-G2语法总结 目的 第一步语法基础 第二步设置坐标轴的外观与度量 第二步创建view vue结合AntV G2使用踩坑 官网使用import G2 from '@antv/g2';引入但是会报一个 "export 'default' (imported as 'G2') was not found in '@antv/g2'  得错误 找了半天原因,最终解决办法 import * as G2 from '@antv/g2' AntV-G2语法总结

  • 详解webpack import()动态加载模块踩坑

    import webpack根据ES2015 loader 规范实现了用于动态加载的import()方法. 这个功能可以实现按需加载我们的代码,并且使用了promise式的回调,获取加载的包. 在代码中所有被import()的模块,都将打成一个单独的包,放在chunk存储的目录下.在浏览器运行到这一行代码时,就会自动请求这个资源,实现异步加载. 这里是一个简单的demo. import('lodash').then(_ => { // Do something with lodash (a.k.

  • 详解vue组件基础

    什么是组件 组件(Component)是对数据和方法的简单封装.web中的组件其实可以看成是页面的一个组成部分,它是一个具有独立的逻辑和功能的界面,同时又能根据规定的接口规则进行相互融和,最终成为一个完整的应用,页面就是由一个个类似这样的组成部分组成的,比如导航.列表.弹窗.下拉菜单等.页面只不过是这样组件的容器,组件自由组合形成功能完整的界面,当不需要某个组件,或者想要替换某个组件时,可以随时进行替换和删除,而不影响整个应用的运行..前端组件化的核心思想就是将一个巨大复杂的东西拆分成粒度合理的

  • 详解Vue适时清理keepalive缓存方案

    目录 需求 思考 尝试 1. 手动操作 keep-alive 组件的 cache 数组 2. exclude 大法好 Demo 需求 单页面应用中,用户进入表单填写页面,需要初始化表单内容,填写过程中可能涉及到地图选点或者列表选择等操作,需要到新的页面选择并回调显示. 此时我们需要缓存表单填写页面实例,当退出表单填写或提交完表单内容之后,需要销毁当前表单实例,下次进入重新进行初始化 思考 说到 Vue 缓存,我们肯定首先选择官方提供的缓存方案 keep-alive 内置组件来实现. keep-a

  • 详解vue 模版组件的三种用法

    本文介绍了详解vue 模版组件的三种用法,分享给大家,具体如下: 第一种 //首先,别忘了引入vue.js <div id="user_name_01"></div> <script src="../node_modules/vue/dist/vue.js"></script> <script> var User_01 = Vue.extend({// 创建可复用的构造器 template: '<p&

  • 详解 vue.js用法和特性

    前  言 最近用Vue.js做了一个数据查询平台,还做了一个拼图游戏,突然深深的感到了vue的强大. Vue.js是一套构建用户界面(user interface)的渐进式框架.与其他重量级框架不同的是,Vue 从根本上采用最小成本.渐进增量(incrementally adoptable)的设计.Vue 的核心库只专注于视图层,并且很容易与其他第三方库或现有项目集成.另一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也完全能够为复杂的单页应用程序提供有力驱动. Vue.j

  • 详解vue的数据binding绑定原理

    自从angular火了以后,各种mvc框架喷涌而出,angular虽然比较火,但是他的坑还是蛮多的,还有许多性能问题被人们吐槽.比如坑爹的脏检查机制,数据binding是受人喜爱的,脏检查就有点-性能低下了.有时候改了一个地方,脏循环要循环多次来保证数据是不是真的变了和是否停止变化了.这样性能就很低了.于是人们开始钻研新的双向数据binding的方法.尤大的vue binding就是本人蛮喜欢的一种实现方式,本文跟随尤大的一个例子来详解vue的数据binding的原理. 数据binding,一般

  • 详解VUE 数组更新

    1.数据方法分类: (1)原数组改变 push  pop  unshift  shift  reverse  sort  splice (2)原数组未变,生成新数组 slice  concat  filter 对于使原数组变化的方法,可以直接更新视图. 对于原数组未变的方法,可以使用新数组替换原来的数组,以使视图发生变化. 示例代码: <!DOCTYPE html> <html lang="zh"> <head> <meta charset=&

随机推荐