詳解vue-socket.io使用教程與踩坑記錄
請先允許我狠狠吐個(gè)槽:vue-socket.io相關(guān)中文博客實(shí)在太少太少,來來去去就那么幾篇,教程也比較零散,版本也比較老,就算我有暴風(fēng)式搜索還是找不到解決問題的方案,然后我怒了,開始看源碼、寫測試demo、幾乎把相關(guān)的issues都看了一遍,折騰1天后終于。。。搞定了,下面總結(jié)一下~
考慮到很多小伙伴看完文章還是一頭霧水或者無法復(fù)現(xiàn)方案,附加demo源碼https://github.com/dreamsqin/demo-vue-socket.io一份,耗時(shí)一天~滿意到話給我個(gè)start~感謝
前言
vue-socket.io
其實(shí)是在socket.io-client
基礎(chǔ)上做了一層封裝,將$socket
掛載到vue實(shí)例上,同時(shí)你可以使用sockets對象
輕松實(shí)現(xiàn)組件化的事件監(jiān)聽,讓你在vue項(xiàng)目中使用起來更方便。我目前用的vue-socket.io:3.0.7
,可以在其package.json
中看到它依賴于socket.io-client:2.1.1
。
我遇到的問題
websocket連接地址是從后端動(dòng)態(tài)獲取,所以導(dǎo)致頁面加載時(shí)VueSocketIO
實(shí)例還未創(chuàng)建,頁面中通過this.$socket.emit
發(fā)起訂閱報(bào)錯(cuò),同時(shí)無法找到vue實(shí)例的sockets對象(寫在內(nèi)部的事件將無法監(jiān)聽到,就算后面已經(jīng)連接成功)
如果你的websocket連接地址是靜態(tài)的(寫死的),可以只看使用教程
,如果你跟我遇到了同樣的問題,那就跳躍到解決方案
console報(bào)錯(cuò)如下:
使用教程
先拋開可能遇到的問題,按照官網(wǎng)的教程我們走一遍:
安裝
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
:生產(chǎn)環(huán)境建議關(guān)閉,開發(fā)環(huán)境可以打開,這樣你就可以在控制臺(tái)看到socket連接和事件監(jiān)聽的一些信息,例如下面這樣:
connection
:連接地址前綴,注意!這里只有前綴,我之前被坑過,因?yàn)槊髅骱蠖擞薪o我返回上下文,但莫名其妙的被去除了,vue-socket.io
這里用到的是socket.io-client
的Manager api
,關(guān)鍵源碼如下(只看我寫中文備注的部分就好):
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); // 獲取到你設(shè)定的參數(shù)后就調(diào)用了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);// 其實(shí)用的是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前綴進(jìn)行截取 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);// 實(shí)際調(diào)用的是解析后的前綴地址 }
options.path
: 這里就可以填websocket連接地址的后綴,如果不填會(huì)被默認(rèn)添加/socket.io
,關(guān)鍵源碼如下(只看我寫中文備注的部分就好):
其他的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'; // 看到?jīng)]有,如果你不傳遞options.path參數(shù)的話會(huì)被默認(rèn)安一個(gè)尾巴"/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等),這部分目前用得比較少,也挺簡單,如果有疑問可以給我留言我再單獨(dú)提供教程。
使用(Page.vue)
注意:熟悉socket.io-client
的應(yīng)該知道,默認(rèn)情況下,websocket在創(chuàng)建實(shí)例的時(shí)候就會(huì)自動(dòng)發(fā)起連接了,所以切記不要在組件中重復(fù)發(fā)起連接。如果你想自己控制發(fā)起連接的時(shí)機(jī)可以將options.autoConnect
設(shè)置為false
。
export default { name: 'Page', sockets: {// 通過vue實(shí)例對象sockets實(shí)現(xiàn)組件中的事件監(jiān)聽 connect: function () {// socket的connect事件 console.log('socket connected from Page') }, STREAM_STATUS(data) {// 后端按主題名推送的消息數(shù)據(jù) console.log('Page:' + data) } }, mounted() { console.log('page mounted') this.$socket.emit('STREAM_STATUS', { subscribe: true })// 在頁面加載時(shí)發(fā)起訂閱,“STREAM_STATUS”是你跟后端約定好的主題名 } }
事件除了在sockets對象
中默認(rèn)監(jiān)聽,你還可以在外部單獨(dú)注冊事件監(jiān)聽或取消注冊:
this.sockets.subscribe('EVENT_NAME', (data) => { this.msg = data.message; }); this.sockets.unsubscribe('EVENT_NAME');
但這種方式從源碼上看是不支持參數(shù)傳遞的,只支持傳遞事件名及回調(diào)函數(shù)(部分源碼如下):
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
打印時(shí),this.$socket
還是undefined
。所以我們要做的就是怎么樣讓頁面加載在VueSocketIO
實(shí)例創(chuàng)建之后。
我提供兩種解決方案,具體怎么選擇看你們的需求~
保證拿到socket連接地址后再將vue實(shí)例掛載到app
缺點(diǎn):如果你獲取socket地址的請求失敗了,整個(gè)項(xiàng)目的頁面都加載不出來(一般服務(wù)器出現(xiàn)問題才會(huì)有這種情況產(chǎn)生)
優(yōu)點(diǎn):實(shí)現(xiàn)簡單,一小段代碼挪個(gè)位置就好
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') })
控制臺(tái)打印如下圖:
結(jié)合connect事件+store+路由守衛(wèi)實(shí)現(xiàn)攔截
原理:異步請求回調(diào)中創(chuàng)建VueSocketIO
實(shí)例并監(jiān)聽connect
事件,監(jiān)聽回調(diào)中修改isSuccessConnect
參數(shù)的值,在Page頁面路由中增加beforeEnter
守衛(wèi),利用setInterval
周期性判斷isSuccessConnect
的值,滿足條件則取消定時(shí)執(zhí)行并路由跳轉(zhuǎn)。
缺點(diǎn):實(shí)現(xiàn)起來稍微復(fù)雜一點(diǎn)
優(yōu)點(diǎn):不會(huì)影響其他頁面的加載
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事件,設(shè)置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時(shí)才能進(jìn)入/page if (store.getters.getIsSuccessConnect) { clearInterval(intervalId) next() } }, 500) } } ] })
控制臺(tái)打印如下圖:
參考資料:
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
到此這篇關(guān)于詳解vue-socket.io使用教程與踩坑記錄 的文章就介紹到這了,更多相關(guān)vue-socket.io使用教程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue中前進(jìn)刷新、后退緩存用戶瀏覽數(shù)據(jù)和瀏覽位置的實(shí)例講解
今天小編就為大家分享一篇vue中前進(jìn)刷新、后退緩存用戶瀏覽數(shù)據(jù)和瀏覽位置的實(shí)例講解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09Element?table?上下移需求的實(shí)現(xiàn)
本文主要介紹了Element?table?上下移需求的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07vant中l(wèi)ist的使用以及首次加載觸發(fā)兩次解決問題
這篇文章主要介紹了vant中l(wèi)ist的使用以及首次加載觸發(fā)兩次解決問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10ElementUI修改實(shí)現(xiàn)更好用圖片上傳預(yù)覽組件
這篇文章主要為大家介紹了ElementUI修改實(shí)現(xiàn)更好用圖片上傳預(yù)覽組件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09案例實(shí)操vue事件修飾符帶你快速了解與應(yīng)用
這篇文章主要介紹了vue常見的事件修飾符,在平時(shí)無論是面試還是學(xué)習(xí)工作中都會(huì)經(jīng)常遇到的,本文就帶你快速上手,需要的朋友可以參考下2023-03-03Vue屏幕自適應(yīng)三種實(shí)現(xiàn)方法詳解
在實(shí)際業(yè)務(wù)中,我們常用圖表來做數(shù)據(jù)統(tǒng)計(jì),數(shù)據(jù)展示,數(shù)據(jù)可視化等比較直觀的方式來達(dá)到一目了然的數(shù)據(jù)查看,但在大屏開發(fā)過程中,常會(huì)因?yàn)檫m配不同屏幕而感到困擾,下面我們來解決一下這個(gè)不算難題的難題2022-11-11vue導(dǎo)出word純前端的實(shí)現(xiàn)方式
這篇文章主要介紹了vue導(dǎo)出word純前端的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04vue3動(dòng)態(tài)路由刷新出現(xiàn)空白頁的原因與最優(yōu)解
頁面刷新白屏其實(shí)是因?yàn)関uex引起的,由于刷新頁面vuex數(shù)據(jù)會(huì)丟失,這篇文章主要給大家介紹了關(guān)于vue3動(dòng)態(tài)路由刷新出現(xiàn)空白頁的原因與最優(yōu)解的相關(guān)資料,需要的朋友可以參考下2023-11-11el-form組件使用resetFields重置失效的問題解決
用el-form寫了包含三個(gè)字段的表單,使用resetFields方法進(jìn)行重置,發(fā)現(xiàn)點(diǎn)擊重置或要清空校驗(yàn)時(shí)是失效的,所以本文給大家介紹了el-form組件使用resetFields重置失效的問題解決,需要的朋友可以參考下2023-12-12