需求:在一个vue的项目中,我们需要从一个列表页面点击列表中的某一个详情页面,从详情页面返回不刷新列表,而从列表的上一个页面重新进入列表页面则需要刷新列表。

  而浏览器的机制则是每一次的页面打开都会重新执行所有的程序,所以这个功能并不能直接实现。而vue-router给我们提供了一个叫scrollBehavior的回调函数,我门可以用这个方法结合keep-alive能很好的实现这个功能,下面第一步附上实现代码:

  首先我们创建a,b,c,d四个页面,在路由的meta属性中添加需要缓存的页面标识(isKeepAlive):

  1. import Vue from 'vue'
  2. import Router from 'vue-router'
  3. const HelloWorld = () => import('@/components/HelloWorld')
  4. const A = () => import('@/components/router-return/router-a')
  5. const B = () => import('@/components/router-return/router-b')
  6. const C = () => import('@/components/router-return/router-c')
  7. const D = () => import('@/components/router-return/router-d')
  8.  
  9. Vue.use(Router)
  10.  
  11. const routes = [
  12. {
  13. path: '/',
  14. name: 'HelloWorld',
  15. component: HelloWorld
  16. }, {
  17. path: '/a',
  18. name: 'A',
  19. component: A
  20. }, {
  21. path: '/b',
  22. name: 'B',
  23. component: B,
  24. meta: {
  25. isKeepAlive: true
  26. }
  27. }, {
  28. path: '/c',
  29. name: 'C',
  30. component: C
  31. }, {
  32. path: '/d',
  33. name: 'D',
  34. component: D
  35. }
  36. ]

然后我们修改app.vue页面:

  1. <template>
  2. <div id="app">
  3. <img src="./assets/logo.png">
  4. <keep-alive>
  5. <router-view v-if="$route.meta.isKeepAlive"/>
  6. </keep-alive>
  7. <router-view v-if="!$route.meta.isKeepAlive"/>
  8. </div>
  9. </template>

最后我们添加new Router方法的scrollBehavior的回调处理方法:

  1. export default new Router({
  2. routes,
  3. scrollBehavior (to, from, savedPosition) {
  4. // 从第二页返回首页时savedPosition为undefined
  5. if (savedPosition || typeof savedPosition === 'undefined') {
  6. // 只处理设置了路由元信息的组件
  7. from.meta.isKeepAlive = typeof from.meta.isKeepAlive === 'undefined' ? undefined : false
  8. to.meta.isKeepAlive = typeof to.meta.isKeepAlive === 'undefined' ? undefined : true
  9. if (savedPosition) {
  10. return savedPosition
  11. }
  12. } else {
  13. from.meta.isKeepAlive = typeof from.meta.isKeepAlive === 'undefined' ? undefined : true
  14. to.meta.isKeepAlive = typeof to.meta.isKeepAlive === 'undefined' ? undefined : false
  15. }
  16. }
  17. })

scrollBehavior方法中的savedPosition参数,每一次点击进去的值为null,而点击浏览器的前进与后退则会返回上一次该页面离开时候的pageXOffset与pageYOffset的值,然后我们可以根据这个返回的值来修改路由信息里面的isKeepAlive值来控制是否显示缓存。

我们来看下vue-router里面scrollBehavior执行的源码:

在vue-router.js的1547行发现:

  1. function handleScroll ( router, to, from, isPop) {
  2. if (!router.app) {
  3. return
  4. }
  5.  
  6. var behavior = router.options.scrollBehavior;
  7. if (!behavior) {
  8. return
  9. }
  10.  
  11. {
  12. assert(typeof behavior === 'function', "scrollBehavior must be a function");
  13. }
  14.  
  15. // wait until re-render finishes before scrolling
  16. router.app.$nextTick(function () {
  17. // 得到该页面之前的position值,如果没有缓存则返回null
  18. var position = getScrollPosition();
  19. var shouldScroll = behavior(to, from, isPop ? position : null);
  20.  
  21. if (!shouldScroll) {
  22. return
  23. }
  24.  
  25. if (typeof shouldScroll.then === 'function') {
  26. shouldScroll.then(function (shouldScroll) {
  27. // 移动页面到指定位置
  28. scrollToPosition((shouldScroll), position);
  29. }).catch(function (err) {
  30. {
  31. assert(false, err.toString());
  32. }
  33. });
  34. } else {
  35. // 移动页面到指定位置
  36. scrollToPosition(shouldScroll, position);
  37. }
  38. });
  39. }

再看下上面方法中用到的几个主要方法的写法:

  1. // getScrollPosition 得到移动的坐标
  2. function getScrollPosition () {
  3. var key = getStateKey();
  4. if (key) {
  5. return positionStore[key]
  6. }
  7. }
  8.  
  9. // scrollToPosition 页面移动方法
  10. function scrollToPosition (shouldScroll, position) {
  11. var isObject = typeof shouldScroll === 'object';
  12. if (isObject && typeof shouldScroll.selector === 'string') {
  13. var el = document.querySelector(shouldScroll.selector);
  14. if (el) {
  15. var offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {};
  16. offset = normalizeOffset(offset);
  17. position = getElementPosition(el, offset);
  18. } else if (isValidPosition(shouldScroll)) {
  19. position = normalizePosition(shouldScroll);
  20. }
  21. } else if (isObject && isValidPosition(shouldScroll)) {
  22. position = normalizePosition(shouldScroll);
  23. }
  24.  
  25. if (position) {
  26. window.scrollTo(position.x, position.y);
  27. }
  28. }

然后我们看看vue-router是怎么缓存页面x,y的坐标的,上面的getScrollPosition是用来获取坐标的,那么肯定也有保存坐标的方法,在getScrollPosition的上面一个方法则是saveScrollPosition就是保存的方法:

  1. // saveScrollPosition
  2. function saveScrollPosition () {
  3. var key = getStateKey();
  4. if (key) {
  5. positionStore[key] = {
  6. x: window.pageXOffset,
  7. y: window.pageYOffset
  8. };
  9. }
  10. }

而这个保存的方法会有一个key值是缓存的标识,继续查找getStateKey

根据上面代码发现key值就是一个时间值。而setStateKey则是一个key值更新的方法,然后继续查找setStateKey执行的地方:

  1. function setupScroll () {
  2. // Fix for #1585 for Firefox
  3. window.history.replaceState({ key: getStateKey() }, '');
  4. window.addEventListener('popstate', function (e) {
  5. saveScrollPosition();
  6. if (e.state && e.state.key) {
  7. setStateKey(e.state.key);
  8. }
  9. });
  10. }

然后发现该方法执行的地方是popState执行的时候,而key的来源则是popState返回参数里面的state属性里面,而state值的设定则是pushstate执行的时候传进去的,所以我们继续查pushstate执行的方法:

  1. function pushState (url, replace) {
  2. saveScrollPosition();
  3. // try...catch the pushState call to get around Safari
  4. // DOM Exception 18 where it limits to 100 pushState calls
  5. var history = window.history;
  6. try {
  7. if (replace) {
  8. history.replaceState({ key: _key }, '', url);
  9. } else {
  10. _key = genKey();
  11. history.pushState({ key: _key }, '', url);
  12. }
  13. } catch (e) {
  14. window.location[replace ? 'replace' : 'assign'](url);
  15. }
  16. }

根据上面代码发现,每次push的时候都会去生成一个当前时间的key值保存在state里面,作用于popstate时使用。

那么到此scrollBehavior方法的整个执行逻辑就清楚了:该方法最主要的是运用了浏览器的popstate方法只会在浏览器回退与前进才会执行的机制,在页面进入时生成一个唯一的key值保存在state里面,离开的时候将页面滚动位置保存在state里面的唯一key值上。每次pushstate的时候key值都是最新的,没有缓存所以返回null,而执行popstate的时候state里面的key都有缓存,则返回上次离开时候的滚动坐标。

vue vue-router 完美实现前进刷新,后退不刷新。附scrollBehavior源码解析的更多相关文章

  1. vue单页应用前进刷新后退不刷新方案探讨

    引言 前端webapp应用为了追求类似于native模式的细致体验,总是在不断的在向native的体验靠拢:比如本文即将要说到的功能,native由于是多页应用,新页面可以启用一个的新的webview ...

  2. Vue源码解析之数组变异

    力有不逮的对象 众所周知,在 Vue 中,直接修改对象属性的值无法触发响应式.当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变. 这是什么原因? 原因在于: Vue 的响应式 ...

  3. vue系列---响应式原理实现及Observer源码解析(一)

    _ 阅读目录 一. 什么是响应式? 二:如何侦测数据的变化? 2.1 Object.defineProperty() 侦测对象属性值变化 2.2 如何侦测数组的索引值的变化 2.3 如何监听数组内容的 ...

  4. 【vuejs深入二】vue源码解析之一,基础源码结构和htmlParse解析器

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. vuejs是一个优秀的前端mvvm框架,它的易用性和渐进式的理念可以使每一个前端开发人员感到舒服,感到easy.它内 ...

  5. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  6. vue UI库iview源码解析(2)

    上篇问题 在上篇<iview源码解析(1)>中的index.js 入口文件的源码中有一段代码有点疑惑: /** * 在浏览器环境下默认加载组件 */ // auto install if ...

  7. Vue源码解析---数据的双向绑定

    本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...

  8. Vue源码解析之nextTick

    Vue源码解析之nextTick 前言 nextTick是Vue的一个核心功能,在Vue内部实现中也经常用到nextTick.但是,很多新手不理解nextTick的原理,甚至不清楚nextTick的作 ...

  9. 【VUE】Vue 源码解析

    Vue 源码解析 Vue 的工作机制 在 new vue() 之后,Vue 会调用进行初始化,会初始化生命周期.事件.props.methods.data.computed和watch等.其中最重要的 ...

随机推荐

  1. 关于Flume以及Kafka理解

  2. centos 开机执行的命令

    centos开机执行的命令-------待验证,因为有可能涉及到root问题,没想明白怎么输入密码 1.增加rc.local可执行权限 chmod +x /etc/rc.d/rc.local 2.在里 ...

  3. JS获取屏幕分辨率以及当前对象大小等

    <script type="text/javascript"> function getInfo(){ var s = ""; s += " ...

  4. NSJSONSerialization能够处理的JSONData

    NSJSONSerialization能够处理的JSONData You use the NSJSONSerialization class to convert JSON to Foundation ...

  5. Batch Normalization 笔记

    原理 BN的效果 Why BN works? 原理 输入层可以归一化,那么其他层也应该可以归一化.但是有个重要的问题,为什么要引入beta和gamma. 为什么要引入beta和gamma 不总是要标准 ...

  6. spring初始化完成后执行初始化数据方法

    Spring提供的解决方案三种: 1.InitializingBean package com.foriseland.fsoa.fabricca; import com.foriseland.fsoa ...

  7. June 28th 2017 Week 26th Wednesday

    Anger begins with folly, and ends in repentance. 愤怒以愚蠢开始,以后悔告终. Learn to control your temper, don't ...

  8. GO语言(七)多核并行化的问题

    package main import "fmt" type Vector []float64 func (v Vector) DoSome(i,n int, u Vector, ...

  9. 【海龟汤策略】反趋势交易策略源代码分享(基于BOTVS)

    策略介绍: 海龟之汤,简称“龟汤”,是个与海龟交易法则相反的交易策略,它利用了跟势交易(特别是海龟方式)在很多假突破方面的缺陷来获利(把海龟做成汤吃掉).上世纪八十年代早期,有个非常著名的交易员团体— ...

  10. Micro

    Micro 架构与设计   Micro 架构与设计 翻译自 Micro architecture & design patterns for microservices 注: 原文作者即 Mi ...