Vue.js轻松实现页面后退时,还原滚动位置

前言

Vue.js 2.x发布之后,陆陆续续做了七八个项目,摸索出来了一套自己的状态管理模式,我将之称为Vuet。它以规则来驱动状态更新,它带来的是开发效率上的飙升,它就像草原,而你是野马,任你随意驰骋,总之它是为敏捷开发而诞生。

缘由

在大型的Vue应用程序开发中,多组件通信、多页面通信,往往是跨不过的坎,一个页面组件中往往参杂着页面获取数据的代码和响应用户操作的代码,稍有不慎,就使得代码混乱不堪。A、B、C三个页面中,都需要同样的数据,然后每一个页面都写一次、发送一次请求,不久之后,代码就十分臃肿了。因此我们就需要vuex这样的第三方库来管理状态了

Vuet诞生初衷

从列表点击进去到详情,从详情返回后,我们期望能显示回原来的位置,而不是整个页面重新初始化,重新请求数据,这样带来的是用户体验的极度糟糕的,我们期望能有一种规则来定义状态应该如何更新,这便是Vuet.js诞生的初衷。它以规则来定义状态的更新,它也是一种Vue.js全新的状态管理模式。天生的规则驱动,使得本次教程的主题,也将变得异常简单,因为我们只需要定义好页面更新的规则即可实现。

有了Vuex还需要Vuet做什么?

Vuex和Vuet的出发点不一样,Vuex不建议直接更新状态,而是通过提交mutation来更新状态,而Vuet则是允许的。因此Vuex和Vuet是可以配合使用的,并且有着不同的应用场景,该用Vuex的地方就用Vuex,可用Vuet的地方,就可以使用Vuet

开始

上面废话了那么久,也是因为Vuet.js才刚刚诞生,急需大家的支持。嗯,接下来我们开始本次的主题!

目录结构

|-- pages                 // 页面组件
|   |-- topic             // 主题模块
|       |-- Detail.vue    // 主题详情
|       |-- List.vue      // 主题列表
|-- router                // router相关
|   |-- index.js          // 入口文件
|   |-- router.js         // 实例化VueRouter
|-- vuet                  // vuet相关
|   |-- index.js          // 入口文件
|   |-- topic-detail.js   // 主题详情的状态
|   |-- topic-list.js     // 主题列表的状态
|   |-- vuet.js           // 实例化Vuet
|- index.html             // 程序页面入口文件
|- main.js                // Vue实例化入口文件

上面是我们本次项目的基本目录结构

安装模块

npm install vue vue-router vuet --save

这些都是基本的模块,想必不用多说,大家都知道的。

route规则

先给出官方文档地址
本章的主题,核心就是在route规则身上,它能帮你获取、更新、重置页面的状态,配合v-vuet-scroll指令就能帮你处理页面的全局滚动条和div元素自身的滚动条

code社区api为例子

  • main.js
      import Vue from 'vue'
      import router from './router/'
      import vuet from './vuet/'
      
      export default new Vue({
        el: '#app',
        vuet,
        router,
        render (h) {
          return h('router-view')
        }
      })
    
  • vuet/index.js
      import vuet from './vuet'
      
      export default vuet
    
    
  • vuet/vuet.js
      import Vue from 'vue'
      import Vuet from 'vuet'
      import topicList from './topic-list'
      import topicDetail from './topic-detail'
      
      Vue.use(Vuet)
      
      const vuet = new Vuet({
        data () {
          return {
            loading: true, // 请求中
            loaderr: false // 请求失败
          }
        },
        pathJoin: '-', // 父子模块的连接路径
        modules: {
          topic: {
            list: topicList,
            detail: topicDetail
          }
        }
      })
      
      vuet.beforeEach(({ path, params, state }) => {
        state.loading = true
        state.loaderr = false
      })
      
      vuet.afterEach((err, { path, params, state }) => {
        state.loading = false
        state.loaderr = !!err
      })
      
      export default vuet
    
    
  • vuet/topic-list.js
      export default {
        routeWatch: 'query', // 定义页面的更新规则
        data () {
          return {
            data: [],
            tabs: [
              {
                label: '全部',
                value: 'all'
              },
              {
                label: '精华',
                value: 'good'
              },
              {
                label: '分享',
                value: 'share'
              },
              {
                label: '问答',
                value: 'ask'
              },
              {
                label: '招聘',
                value: 'job'
              }
            ]
          }
        },
        async fetch ({ route }) {
          const { tab = '' } = route.query
          const { data } = await window.fetch(`https://cnodejs.org/api/v1/topics?mdrender=false&tab=${tab}`).then(response => response.json())
          return {
            data
          }
        }
      }
    
    
  • vuet/topic-detail.js
      export default {
        routeWatch: 'params.id', // 定义页面的更新规则
        data () {
          return {
            data: {
              id: null,
              author_id: null,
              tab: null,
              content: null,
              title: null,
              last_reply_at: null,
              good: false,
              top: false,
              reply_count: 0,
              visit_count: 0,
              create_at: null,
              author: {
                loginname: null,
                avatar_url: null
              },
              replies: [],
              is_collect: false
            }
          }
        },
        async fetch ({ route }) {
          const { data } = await window.fetch(`https://cnodejs.org/api/v1/topic/${route.params.id}`).then(response => response.json())
          return {
            data
          }
        }
      }
    
    
  • router/index.js
      import router from './router'
      
      export default router
    
    
  • router/router.js
      import Vue from 'vue'
      import VueRouter from 'vue-router'
      import TopicList from '../pages/topic/List'
      import TopicDetail from '../pages/topic/Detail'
      
      Vue.use(VueRouter)
      
      const RouterView = {
        render (h) {
          return h('router-view')
        }
      }
      
      const router = new VueRouter({
        routes: [
          {
            path: '/',
            component: RouterView,
            children: [
              {
                path: '',
                name: 'topic-list',
                component: TopicList
              },
              {
                path: '/:id',
                name: 'topic-detail',
                component: TopicDetail
              }
            ]
          }
        ]
      })
      
      export default router
    
    
  • pages/topic/List.vue
  <template>
    <!-- 
        设置指令监听全局滚动条,
        注意了,光是设置指令可不行,还需要在组件中使用route规则,
        来处理页面滚动的操作,
        局部滚动条直接去掉.window即可
        如果需要同时记录全局滚动条和div滚动条直接设置.window.self即可
        它能做到N多个滚动位置记录,具体看官方文档喔!
        注:记录div滚动的话,需要设置一个name来识别
        v-vuet-scroll="{ path: 'topic-detail', name: 'xxx' }"
    -->
    <div v-vuet-scroll.window="{ path: 'topic-list' }">
      <header>
        <ul>
          <li v-for="item in list.tabs">
            <router-link :to="{ name: 'topic-list', query: { tab: item.value } }">{{ item.label }}</router-link>
          </li>
        </ul>
      </header>
      <ul class="list">
        <li v-for="item in list.data">
            <router-link :to="{ name: 'topic-detail', params: { id: item.id } }">{{ item.title }}</router-link>
        </li>
      </ul>
    </div>
  </template>
  <script>
    import { mapRules, mapModules } from 'vuet'
  
    export default {
      mixins: [
        // 设置模块的更新规则
        mapRules({
          route: 'topic-list'
        }),
        // 连接模块的状态
        mapModules({
          list: 'topic-list'
        })
      ]
    }
  </script>
  <style scoped>
  
  </style>
  • pages/topic/Detail.vue
  <template>
    <div v-vuet-scroll.window="{ path: 'topic-detail' }">
      <h3>{{ detail.data.title }}</h3>
      <div v-html="detail.data.content"></div>
    </div>  
  </template>
  <script>
    import { mapRules, mapModules } from 'vuet'
  
    export default {
      mixins: [
        // 设置模块的更新规则
        mapRules({
          route: 'topic-detail'
        }),
        // 连接模块的状态
        mapModules({
          detail: 'topic-detail'
        })
      ]
    }
  </script>
  <style scoped>
  
  </style>

总结

咋的一看,Vuet看起来也不是很复杂,只需要定义好模块状态,然后在组件中设置对应的规则来更新模块的状态即可。其实vuet自带的route规则能够支持同时记录全局滚动条、div自身的滚动条,这样就能大大的提升了我们的用户体验

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容