Vue.js輕鬆實現頁面後退時,還原滾動位置

狼族小狽發表於2017-06-11

前言

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自身的滾動條,這樣就能大大的提升了我們的使用者體驗

相關文章