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不作数据请求和处理,而把请求数据的工做交给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分发调取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来干,这样就将功能进一步的分离,便于后期的代码维护。