Vue作为一个单页面应用, webSocket可以说是用起来非常爽了. 因为webSocket在同一个html文件中,是可以保持连接的. 但是当使用f5或者点击刷新页面以后, websocket连接就会强制关闭. 关于这个问题我研究了很久,最后找到两种解决办法;

先说下我踩过的坑,起初我设想,在登录并创建websocket连接成功后,设置一个字段保存在localStorage或者cookie: window.localStorage.setItem(‘connSocket’,’1’), 在关闭时触发window.localStorage.setItem(‘connSocket’,’0’),然后在vue的全局路由中进行判断, 每次路由发生变化时,如果已登录但connSocket值为0,则创建socket连接,否则不处理;

设想很美好, 然而实际操作过程中, 发现页面手动f5或按钮刷新时, 根本不触发websocketonclose事件, 因为此时连接并不是正常关闭的.....这个设想流产了, 但是启发了我第一个解决办法的思路. 后面再提;

我踩过的第二个坑, 是在网上百度到, js通过监控页面刷新事件, 可以进行一些操作,示例代码如下:

window.onbeforeunload = function(event) {
	console.log('您正在刷新网页');
	return 'Are you sure?';
};

当我把这段代码放到vue挂载的html中之后, 结果页面刷新根本不触发这个事件. 因此我转而研究vue的生命周期,尝试在destroyed的钩子函数中,设置localStorageconnSocket的值,没错,此时我仍旧是想通过这种方法来控制路由判断. 测试半天发现在这个钩子函数中并不能操作localStorage这个对象. 而且页面刷新时, 似乎并不会触发这个钩子函数, 反而每一次刷新,createdmounted函数是一定会被触发的. 于是我决定在created中操作localStorage. 终于生效了, 我成功删除了. 这个思路又启发了第二个解决办法的诞生, 并且比第一种简单许多许多. 接下来,就贴下两种解决方案的代码.

首先, 准备工作, 先封装一个连接websocket的函数:

//创建websocket连接
  connectSocket(host) {
    window.webSocket = new WebSocket(host);
    /*建立连接*/
    webSocket.onopen = evt => {
      console.log("webSocket连接成功");
      let data = {type: 'bind'};
      let json = JSON.stringify(data);
      webSocket.send(json);
    };
    /*连接关闭*/
    webSocket.onclose = evt => {
      console.log("webSocket连接关闭");
    };
    /*接收服务器推送消息*/
    webSocket.onmessage = evt => {
      let data = JSON.parse(evt.data);
      console.log(data)
    };
    /*连接发生错误时*/
    webSocket.onerror = (evt,e) => {
      console.log(evt);
    }
  }

封装好之后, 在main.js中把这个函数注册全局, 我是在我的项目中封装了一个包裹所有的全局函数的global.js文件,注入在了vue实例中. 如果只注入现在这个连接函数的话,参考下列代码:

Vue.prototype.$connectSocket= (上面贴的那个函数内容);

接下来说第一种解决方案:

在通过监听socket关闭事件时尝试修改localStorage值失败之后,我气得关上电脑去洗澡, 睡衣还没拿好我就突然想到了vuex, 之前一直苦恼vuex中存储的值在页面刷新后都被重置为默认值, 才想到把数据存储在localStorage中的结局方案. 此时我为何要绕远路把标记websocket连接的状态值存储在localStorage中呢,我为什么不存在vuex中呢.........想到这里就豁然开朗了,开始在全局路由拦截中进行判断.

/*登录状态下,判断是否存在websocket连接是否存在,不存在则立即建立连接*/
const listenSocket = ()=>{
  let userMessage = JSON.parse(window.localStorage.getItem('userMessage'));
  let token = userMessage ? userMessage.token : '';
  let socketState = store.state.socketConnect;
  if(token && !socketState){
    console.log(socketState);
    console.log('用户是登录状态,但并未创建socket连接')
    global.connectSocket('你的webSocket连接地址');
  }
}

/*全局路由控制*/
router.beforeEach((to, from, next) => {
  /* 路由发生变化修改页面title */
  if (to.meta.title) {
    document.title = to.meta.title
  }
  listenSocket();   //这调用了判断socket是否连接的函数, 在每一次路由变化时都会调用

  next()
  /*路由验证拦截*/
  //routerIntercept(to, from, next);
});

在这里我用localStorage中的值判断是否登录. 用vuex中的socketConnect判断是否创建连接. 在登录成功后,将socketConnect=true, 在页面刷新后socketConnect自动置为默认值false; 此时就会调用connectSocket()创建连接了; 如果正常的页面内路由跳转, 并没有刷新页面. 则socketConnect一直都是true, 不会重新创建socket连接了. 

总的来说第一种方法比较笨重,牵扯的对象比较多,有vuex, localStorage, 相对来说第二种方法就要简单许多许多了;

第二种方法, 前面说到刷新页面时,组件的created和mounted钩子函数是一定会被调用的, 因此我们可以在vue项目的根组件上,加一段代码, 我的项目是vue-cli生成的,因此根组件是app.vue;在这个app.vue中加入如下代码:

export default {
  name: 'App',
  created() {
    //当在任一路由页面被刷新时,即是根组件app被重新创建,此时可以进行webSocket重连
    //从localStorage中获取用户信息,是登录状态则可以进行webSocket重连
    let userMessage = window.localStorage.getItem('userMessage');
    if(userMessage){
      userMessage = JSON.parse(userMessage);
      this.$globalFunction.connectSocket('你的webSocket连接地址');
    }
  }
}

这里不需要用到vuex, 原理也很简单, 正常登录后创建的websocket连接会一直保持. 用户刷新页面后, 则是根组件被重新创建, 此时在钩子函数created中判断用户是否登录, 登录状态则创建socket连接, 否则不做任何处理.

这里还需要注意的一点是, 用户在手动退出登录之后, 应该关闭socket连接. 为了方便, 我在封装connectSocket函数时,创建websocket对象使用如下代码:

window.webSocket = new WebSocket(host);

也就是把创建的这个webSocket对象注册到window对象下, 这样我在站内的任何地方都可以很方便的调用这个websocket对象了, 退出登录时,只需调用如下方法:

webSocket.close();
总结的差不多啦~~我觉得大脑可以歇一歇了
Logo

基于 Vue 的企业级 UI 组件库和中后台系统解决方案,为数万开发者服务。

更多推荐