2016-12-02 21:41:21

vue2:数据获取的一点思考

当我第一次把项目升到vue2的时候,就发现了一个问题: 官方推荐在组件内使用watch来监听路由变化,然而:

watch没有next参数,无法阻止路由的改变

从博客源码的第二个issue开始

本来博客前台一直有issue上这个问题,但是由于SSR做的还算不错,光首屏加载博主这边都能压进160毫秒,纯XHR请求更加快了,issue中的问题其实在我看来是一闪而过的,因此一直没有修。

但是读者找上门来要博客加个loading状态栏,就不得不面临上面描述的路由问题了

LoadingBar的实现

LoadingBar的实现是比较简单的,在此略过,只需要在任意地方dispatch一下设置LoadingBar的宽度即可。

但是路由方面就太坑了,这一段主要记录一下解决这个问题的一些尝试

LoadingBar宽度由vuex的action控制

这里的意思是,当一些action(例如获取前10条博文)被触发时,在其中的api调用前开始让LoadingBar动起来,再在api结束时将其结束掉。

其实这个思路一点问题也没有,但是别忘了:watch无法阻止路由改变

因此,得另找一些办法

尝试在beforeRouteEnter中触发action

本来挺简单的,因为博客前台已经改成了SSR的形式,所有数据接口都是写在不在组件对象中的一个纯函数,因此可以直接在beforeRouteEnter调用,比较方便

然而让我震惊的是,SSR中beforeRouteEnter同样会被触发。好吧,其实这个不是很震惊,尝试跳过,发现Vue.prototype.$isServer返回是否是SSR环境,那么在SSR中直接跳过beforeRouteEnter呢?结果是,客户端beforeRouteEnter被调用了两次。

excuse me?从vue-router@2.0.1升到@2.0.3也没解决

其实问题也不大,我自己弄一个计数器,大于1的时候直接不执行beforeRouteEnter就好了

beforeRouteEnter/beforeEnter的蛋疼之处

解决了上面的问题,还有个更蛋疼: beforeRouteEnter/beforeEnter无法在路由params/query变化时被触发。这导致了首页翻页时无法触发LoadingBar

从这几个钩子的名字上来说,这个没有问题,但是我现在想监听所有路由的更改,并且想有控制是否进入这个路由的权限呢? watch这种蛋疼的东西是肯定不行的。

beforeEach出马

好吧,这时候想起了我是怎么在博客前台发谷歌统计的呢?

    router.afterEach(route => {
      clientGoogleAnalyse(route.path)
    })

然后测试了一下,发现beforeEach真的可以监听到仅仅是路由的params/query的变化。

最后在客户端入口文件中写成下面这样,终于把第二个issue给解决了:

router.beforeEach((to, from, next) => {
  let loadingPromise = store.dispatch('START_LOADING')
  let endLoadingCallback = (path) => {
    return loadingPromise.then(interval => {
      clearInterval(interval)
      store.dispatch('SET_PROGRESS', 100)
      next(path)
    })
  }

  let component = router.getMatchedComponents(to.fullPath)[0]
  // there must be a matched component according
  // to routes definition
  if (component.preFetch) {
    // component need fetching some data before navigating to it
    return component.preFetch(store, to, endLoadingCallback).catch(err => {
      console.error(Date.now().toLocaleString(), err)
    })
  } else {
    // component's a static page and just navigate to it
    endLoadingCallback()
  }
})

修改完后的代码好看得出乎我的意料,每个组件都是单独写的一个外部函数用来dipatch action获取数据,组件内只需要写一个preFetch接口,这样preFetch接口不仅在SSR中被用到了,在客户端渲染中也被完全使用了,少了很多代码,让我感觉每个组件非常清爽,结构清晰。有兴趣的读者可以自己翻一翻博客前台的组件代码

最后,如果不需要控制是否进入路由,那么完全可以自行封装一个类似于vue-resource和axios中的截取器,在截取request时触发Loading加载,在截取response时触发Loading结束。

思考

vue-router在我修issue的时间段上,正好有一个关于beforeRouteEnter和beforeEnter无法在参数变化时触发的讨论

讨论中有两种方法可以解决:

  • 添加一个beforeRouteUpdate,这个钩子专门监听参数变化。
    • 这样有个好处,不需要beforeRouteEnter向后兼容,但是这样的话,更高一级的beforeEnter钩子还是无法监听参数变化,还得继续加一个钩子
  • 让beforeRouteEnter/beforeEnter在参数变化时也被触发。
    • 我比较喜欢这个解决办法,因为watch实在是没有什么卵用。我觉得这本来就是vue2升级后没有考虑周全,因为没记错的话,vue1组件的route对象,是可以监听到参数变化的,为什么升到vue2反而不行了呢?

2017-01-25更新

Evan已经增加了beforeRouteUpdate的接口
此接口能够拿到当前组件的上下文this以及next,因此本文所提到的使用beforeEach来全局控制Loading组件的方法,已经可以拆分到组件级别的路由钩子中了

本文链接:https://smallpath.me/post/vue2-before-guards

-- EOF --