vue使用?vue-socket.io三種方式及踩坑實(shí)例解析
前言
vue項(xiàng)目實(shí)時(shí)通信實(shí)現(xiàn)常用方式:
一、原生HTML5 WebSocket實(shí)現(xiàn),vue中使用websocket
二、插件socket.io官網(wǎng) ,Socket.io是一個(gè)WebSocket庫(kù),包括了客戶(hù)端js和服務(wù)器端的nodejs,會(huì)自動(dòng)根據(jù)瀏覽器從WebSocket、AJAX長(zhǎng)輪詢(xún)、Iframe流等等各種方式中選擇最佳的方式來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)實(shí)時(shí)應(yīng)用,最低支持IE5.5; 說(shuō)白了就是如果瀏覽器支持websocket,那么socket.io就等同于websocket。當(dāng)然 socket.io還用到了其它的技術(shù)來(lái)模擬websocket,所以當(dāng)你使用socket.io的時(shí)候,不管瀏覽器是否支持websocket,你都可以實(shí)現(xiàn)異步操作。
使用注意:客戶(hù)端使用了socket.io 服務(wù)端也必須對(duì)應(yīng)使用
三、vue-socket.io是vue對(duì)socket.io的封裝,使用方式與socket.io大同小異,核心參數(shù)還是要參照socket.io官網(wǎng)相關(guān)配置;當(dāng)前在使用中也存在部分坑
當(dāng)前文章主要說(shuō)vue-socket.io以下問(wèn)題:
- 初始化全局掛載使用,事件訂閱與銷(xiāo)毀(基本無(wú)坑,需注意訂閱事件要對(duì)應(yīng)銷(xiāo)毀,否則存在多次訂閱引發(fā)的消息重復(fù)接收問(wèn)題)
- 帶token驗(yàn)證的全局掛載方式(有坑,有解決方案與解決后使用注意事項(xiàng))
- 組件內(nèi)使用
安裝
建議直接使用taobao鏡像,淘寶鏡像地址建議使用新地址,老地域名將于2022年05月31日零時(shí)起停止服務(wù)nrm use taobao || npm config set registry registry.npmmirror.com/
npm i vue-socket.io@3.0.10 -S //個(gè)人使用了當(dāng)前版本
使用方式一 (官方用法)[全局掛載,不驗(yàn)證]
需注意:事件只有在訂閱后才可接接收返回消息(訂閱方法與后端約定名稱(chēng))。
實(shí)例創(chuàng)建后,全局掛載實(shí)例方法,this.$socket,this.sockets。
其中事件訂閱、取消與監(jiān)聽(tīng)方法在this.sockets封裝;
this.$socket是socket.io實(shí)例中io的封裝。
// 全局掛載
// main.js
import VueSocketIO from 'vue-socket.io'
Vue.use(new VueSocketIO({
debug: true,// 生產(chǎn)環(huán)境關(guān)閉,打開(kāi)可在控制臺(tái)查看socket連接和事件監(jiān)聽(tīng)的信息
options: {
autoConnect: false //創(chuàng)建時(shí)是否自動(dòng)連接,默認(rèn)關(guān)閉,使用時(shí)用open開(kāi)啟鏈接
},
connection: 'http://127.0.0.1:9527' //鏈接地址
}))
//1.全局掛載-全局使用
//main.js
new Vue({
sockets: {
connecting() { console.log('正在連接') },
connect() { console.log('連接成功') },
disconnect() { console.log('斷開(kāi)連接') },
connect_failed() { console.log('連接失敗') },
error() { console.log('錯(cuò)誤發(fā)生,并且無(wú)法被其他事件類(lèi)型所處理') },
reconnecting() { console.log('正在重連') },
reconnect_failed() { console.log('重連失敗') },
reconnect() { console.log('重連成功') },
welcome: data => {//全局監(jiān)聽(tīng)訂閱事件,需要與后端約定好
console.log('welcome data', data)
}
}
})
//2. 全局掛載-組件內(nèi)使用
//demo.vue
<template>
<div>
<button @click="socketOpen">連接Socket</button>
<button @click="closeSocket">斷開(kāi)鏈接</button>
<button @click="submsgContent(true)">訂閱事件</button>
<button @click="submsgContent(false)">取消訂閱事件</button>
<button @click="socketSendmsg">發(fā)送數(shù)據(jù)</button>
<button @click="lockResult">查看鏈接參數(shù)</button>
</div>
</template>
<script>
export default {
beforeDestroy() { //訂閱事件記得要取消---否則多次訂閱會(huì)引發(fā)多次消息返回
if (!this.$socket) return
this.sockets.unsubscribe('msgContent')
this.$socket.close()
},
sockets: { //監(jiān)聽(tīng)用的是this.sockets 發(fā)送消息是this.$socket,不要弄混
connecting() { console.log('正在連接') },
connect() { console.log('連接成功') },
disconnect() { console.log('斷開(kāi)連接') },
connect_failed() { console.log('連接失敗') },
error() { console.log('錯(cuò)誤發(fā)生,并且無(wú)法被其他事件類(lèi)型所處理') },
reconnecting() { console.log('正在重連') },
reconnect_failed() { console.log('重連失敗') },
reconnect() { console.log('重連成功') },
welcome: data => {//全局監(jiān)聽(tīng)訂閱事件,需要與后端約定好
console.log('welcome data', data)
}
},
methods: {
socketOpen() {
this.$socket.open()// 開(kāi)始連接 socket
},
socketSendmsg() { // 發(fā)送消息
this.$socket.emit('hello', '這里是客戶(hù)端')
},
lockResult() {
console.log('鏈接狀態(tài)', this.$socket.connected)
console.log('this.$socket', this.$socket)
console.log('this.sockets', this.sockets)
},
closeSocket() {
this.$socket.close()
},
submsgContent(flag) {
if (flag) { //事件訂閱
this.sockets.subscribe('welcome', data => { //組件內(nèi)監(jiān)聽(tīng)
console.log('組件內(nèi)監(jiān)聽(tīng)-welcome', data)
})
} else {//取消訂閱
this.sockets.unsubscribe('welcome')
}
}
}
}
</script>
使用方式二 (組件掛載使用)[可驗(yàn)證]
因在組件內(nèi)使用,所以可將邏輯寫(xiě)在登錄后加載掛載組件,全局或指定頁(yè)面使用。
組件內(nèi)創(chuàng)建注意事項(xiàng):因vue-socket.io,在new.use()時(shí)掛載方法$socket到全局,在創(chuàng)建實(shí)例時(shí)掛載sockets監(jiān)聽(tīng)對(duì)象到當(dāng)前實(shí)例,所以,在組件中使用后會(huì)導(dǎo)致以下問(wèn)題:
問(wèn)題:
1. $socket可正常使用,與$socket也就是io相關(guān)的方法都可正常使用(例如:this.$socket.open()、close等等...)
2. sockets相關(guān)方法無(wú)法使用(例如:this.sockets.unsubscribe、this.sockets.subscribe等等)
處理方式:使用socket.io原生方法,
1. 事件訂閱
實(shí)例.emitter.addListener
2. 取消訂閱
實(shí)例.emitter.addListener
//demo.vue
<template>
<div>
<button @click="createSocket">創(chuàng)建socket</button>
<div v-if="createSocketIO ? true:false ">
<button @click="socketOpen">連接Socket</button>
<button @click="closeSocket">斷開(kāi)鏈接</button>
<button @click="submsgContent(true)">訂閱事件</button>
<button @click="submsgContent(false)">取消訂閱事件</button>
<button @click="socketSendmsg">發(fā)送數(shù)據(jù)</button>
<button @click="lockResult">查看鏈接參數(shù)</button>
</div>
</div>
</template>
<script>
import VueSocketIO from 'vue-socket.io'
export default {
data() {
return {
createSocketIO: null,
createSocketEmitter: null
}
},
beforeDestroy() { //訂閱事件記得要取消
if (this.createSocketIO) {
this.createSocketEmitter.removeListener('msgContent', this)
this.createSocketIO.close()
}
},
methods: {
async createSocket() {//也可在頁(yè)面初始話(huà)調(diào)用
let socketUrl
/* // 動(dòng)態(tài)ip與token實(shí)現(xiàn),ip+端口可通過(guò)接口拿取,token及用戶(hù)信息可通過(guò)vuex拿取
const userId = store.getters.userInfo.userId
const ipResult = await getHostIp()
if (ipResult.code !== 0) return
const { ip, port } = ipResult.data
const protocol = window.location.protocol
const socketUrl = `${protocol}//${ip}:${port}?userId=${userId}`
console.log('socketUrl', socketUrl)
*/
socketUrl = 'http://127.0.0.1:9527'
const createSocketItem = new VueSocketIO({
debug: true,
options: {
autoConnect: false,//默認(rèn)關(guān)閉,創(chuàng)建后打開(kāi),組件內(nèi)使用可直接打開(kāi),就不需要用io.open()
transports: ['websocket'],
query: {
token: 77777777777 //攜帶的額外參數(shù)也可通過(guò)url拼接實(shí)現(xiàn)
}
},
connection:socketUrl
})
const { io, emitter } = createSocketItem
io.query.ttt = 8888888888
this.createSocketIO = io
this.createSocketEmitter = emitter
io.open()
io.on('connecting', () => { console.log('正在連接---888') })
io.on('connect', () => { console.log('連接成功---888') })
io.on('disconnect', () => { console.log('斷開(kāi)連接---888') })
io.on('connect_failed', () => { console.log('連接失敗---888') })
io.on('error', () => { console.log('錯(cuò)誤發(fā)生,并且無(wú)法被其他事件類(lèi)型所處理') })
io.on('reconnect_attempt', () => {console.log('觸發(fā)嘗試重新連接', 888)})
io.on('reconnecting', () => { console.log('正在重連---888') })
io.on('reconnect_failed', () => { console.log('重連失敗---888') })
io.on('reconnect', () => { console.log('重連成功---888') })
emitter.addListener('welcome', (data) => {
console.log('data', data)
}, this)
},
socketOpen() {
this.createSocketIO.open()
},
socketSendmsg() { // 發(fā)送消息
this.createSocketIO.emit('hello', '這里是客戶(hù)端')
},
lockResult() {
console.log('鏈接狀態(tài)', this.createSocketIO.connected)
},
closeSocket() {
this.createSocketIO.close()
},
submsgContent(flag) {
if (flag) {
this.createSocketEmitter.addListener('welcome', (data) => {
console.log('data', data)
}, this)
} else {
this.createSocketEmitter.removeListener('welcome', this)
}
}
}
}
</script>
使用方式三 (全局掛載使用)[可驗(yàn)證]
當(dāng)前方法實(shí)現(xiàn)類(lèi)似直接使用socket.io,基本相當(dāng)于只是使用了vue-socket.io的庫(kù)與全局掛載的this.$scoket方法。
注: vue.use( new VueSocketIO({connection:'http://127.0.0.1:9527'})) 只將實(shí)例中io模塊掛載了this.$scoket
實(shí)現(xiàn)思路與遭遇問(wèn)題:
實(shí)現(xiàn):
通過(guò)本地輪詢(xún),實(shí)時(shí)獲取用戶(hù)登錄狀態(tài),用戶(hù)登錄后掛載全局方法
問(wèn)題:
同實(shí)現(xiàn)方式二問(wèn)題,vue-socket.io,在new.use()時(shí)掛載方法$socket到全局(io模塊),在創(chuàng)建實(shí)例時(shí)掛載sockets監(jiān)聽(tīng)對(duì)象到當(dāng)前實(shí)例;在輪詢(xún)拿到用戶(hù)當(dāng)前掛載相關(guān)方法時(shí),當(dāng)前項(xiàng)目實(shí)例已經(jīng)被創(chuàng)建,能拿到this.$socket,不能拿到this.sockets;需要使用頁(yè)面后置加載,或重新加載后正常,掛載方法才能正常使用
解決方式:
在創(chuàng)建實(shí)例時(shí),將除io,模塊的事件(emitter)與監(jiān)聽(tīng)(listener)也掛載到全局,使用相關(guān)定義方法在mounted生命周期中進(jìn)行;登錄前盡量把數(shù)據(jù)存儲(chǔ)好后在進(jìn)入系統(tǒng)
使用示例:
//main.js 入口文件全局掛載
// import Vue from 'vue'
import router from '@/router'
import store from '@/store'
import App from './App'
import '@/util/socket.js' //使用全局掛載
Vue.config.productionTip = false
const saasVue = new Vue({
router,
store,
i18n,
render: h => h(App)
}).$mount('#app')
window.saasVue = saasVue
// socket.js 全局方法實(shí)現(xiàn)
import Vue from 'vue'
import VueSocketIO from 'vue-socket.io'
import store from '@/store'
import { getHostIp } from '@/api/admin/message' //獲取后端動(dòng)態(tài)ip地址
let socketIo, socketListener, socketEmitter
if (!getToken()) {
const timer = setInterval(() => {
console.count()
if (!getToken()) return
initSocket(getToken())
window.clearInterval(timer)
}, 1000)
} else {
initSocket(getToken())
}
function getToken() {//獲取登錄標(biāo)識(shí)---請(qǐng)修改為自己項(xiàng)目標(biāo)識(shí)
return store.getters.access_token
}
async function initSocket(token) {
const userId = store.getters.userInfo.userId //獲取登錄標(biāo)識(shí)---請(qǐng)修改為自己項(xiàng)目用戶(hù)id
const ipResult = await getHostIp()
if (ipResult.code !== 0) return
const { ip, port } = ipResult.data
const protocol = window.location.protocol
const socketUrl = `${protocol}//${ip}:${port}?userId=${userId}`
console.log('socketUrl', socketUrl)
//const socketUrl = 'http://127.0.0.1:9527' //本地測(cè)試地址---nodejs服務(wù)代碼放后面
const socket = new VueSocketIO({
debug: process.env.NODE_ENV !== 'production',
options: {
autoConnect: false //已通過(guò)驗(yàn)證,全局使用可默認(rèn)打開(kāi),組件內(nèi)使用則默認(rèn)關(guān)閉,使用時(shí)在打開(kāi)
},
connection: socketUrl
})
const { emitter, io, listener } = socket
socketIo = io
socketListener = listener
socketEmitter = emitter
if (process.env.NODE_ENV !== 'production') {// 與socket鏈接相關(guān)全局處理,與后端預(yù)定定義事件在組件內(nèi)訂閱使用
io.on('connecting', () => { console.log('socketjs---正在連接') })
io.on('connect', () => { console.log('socketjs---連接成功') })
io.on('disconnect', () => { console.log('socketjs---斷開(kāi)連接') })
io.on('connect_failed', () => { console.log('socketjs---連接失敗') })
io.on('error', () => { console.log('socketjs---錯(cuò)誤發(fā)生,并且無(wú)法被其他事件類(lèi)型所處理') })
io.on('reconnect_attempt', () => { console.log('socketjs---觸發(fā)嘗試重新連接')})
io.on('reconnecting', () => { console.log('socketjs---正在重連') })
io.on('reconnect_failed', () => { console.log('socketjs---重連失敗') })
io.on('reconnect', () => { console.log('socketjs---重連成功') })
}
// Vue.use(socket) //只是掛載了io模塊,存在已加載頁(yè)面無(wú)法訂閱問(wèn)題,不如不使用,自己掛載方便
Object.defineProperty(Vue.prototype, '$socketIo', { value: socketIo })
Object.defineProperty(Vue.prototype, '$socketListener', { value: socketListener })
Object.defineProperty(Vue.prototype, '$socketEmitter', { value: socketEmitter })
}
export default { socketIo, socketListener, socketEmitter }
組件內(nèi)使用:
//demo.vue 訂閱事件與銷(xiāo)毀在組件中進(jìn)行
<template>
<div>
<button @click="socketOpen">連接Socket</button>
<button @click="closeSocket">斷開(kāi)鏈接</button>
<button @click="submsgContent(true)">訂閱事件</button>
<button @click="submsgContent(false)">取消訂閱事件</button>
<button @click="socketSendmsg">發(fā)送數(shù)據(jù)</button>
<button @click="lockResult">查看鏈接參數(shù)</button>
</div>
</template>
<script>
export default {
beforeDestroy() { //訂閱事件記得要取消
if (this.$socketIo) {
this.$socketEmitter.removeListener('msgContent', this)
this.$socketIo.close()
}
},
mounted() {
this.$socketIo.open()//初始化打開(kāi)鏈接
this.$socketEmitter.addListener('welcome', (data) => {//組件初始化掛載后,訂閱后端事件
console.log('data', data)
}, this)
},
methods: {
socketOpen() {
this.$socketIo.open()
},
socketSendmsg() { // 發(fā)送消息
this.$socketIo.emit('hello', '這里是客戶(hù)端')
},
lockResult() {
console.log('鏈接狀態(tài)', this.$socketIo.connected)
},
closeSocket() {
this.$socketIo.close()
},
submsgContent(flag) {
if (flag) {
this.$socketEmitter.addListener('welcome', (data) => {
console.log('data', data)
}, this)
} else {
this.$socketEmitter.removeListener('welcome', this)
}
}
}
}
</script>
使用方式推薦
方式一:適用于全局通知類(lèi)業(yè)務(wù)類(lèi)型多,不驗(yàn)證用戶(hù)登錄,一般是門(mén)口網(wǎng)站引流類(lèi)型使用。
方法二:適用于通知類(lèi)型較少的業(yè)務(wù)項(xiàng)目
方式三:適用項(xiàng)目通知類(lèi)型多,業(yè)務(wù)場(chǎng)景模塊較多情況,全局掛載,每個(gè)頁(yè)面只需要做對(duì)應(yīng)方法訂閱,與銷(xiāo)毀;例如:系統(tǒng)消息+業(yè)務(wù)消息待辦通知,首屏監(jiān)控、大屏展示下多個(gè)模塊多訂閱場(chǎng)景。
nodejs服務(wù)端本地demo代碼
相關(guān)依賴(lài):
npm install socket.io@2.0.4 -S
// nodeSocket.js
var http = require('http')
var io = require('socket.io')
// 創(chuàng)建server服務(wù)
var server = http.createServer(function(req, res) {
var headers = {}
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
headers['Access-Control-Allow-Credentials'] = true
headers['Access-Control-Max-Age'] = '86400' // 24 hours
headers['Access-Control-Allow-Headers'] = 'X-Requested-With, Access-Control-Allow-Origin, X-HTTP-Method-Override, Content-Type, Authorization, Accept'
res.writeHead(200, headers)
res.end()
})
// 啟動(dòng)服務(wù)器 監(jiān)聽(tīng) 1024 端口
server.listen(1024, function() {
console.log('server runing at 127.0.0.1:1024')
})
// 啟動(dòng)socket服務(wù)
var socket = io.listen(server, { origins: '*:*' })
socket.use((socket, next) => {
const query = socket.handshake.query
console.log('token', query.token)
if (query.token) {
return next()
}
return next(new Error('authentication error 鑒權(quán)失敗'))
})
// 監(jiān)聽(tīng)客戶(hù)端連接
socket.on('connection', function(socket) {
console.log('客戶(hù)端有連接')
// 監(jiān)聽(tīng)客戶(hù)端斷開(kāi)
socket.on('disconnect', () => {
console.log('客戶(hù)端斷開(kāi)')
})
// 給客戶(hù)端發(fā)送消息
setInterval(() => {
socket.emit('welcome', '歡迎連接socket')
console.count(1)
}, 2000)
// 監(jiān)聽(tīng)客戶(hù)端消息
socket.on('hello', data => {
console.log('接收客戶(hù)端數(shù)據(jù)---:', data)
})
})
啟動(dòng):
node nodeSocket.js
總結(jié)
- vue-socket.io使用版本注意,3.0.8、3.0.9有部分bug,感興趣的可以去試試;
- nodejs模塊使用的socket.io版本注意,socket.io版本大于了2.0.4后使用方法有變化;
- 消息訂閱同一方法可多次訂閱,訂閱幾次,后端一次返回了幾次訂閱消息。所以注意取消訂閱方法。
- 同一訂閱方法,在不同頁(yè)面使用,銷(xiāo)毀后會(huì)取消所有使用當(dāng)前方法的訂閱消息。需要不同模塊與后端約定不同方法,根據(jù)項(xiàng)目消息架構(gòu)處理;
以上就是vue使用 vue-socket.io三種方式及采坑記錄實(shí)例解析的詳細(xì)內(nèi)容,更多關(guān)于vue使用vue-socket.io采坑的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3實(shí)現(xiàn)多個(gè)表格同時(shí)滾動(dòng)并固定表頭
這篇文章主要給大家介紹了vue3中多個(gè)表格怎么同時(shí)滾動(dòng)并且固定表頭,文中通過(guò)代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-02-02
如何解決d3.event在v7版本無(wú)效影響zoom拖拽縮放問(wèn)題
這篇文章主要介紹了如何解決d3.event在v7版本無(wú)效影響zoom拖拽縮放問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
vue使用css-rcurlyexpected等less報(bào)錯(cuò)問(wèn)題
這篇文章主要介紹了vue使用css-rcurlyexpected等less報(bào)錯(cuò)問(wèn)題,具有很的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
解決vue中使用history.replaceState?更改url?vue?router?無(wú)法感知的問(wèn)題
這篇文章主要介紹了vue中使用history.replaceState?更改url?vue?router?無(wú)法感知的問(wèn)題,本文給大家分享修復(fù)這個(gè)問(wèn)題的方法,需要的朋友可以參考下2022-09-09
Vue + Vue-router 同名路由切換數(shù)據(jù)不更新的方法
本篇文章主要介紹了Vue + Vue-router 同名路由切換數(shù)據(jù)不更新的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
VueCli4項(xiàng)目配置反向代理proxy的方法步驟
這篇文章主要介紹了VueCli4項(xiàng)目配置反向代理proxy的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05

