VUE如何重载当前视图

时间:2017-10-9 作者:剧中人

今天小剧来分享在使用 vue 时遇到一个问题,困扰小剧比较长时间。

概括下来就是:vue 项目如何在不修改 URL 的前提下主动 reload 当前 router?

先来说下场景

项目的业务模型中有一个独立的模块,用于处理全局数据:project,首先 project list 是一个可以进行切换的列表,其次还有一个当前选中的 project 项。所有 View Model 在初始化时都会依赖当前选中的 project。

那么问题来了

一个 View Model 已经被渲染完毕后,切换 project 时该如何操作?

仔细思考之后,发现有三个可行性较高的方案:

1、重新设计路由规则

通过场景分析可以看出来 project 是一个基础性字段,因此可以考虑将 project 字段显式地体现在 URL 上。在切换 project 时直接更新当前路由即可实现 View Model 的更新。

然而实际项目中此类基础字段还不少,如果全部体现在路由上既会导致 URL 冗长,又需要处理各种字符转码的问题。所以此方案暂不考虑。

2、View Model 监听 project 变动

这是一种比较常规的解决方案,也比较容易实行。具体做法是:View Model 内部去监听 project,每当值发生改变时,主动更新自身数据。

监听方案可以用 vuex,也可以通过全局的自定义事件来实现,如:

var bus = new Vue();
// project 模块触发事件
bus.$emit('project-switched', 'nanjin-1');

// 在 View Model 中监听事件
bus.$on('project-switched', function (projectName) {
  // ...
});

看起来很美好的实现方案,分析之后发现其仍然存在不少弊端,如:

分析之后发现,此方案虽然可以很好的解决问题,粒度也较细,但是存在一些弊端,可以作为备选方案。

3、project 切换后通过 router 去重载当前 View Model

此方案是借鉴了在 angular 项目中的经验,当 view 需要重载时,直接执行 $route.reload() 即可重新初始化当前视图。

虽然重载 router 下整个 view 这种方案粒度较粗,但优势在于改动范围小,只需要在 project 切换时执行重载即可。

可以算是一种简单、高效、易维护方案,在这三个方案中算是一个相对最优解。

问题就这样解决了 ?

本以为到这儿问题就已经轻松愉快地解决了,在翻阅 vue-router文档 后眉头一皱,却发现事情并不简单。

因为 vue router 不支持 reload 方法。

按照惯例,这类使用场景较高 API 一般在 issue 中都会有人去反馈。于是小剧分别在 vue、vue-router 两个项目中去找反馈。

结果正如小剧预料,有不少反馈表示需要 reload 或者替代方案。比较主流的讨论集中在这个 issue 中:[Feature] #296 「A reload method like location.reload() 」。

尤大神及其他 Contributor 均表示数据驱动的模型下不需要 reload 方法,更倾向于小剧前面提到的第二种方案。

虽然听起来很有道理,但小剧还是想要可以直接 reload 的方法。

有哪些 router reload 的替代方法

终于到了点题的时候了,VUE 项目下如何重载当前 router 对应的视图?

结合自己的思考和 issue 中其他同仁的讨论,小剧总结大致有这么几种方法可以作为替代:

第一种方案可谓粗暴至极,单页应用中异步通信相对较多,刷新页面也会引起用户直观的刷新感知,因此不建议使用此操作。

第二种方案看起来并无不妥,因为 vue 本身是数据驱动,控制了数据也就控制了视图表现,但是实际处理中却会丢失各个生命周期中进行的一些操作,因此也并不靠谱儿。

第三种方案看起来略显猥琐,实际使用起来却非常简单,既不会增加额外的历史记录,也不存在体验上的问题。

因此,在官方暂不提供 reload 方法支持的情况下,第三种方案可以视为最佳解决方案。

那该如何操作呢 ?

曲线救国型:先将 history replace 至一个中转页面,再 replace 回最初页面

先来看下小剧上面的描述,简单来看其实只有两次跳转而已,关键点在于跳转方法的选用上。push 方法会在浏览器的历史记录中增加一次访问记录,而 replace 不会。我们引入的这一次跳转本质上不希望引起用户的感知,所以 replace 方法在这里再合适不过了。

另外就是中转页的处理上,如果你配置了通配符 path: '*' 并且有重定向,那么中转页的 path 必须在路由中做对应配置。如果没有,中转页的 path 你可以写路由配置中任何不存在的路径。

还有就是两次 replace 操作在一个调用栈中进行,而且开始和结束的 path 也相同,极大可能会被浏览器忽略,所以可以引入一个定时器进行延时操作。

方法大致如下方代码所示。

function reloadRoute($router) {
  var curruntPath = $router.history.current.fullPath;
  $router.replace({
    path: '/_empty'
  });
  setTimeout(function () {
    $router.replace({
      path: curruntPath
    });
  });
}

写到这儿,我们已经解决了 VUE 项目下重载当前 router 对应的视图的问题,猥琐但有效。


PS:视图重载这类需求的场景其实蛮多的,举几个例子:

最后,期待官方提供 reload 方法。