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

前言

Vue.js 2.x發佈以後,陸陸續續作了七八個項目,摸索出來了一套本身的狀態管理模式,我將之稱爲Vuet。它以規則來驅動狀態更新,它帶來的是開發效率上的飆升,它就像草原,而你是野馬,任你隨意馳騁,總之它是爲敏捷開發而誕生。javascript

原因

在大型的Vue應用程序開發中,多組件通訊、多頁面通訊,每每是跨不過的坎,一個頁面組件中每每參雜着頁面獲取數據的代碼和響應用戶操做的代碼,稍有不慎,就使得代碼混亂不堪。A、B、C三個頁面中,都須要一樣的數據,而後每個頁面都寫一次、發送一次請求,不久以後,代碼就十分臃腫了。所以咱們就須要vuex這樣的第三方庫來管理狀態了html

Vuet誕生初衷

從列表點擊進去到詳情,從詳情返回後,咱們指望能顯示回原來的位置,而不是整個頁面從新初始化,從新請求數據,這樣帶來的是用戶體驗的極度糟糕的,咱們指望能有一種規則來定義狀態應該如何更新,這即是Vuet.js誕生的初衷。它以規則來定義狀態的更新,它也是一種Vue.js全新的狀態管理模式。天生的規則驅動,使得本次教程的主題,也將變得異常簡單,由於咱們只須要定義好頁面更新的規則便可實現。vue

有了Vuex還須要Vuet作什麼?

Vuex和Vuet的出發點不同,Vuex不建議直接更新狀態,而是經過提交mutation來更新狀態,而Vuet則是容許的。所以Vuex和Vuet是能夠配合使用的,而且有着不一樣的應用場景,該用Vuex的地方就用Vuex,可用Vuet的地方,就可使用Vuetjava

開始

上面廢話了那麼久,也是由於Vuet.js纔剛剛誕生,急需你們的支持。嗯,接下來咱們開始本次的主題!node

目錄結構

|-- 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實例化入口文件

上面是咱們本次項目的基本目錄結構git

安裝模塊

npm install vue vue-router vuet --save

這些都是基本的模塊,想必不用多說,你們都知道的。github

route規則

先給出官方文檔地址
本章的主題,核心就是在route規則身上,它能幫你獲取、更新、重置頁面的狀態,配合v-route-scroll指令就能幫你處理頁面的全局滾動條和div元素自身的滾動條vue-router

code社區api爲例子

  • main.jsvuex

    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.jsnpm

    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-route-scroll="{ path: 'topic-detail', name: 'xxx' }"
      -->
      <div v-route-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-route-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自身的滾動條,這樣就能大大的提高了咱們的用戶體驗

相關文章
相關標籤/搜索