基於Vuex實現更好的數據流請求和渲染

18年年末個人最後一次迭代任務,其中之一就是對咱們社區首頁作優化改版。咱們社區工程是基於vue的服務端渲染腳手架工程——nuxt開發的,目的是爲了首屏服務端渲染,進而保證SEO。因此,時至今日我都不是太理解爲啥掘金沒有作服務端渲染。可能自己就足夠NB而不須要SEO了吧,嘿嘿嘿。vue

可能有不少朋友說,滾動加載很簡單,徹底沒有必要看。但我在寫代碼的過程當中仍是以爲有不少地方是值得借鑑的。固然,大牛繞道哈,謝謝您。vuex

首先先看一下效果: api

首頁圖片

首頁(index.vue)都是以卡片流的形式展現,分欄爲推薦、關注和熱門類,其中,推薦類卡片比較複雜,其內容涉及多個接口的調取和數據的粘合,在此不作業務上的詳述,總之明確一點:數據處理比較繁瑣。瀏覽器

先來整理一些我所理解的vuex中的概念和一些平時容易被忽略但卻很實用的方法:bash

(1) Vuex是用於狀態管理,其核心目的在於集中式存儲,實現多個組件共享狀態。但到底什麼纔算是狀態數據?若是不存在多個組件共享數據,那還要不要用vuex呢?我我的認爲若是數據獲取和處理機制比較複雜,那將這部分工做代理到vuex上也是很棒的,且效果會更好;異步

(2) vuex中絕對不能直接更改state中的值,而是必需要經過提交mutation來變動。其中mutation執行的是同步任務,action執行的是異步任務,且action經過dispatch方法觸發;fetch

(3) action相似於mutation,不一樣在於:action提交的是mutation,而非直接變動狀態;action能夠包含任意異步操做。這樣,咱們就能夠把請求的執行放到action中去,獲得數據後再提交mutation來更新store。優化

代碼規劃

下面整理一些內容給你們ui

index.vue

這裏我作的規劃是,因爲數據的處理環節比較繁瑣,所以能夠把請求數據和整合數據的過程通通從index.vue中抽離出來,即index.vue不作數據請求和處理,而把請求數據的工做交給vuex,index.vue只在computed中實時獲取vuex的數據。這樣作的意義在於功能思路清晰,便於維護,進一步簡化了index.vue,其部分核心代碼以下:this

// index.vue
<template>
    <div class="topstoryHeader-nav">
      <a @click="_handleClick('recommend');">
        <span :class="{'hight-light-span':activeName=='recommend'}">推薦</span>
      </a>
      <a @click="_handleClick('attention')">
        <span :class="{'hight-light-span':activeName=='attention'}">關注</span>
      </a>
      <a @click="_handleClick('hot')">
        <span :class="{'hight-light-span':activeName=='hot'}">熱門</span>
      </a>
    </div>
    
    <section v-if="activeName=='recommend'" v-kscroll="loadRecommend">
       <card-render v-for="(item,index) in cards_topstories" :key="index" :dataItem="item"/>  //卡片渲染組件
    </section>
    <section v-show="activeName=='attention'" v-kscroll="loadAttentioned">
       <card-render v-for="(item,index) in cards_attention" :key="index" :dataItem="item"/> 
    </section>
    <section v-show="activeName=='hot'" v-kscroll="loadHot">
       <card-render v-for="(item,index) in cards_hot" :key="index" :dataItem="item" />
    </section>
</template>
<script>
import { mapGetters } from "vuex";
export default {
    data() {
        return {
            activeName: "recommend" //初始化設置爲推薦欄,
            page:1, //用於推薦欄的數據請求
            pageSize:10, //用於推薦欄的數據請求,
            
            //關注欄參數
            attentPayload: {
              page: 1,
              pageSize: 10
            },
            //熱門欄參數
            hotPayload: {
              page: 1,
              pageSize: 10
            }
        }
    },
    computed: { },
    methods: { },
}
</script>
複製代碼

滾動加載機制

寫一個比較基礎的滾動加載鉤子:

Vue.directive('kscroll', {
    bind: function(el, binding) {
        window.addEventListener('scroll', _.debounce(() => {
            if (Math.max(window.pageYOffset || 0, document.documentElement.scrollTop) + window.innerHeight +
                10 >= document.body.offsetHeight) {
                binding.value.call();
            }
        }, 150))
    },
    unbind: function() { }
})
複製代碼

可是目前該方法有一個缺陷是當瀏覽器縮放時就會失效,若是有更好的辦法,但願您能教教我,謝謝。

數據加載機制

上述index.vue實現了基本的DOM渲染,接下來即獲取數據機制,本文設計思路便是將全部請求都放在vuex中,每次頁面經過分發dispatch來執行請求。以本文爲例,在store文件夾下建立一個專門給首頁用的中央數據處理機制home.js,核心代碼以下:

// home.js
import {$fetch} from '../plugins/fetch'
import {$api} from '../plugins/api'
export const state = () => ({
  cards_topstories:[],
  cards_attention:null,
  cards_hot:null,

  recommond_last:null,
  attention_last:null,
  hot_last:null
})

export const getters = {
   cards_topstories: (state) => state.cards_topstories,
   cards_attention: (state) => state.cards_attention,
   cards_hot: (state) => state.cards_hot,

   recommond_last: (state) => state.recommond_last,
   attention_last: (state) => state.attention_last,
   hot_last: (state) => state.hot_last
}

export const mutations = {
    //保存推薦卡片集
   SET_STORIES: (state , payload) => {
       if(payload.curArray) { 
         state.cards_topstories = state.cards_topstories.concat(payload.curArray);
         state.recommond_last = payload.last;
       }
   },
   //保存關注卡片集
   SET_ATTENTIONED_STORIES: (state , payload) => {
       if(payload === 0) { //清空cards_attention列
         state.cards_attention = null;
         return;
       }
       if(payload.curArray && !state.cards_attention) {
          state.cards_attention = [];
       }
       state.cards_attention = state.cards_attention.concat(payload.curArray.content);
       state.attention_last = payload.isLast
   },
   //保存熱門卡片集
   SET_HOT_STORIES: (state , payload) => {
       if(payload.curArray && !state.cards_hot) {
         state.cards_hot = [];
       }
       state.cards_hot = state.cards_hot.concat(payload.curArray.content);
       state.hot_last =  payload.isLast;
   }
}
export const actions = {
    loadTopStories:({ state, commit }, payload) => {
      let tempArrays = [];//臨時存放
      //加載推薦卡片
      $fetch.get($api.TOP_STORIES,{
          page: payload.page , 
          pageSize:payload.pageSize,
          types: ["Answer", "Article", "News", "Course"]
      }).then( res => {
        if(res.errorCode) return
        tempArrays = res.content;
         commit({
             type:'SET_STORIES',
             curArray:tempArrays,
             last:res.last
         })
      })
  },
  loadAttentionedStories: ({state, commit}, payload) => {
      let params = 
      $fetch.get($api.TOP_ATTENTION_STORIES , payload).then(res => {
          if(res.errorCode) return
          commit({
              type:'SET_ATTENTIONED_STORIES',
              curArray:res,
              isLast:res.last
          })
      })
  },
  loadHotStories: ({state, commit}, payload) => {
      $fetch.get($api.TOP_HOT_STORIES,{
        page: payload.page , 
        pageSize:payload.pageSize,
      }).then(res => {
          if(res.errorCode) return
          commit({
            type:'SET_HOT_STORIES',
            curArray:res,
            isLast:res.last
          })
      })
  }
}
複製代碼

如上代碼,每次當頁面要執行加載數據的時候,就須要從新調整好請求的參數,並經過dispatch分發機制執行action中的方法。數據獲取後,再經過提交mutation更新數據,以後再在頁面中經過實時獲取getters中的數據來更新視圖。

在index.vue中寫入dispatch分發方法

index.vue不直接參與數據請求,可是也要多少間接參與一下,即經過dispatch分發調取home.js中的action,以後action再經過將數據提交mutation。index.vue 的methods中寫入方法:

// index.vue
methods: {
    loadRecommend() {
      //在這裏就能夠作一些業務層級的限制操做,如未登陸時只能滾動加載3次等
      //限制只能是加載推薦類卡片流,以及當前不是最後的結果
      if (this.activeName != "recommend" || this.recommond_last) return;
      
      this.$store.dispatch("home/loadTopStories", {
        page: ++this.page,
        pageSize: this.pageSize
      });
    },
    loadAttentioned() {
      if (this.activeName != "attention" || this.attention_last) return;
      let params = {
        page: this.attentPayload.page,
        pageSize: this.attentPayload.pageSize
      };
      if (!this.attentAll) {
        params.insulation = true;
      }
      this.$store.dispatch("home/loadAttentionedStories", params);
      this.attentPayload.page++;
    },
    loadHot() {
      if (this.activeName != "hot" || this.hot_last) return;
      this.$store.dispatch("home/loadHotStories", this.hotPayload);
      this.hotPayload.page++;
    }
}
複製代碼

數據分發好後,接下來就是當數據在vuex中整理好後index.vue要實時獲取到最新的數據,即經過計算屬性獲取getters,而當一個組件須要獲取多個數據狀態時,將這些狀態都聲明爲計算屬性會有些重複和冗餘,在此採用mapGetters形式簡化代碼:

// index.vue
import { mapGetters } from 'vuex'
export default {
   computed: {
        ...mapGetters({
          cards_topstories: "home/cards_topstories",
          cards_attention: "home/cards_attention",
          cards_hot: "home/cards_hot",
          recommond_last: "home/recommond_last",
          attention_last: "home/attention_last",
          hot_last: "home/hot_last"
        })
    }   
}
複製代碼

這樣,就實現了請求、處理數據與獲取、渲染數據徹底隔離的效果。最後總結一下就是:頁面組件經過計算屬性獲取最新的數據,觸發滾動加載時,只是提供好參數(如page和pageSize),剩下的都交給vuex來幹,這樣就將功能進一步的分離,便於後期的代碼維護。

相關文章
相關標籤/搜索