30

现在移动web app越来越热门了,许多公司开始尝试使用angular、react、vue等MVVM框架来开发单页架构的web app。但在开发web app时,如果希望页面的导航体验也接近原生应用,那一般都会遇到这两个问题:

  • 识别前进后退行为
  • 后退时恢复之前的页面

笔者开发了一个基于vue与vue-router的导航库vue-navigation,来帮助开发者来解决这些问题,下面是问题的解决思路。

识别前进后退

先说第一个问题。和原生app不一样,浏览器中主要有这几个限制:

  • 没有提供前进后退的事件
  • 不允许开发者读取浏览记录
  • 用户可以手动输入地址,或使用浏览器提供的前进后退来改变url

解决方案是自己维护一份浏览记录,每次url改变时,通过与记录的浏览记录作对比,从而判断出前进后退行为:

  • url存在于浏览记录中即为后退
  • url不存在于浏览记录中即为前进
  • url在浏览记录的末端即为刷新

另外,应用的路由路径中可能允许相同的路由出现多次(例如A->B->A),所以给每个路由添加一个key值来区分相同路由的不同实例。

这个浏览记录需要存储在sessionStorage中,这样用户刷新后浏览记录也可以恢复。

后退时恢复之前的页面

识别出后退行为后,下一步就是像原生一样恢复之前的页面了。

一种方案是页面继续存储在DOM中,添加样式display: none来告诉浏览器不渲染该元素,但是缓存的页面多了DOM就会变得很大,会影响页面的性能,本文不讨论这个方案。

另一种方案是将数据缓存到内存中,开发者需要将页面的数据存储起来,当返回到该页面时,再根据数据将页面恢复。但是这样每个页面存储的数据不通,一般需要进行额外的编码,如果有一种更底层的方案能解决这个问题,并且对开发者是透明的,就最好了,所以尝试并开发了vue-navigation

vue-navigation 0.x版本的时候,借助了vue的keep-alive来缓存页面,但是keep-alive是根据组件的name或tag来决定缓存的,所以带来了很多限制。

通过拜读keep-alive的源码,了解到它的缓存机制后,就自己实现了一个管理缓存的组件,来灵活地缓存子组件,实现思路如下:

  • 每次render时,先取到子组件的vnode(vue的虚拟dom)
  • 计算出vnode的key,把key值赋给vnode避免vue-router复用组件实例
  • 根据key值判断该节点是否已缓存

    • 已缓存:将缓存的实例赋给componentInstance,这样vue就会根据这个实例来恢复组件
    • 未缓存:将vnode存储到内存中,下次返回到该页面时可以从内存中恢复

另外还需要添加一个清除缓存的逻辑,当自己维护的浏览记录变化时,根据浏览记录清除不需要的缓存(例如当前的路由是:A->B->C,用户从C直接返回到了A,那么B和C都需要从缓存中删除)。

最后

虽然是基于vue来开发的,但是思路是不变的,使用其他框架也可以做到同样的事情。

还是安利一下vuevue-navigation。使用插件后,再将router-view放在navigation下就有缓存功能了。

main.js

import Vue from 'vue'
import router from './router' // vue-router 实例
import Navigation from 'vue-navigation'
Vue.use(Navigation, {router})
// 启动你的应用...

App.vue

<template>
  <navigation>
    <router-view></router-view>
  </navigation>
</template>

最后欢迎大家讨论或提供更好的解决方案。

35 条评论
狼族小狈 · 2017年07月31日

很赞!以前也有类似的想法,不过没做出来哈哈

+4 回复

0

你实现了吗

技术哥 · 7月10日
linshuai · 2017年07月31日

不错,试试看怎样。

+3 回复

0

你 实现了吗

技术哥 · 7月10日
zack24q 作者 · 2017年08月01日

依赖vue与vue-router,需要将router实例传入插件。

Vue.use(Navigation, {router})

+2 回复

0

为什么我的页面不能缓存

技术哥 · 7月10日
0

你的文章写得真的很好 ,但是我的没有效果呀

技术哥 · 7月10日
陌上寒 · 2017年08月01日

到处找解决这种问题的方法,我要试一试,一会再过来

+1 回复

0

你的页面缓存了

技术哥 · 7月10日
0

之前的项目采用hash路由,返回即刷新,后来放倒了一个容器里面,容器觉得定位,上下左右0.overflow-y:scroll,解决了返回纪录滚动条,
后来直接用keep-alive纪录滚动条,但是keep-alive,有局限性,效果不理想

陌上寒 · 7月10日
0

试了试怎么样,*实现了吗

技术哥 · 7月10日
hongyanght · 2017年08月01日

没有支持原生返回按钮吧

+1 回复

1

原理是检测URL的变化,所以浏览器的返回,或原生的返回都是可以的

zack24q 作者 · 2017年08月01日
1

嗯,还有一个问题,会记住所有之前浏览过的,还是会记住前几步。还有url上面的VNK这个参数可以去掉不?

hongyanght · 2017年08月01日
1

会记住此次会话的所有浏览过的。
参数的问题之前就考虑过,已经给vue-router提了feature request,待官方支持后,在history模式下就可以将VNK存储到History的state中。

zack24q 作者 · 2017年08月01日
陌上寒 · 2017年08月15日

keep-alive可以记录滚动条位置,vue-navigation好像不能记录滚动条位置

回复

0

是vue-router的html5模式能记录滚动条位置吧?keep-alive好像只管缓存。

vue-router的记录位置只记了window.scrollY,但是移动web应用一般不使用body作为滚动容器,所以这个功能也会失效;由于用户的滚动容器是不固定的,所以应该由开发者自己来管理滚动位置,可以参考example里的Page.vue的代码,只要几行代码就能记住滚动位置了。

如果大部分开发者的滚动容器在一个项目中都唯一的话,我会考虑把这个功能放到vue-navigation里,开发者可以传element进来,vue-navigation来帮忙管理。

zack24q 作者 · 2017年08月15日
0

不能缓存呀,

技术哥 · 7月10日
Meowu · 2017年08月15日

想问一下如果手动刷新浏览器怎么保持原来的状态?

回复

0

目前是不恢复的,只能保留浏览记录;而且浏览器的刷新应该类似于APP的重启,应该就是不保持原来状态的。

zack24q 作者 · 2017年08月15日
付二朋 · 2017年09月18日

你好,看了demo,有些疑问哈。列表页如果是缓加载也可以吗?如果详情页可以改变列表页的状态,这样的方式会有问题吗?

回复

0

可以的

zack24q 作者 · 2017年09月19日
0

针对这个问题我有一个疑问哈,比如地址列表页,点击一条地址可以进去进行修改和删除操作,由于用了。列表页拉去数据的方法 我是在mounted里面执行的。使用了vue-navigation之后,从地址列表页进入地址编辑页 进行完编辑【保存】或者【删除】之后,我会使用history.go(-1)返回列表页。但是这个时候由于返回之后列表页直接读的是缓存,显示编辑或者删除的那条地址并没有改变。
还有很多类似这种有数据更新的页面 返回就会出现类似这样没有重新拉取数据更新的页面,然后我发现 把页面数据请求放在activated这个方法里 返回列表页就会刷新。
请问作者大大,在activated这个方法里做数据请求 是这类问题的最佳解决方案吗?

Honey_hoo · 2017年10月23日
小楼昨夜又春风 · 2017年12月15日

但是好像router.beforeEach会执行两次!

回复

farmerz · 2017年12月31日

去某信贷二手车面试前端时说到了这个问题。由于优信贷的数据量和实时性也挺大的,面试官听到我讲述这个问题时稍微吸引到了其注意力。
首先:我提到自己的观点时当一个表格给的单条数据足够丰富我们可以缓存该条数据用以在更进一步的详情等组件中使用,可以把其用本地存储加vuex的方式保存。
可能这个问题他遇到过,然后放下手中的正在玩的手机问我:如果时效性很强的数据,比如可能下一秒就发生变化的数据怎么存储。其实问这个问题时他的心里已经有了坚定的答案,即这种的根本不能用本地存储,每次需要数据到要去请求数据库的。
我想了下回答道:如果只是浏览数据且对变化特别敏感可以使用缓存,但是可以配合see和moment之类的库来使用,这样可以在浏览时就可看到数据变化,毕竟数据时双向绑定的。
如果是开展业务那么这个数据在后端就已经被锁死了,不可能发生改变,也可以缓存。
总之面试没通过。
我问他自己看法时他也不说,只是说自己去好好查查。

回复

0

边玩手机边面试,666

Geocld · 1月24日
1

他怕他回答的还不如你

静茹鱼 · 3月1日
听说 · 3月15日

好像并没有起什么作用?是我用错了?

回复

0

对呀,我的页面也不能缓存 ,不知道怎么回事, 楼主也不见人

技术哥 · 7月10日
技术哥 · 7月9日

没有用是我用错了吗

回复

技术哥 · 7月13日

楼主能回复我一下吗

回复

范广健 · 7月30日

this.$navigation.once('back', (to, from) => {}) 怎么用啊 大神们

回复

载入中...