詳解vue-socket.io使用教程與踩坑記錄
請先允許我狠狠吐個槽:vue-socket.io相關中文博客實在太少太少,來來去去就那么幾篇,教程也比較零散,版本也比較老,就算我有暴風式搜索還是找不到解決問題的方案,然后我怒了,開始看源碼、寫測試demo、幾乎把相關的issues都看了一遍,折騰1天后終于。。。搞定了,下面總結一下~
考慮到很多小伙伴看完文章還是一頭霧水或者無法復現(xiàn)方案,附加demo源碼https://github.com/dreamsqin/demo-vue-socket.io一份,耗時一天~滿意到話給我個start~感謝
前言
vue-socket.io其實是在socket.io-client基礎上做了一層封裝,將$socket掛載到vue實例上,同時你可以使用sockets對象輕松實現(xiàn)組件化的事件監(jiān)聽,讓你在vue項目中使用起來更方便。我目前用的vue-socket.io:3.0.7,可以在其package.json中看到它依賴于socket.io-client:2.1.1。
我遇到的問題
websocket連接地址是從后端動態(tài)獲取,所以導致頁面加載時VueSocketIO實例還未創(chuàng)建,頁面中通過this.$socket.emit發(fā)起訂閱報錯,同時無法找到vue實例的sockets對象(寫在內部的事件將無法監(jiān)聽到,就算后面已經連接成功)
如果你的websocket連接地址是靜態(tài)的(寫死的),可以只看使用教程,如果你跟我遇到了同樣的問題,那就跳躍到解決方案
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:生產環(huán)境建議關閉,開發(fā)環(huán)境可以打開,這樣你就可以在控制臺看到socket連接和事件監(jiān)聽的一些信息,例如下面這樣:

connection:連接地址前綴,注意!這里只有前綴,我之前被坑過,因為明明后端有給我返回上下文,但莫名其妙的被去除了,vue-socket.io這里用到的是socket.io-client的Manager 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.js的mutations或者actions監(jiān)聽到Vue-Socket.io事件(例如:connect、disconnect、reconnect等),這部分目前用得比較少,也挺簡單,如果有疑問可以給我留言我再單獨提供教程。
使用(Page.vue)
注意:熟悉socket.io-client的應該知道,默認情況下,websocket在創(chuàng)建實例的時候就會自動發(fā)起連接了,所以切記不要在組件中重復發(fā)起連接。如果你想自己控制發(fā)起連接的時機可以將options.autoConnect設置為false。
export default {
name: 'Page',
sockets: {// 通過vue實例對象sockets實現(xiàn)組件中的事件監(jiān)聽
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 })// 在頁面加載時發(fā)起訂閱,“STREAM_STATUS”是你跟后端約定好的主題名
}
}
事件除了在sockets對象中默認監(jiān)聽,你還可以在外部單獨注冊事件監(jiān)聽或取消注冊:
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實例創(chuàng)建之后。
我提供兩種解決方案,具體怎么選擇看你們的需求~
保證拿到socket連接地址后再將vue實例掛載到app
缺點:如果你獲取socket地址的請求失敗了,整個項目的頁面都加載不出來(一般服務器出現(xiàn)問題才會有這種情況產生)
優(yōu)點:實現(xiàn)簡單,一小段代碼挪個位置就好
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+路由守衛(wèi)實現(xiàn)攔截
原理:異步請求回調中創(chuàng)建VueSocketIO實例并監(jiān)聽connect事件,監(jiān)聽回調中修改isSuccessConnect參數的值,在Page頁面路由中增加beforeEnter守衛(wèi),利用setInterval周期性判斷isSuccessConnect的值,滿足條件則取消定時執(zhí)行并路由跳轉。
缺點:實現(xiàn)起來稍微復雜一點
優(yōu)點:不會影響其他頁面的加載
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' }
})
// 監(jiān)聽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連接狀態(tài)
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守衛(wèi):https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
到此這篇關于詳解vue-socket.io使用教程與踩坑記錄 的文章就介紹到這了,更多相關vue-socket.io使用教程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue-admin-template?報Uncaught?(in?promise)?error問題及解決
這篇文章主要介紹了Vue-admin-template?報Uncaught?(in?promise)?error問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
vue + typescript + video.js實現(xiàn) 流媒體播放 視頻監(jiān)控功能
視頻才用流媒體,有后臺實時返回數據, 要支持flash播放, 所以需安裝對應的flash插件。這篇文章主要介紹了vue + typescript + video.js 流媒體播放 視頻監(jiān)控,需要的朋友可以參考下2019-07-07
Vue異步更新DOM及$nextTick執(zhí)行機制解讀
這篇文章主要介紹了Vue異步更新DOM及$nextTick執(zhí)行機制解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
vue中控制mock在開發(fā)環(huán)境使用,在生產環(huán)境禁用方式
這篇文章主要介紹了vue中控制mock在開發(fā)環(huán)境使用,在生產環(huán)境禁用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-04-04

