vue 在移動端體驗上的優化解決方案

去年年末本身搭了一個vue在移動端的開發框架,感受體驗不是很好。上個星期又要作移動端的項目了。因此我花了兩天時間對以前的那個開發框架作了如下優化css

  • 自定義vuex-plugins-loading
  • 路由切換動畫 + keep alive 動態管理緩存組件
  • better-scroll與vue的最佳實踐(better-scroll的vue化)
  • 自定義指令(vue-finger:包括點擊,長按,雙擊,拖拽移動,多點觸控,滑動,旋轉,縮放手勢)
  • 移動端適配方案
  • 如何分狀況處理頁面置頂
  • 路由懶加載

自定義 vuex-plugins-loading

若是每一個頁面在數據加載完成前,展現loading。你首先想到的是每一個頁面設置狀態,show和hide狀態。可是這樣冗餘代碼太多了,並且本身寫的都煩。我以前的react的項目中用到了dva,其中有dva-loading庫,以前就有研究過,因此我就用他的思想,本身寫一個vuex-loading。
實現思路:vuex中註冊一個管理loading的module,經過綁定異步的action,將每一個action的loading存在vuex中,這樣我在每一個頁面只須要在vuex的store中拿相對應的action loading就能達到此目的
html

## 核心代碼
    store.subscribeAction({
      before: action => {
        if (shouldEffect(action, includes, excludes)) {
          store.commit({ type: namespace + '/SHOW', payload: action.type })
        }
      },
      after: action => {
        if (shouldEffect(action, includes, excludes)) {
          store.commit({ type: namespace + '/HIDE', payload: action.type })
        }
      }
    })
  }
}
複製代碼

使用以前你們能夠先了解一下subscribeAction
想安裝此插件,請點擊這裏,記得給個star喲
注意: 使用上述代碼,vuex必需爲3.1.0版本。由於subscribeAction在3.1.0才新增的
vue

補充

我以爲仍是寫一下爲何推薦你們使用vuex-plugins-loading這種處理數據流的思路。react

我也是從前年接觸dva.js的時候,感受到這種處理數據流的思路會讓你的整個項目更清晰,更易迭代。別人接手你的整個框架理解起來比較輕鬆。
git

如今我就講一下我這邊是怎麼根據dva.js的思想封裝vuex的。其實很簡單github

## store 文件目錄下的 index.js
import Vue from 'vue'
import Vuex from 'vuex'
import home from './modules/home'
import createLoadingPlugin from 'vuex-plugins-loading'
Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    home,
  },
  plugins: [createLoadingPlugin()]
})
複製代碼
# home.js
import { loadDataApi } from '../../service/api'

const state = {
  listData: [],
}

const mutations = {
  getData (state, payload) {
    state.listData = state.listData.concat(payload.res.data.data)
    state.page = payload.res.data.page
    state.pageNumber = Math.ceil(payload.res.data.total / payload.res.data.pageSize)
  },
  refreshData (state, payload) {
    state.listData = payload.res.data.data
  },
}
const getters = {

}
const actions = {
  loadMore ({ commit, state }, data) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const { page, type } = data
        loadDataApi({ page }).then(res => {
          if (res.code === 200) {
            if (type === 'loadMore') {
              commit({
                type: 'getData',
                res: res
              })
            } else {
              commit({
                type: 'refreshData',
                res: res
              })
            }
            resolve()
          } else {
            reject(res.error)
            Toast(res.error)
          }
        })
      }, 1000)
    })
  }
}
複製代碼
## Home.vue
computed: {
    // Getting Vuex State from store/modules/home
    ...mapState({
      listData: state => state.home.listData,
      loading: state => state['@@loading'].effects['home/loadMore']
    }),
  },
  methods: {
    ...mapActions('home', ['initData', 'plusPage', 'initPage']),
    // onLoad 加載數據
    onLoad () {
      this.requestData('loadMore')
    },
    requestData (type) {
      setTimeout(() => {
        this.$store.dispatch('home/loadMore', {

        }).then(() => {
        })
      }, 1000)
    },
    onRefresh () {
      this.initPage().then(() => {
        this.requestData('refresh')
      })
    },
複製代碼

1.先說vuex-plugins-loading內層是怎麼實現的?vue-router

1.plugins: [createLoadingPlugin()],

2.loading: state => state['@@loading'].effects['home/loadMore']

使用1此插件的時候會綁定你使用過的每一個action,在我使用2的時候會將綁定action的loading拿到。
這裏面是有兩個過程的。store.subscribeAction 中的before和after。在我調用action以後個人loading一直是true,這就是在before裏面的操做。當個人action裏面包裹的是promise,則走完resolve()或者reject()後整個action纔算完成。以後就會走after。vuex

before api

after promise

after

我知道你們會問爲何把大量邏輯放在vuex裏面處理。其實我反而以爲這樣會讓你的整個項目清晰化,可維護性高。易於迭代。

.vue文件只是將vuex裏面的數據進行頁面渲染。

store裏面將數據存取,進行過濾。

serve層用來封裝你的api

這樣一目瞭然

路由切換動畫 + keep alive 動態管理緩存組件

以前採用的是全局設置路由切換動畫,可是體驗效果不是很好,特別是返回列表頁,頁面會引發回彈,頁面切換時會有暫時的空白。

未改造前的,也是參考別人的作法

## app.vue
 <transition :name="transitionName"> 
    <keep-alive :include="data">
        <router-view></router-view>
    </keep-alive>
 </transition>
 
  computed: {
    // 數據存放在vuex裏面
    ...mapState({
      data: state => {
        return state.global.data
      }
    })
  },
  methods: {
    // 設置Keep_alive路由
    setKeep_alive (to) {
      if (to.meta.keepAlive) {
        this.$store.dispatch({
          type: 'global/setData',
          payload: to.name
        })
      }
    }
  },
  watch: {
    '$route' (to, from) {
      // 此時從from頁面跳轉到to頁面
      this.setKeep_alive(to)
      const routeDeep = ['/', '/list', '/detail', '/reservation', '/addCars']
      const toDepth = routeDeep.indexOf(to.path)
      const fromDepth = routeDeep.indexOf(from.path)
      if (!from.name) {
        this.transitionName = 'fold'
        return
      }
      this.transitionName = toDepth > fromDepth ? 'fold-left' : 'fold-right'
    }
  },
複製代碼
## router.js
scrollBehavior (to, from, savedPosition) {
    // keep-alive 返回緩存頁面後記錄瀏覽位置
    if (savedPosition && to.meta.keepAlive) {
      return savedPosition
    }
    // 異步滾動操做
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({ x: 0, y: 1 })
      }, 0)
    })
  },
複製代碼

兩個問題

  1. 列表頁滑動到必定位置後跳轉到詳情頁,返回列表頁頁面回彈
    緣由:原生滾動條的位置是不變的,使用scrollBehavior,根據上述代碼可知滾動條會有一個閃爍的過程先置頂,而後滾動到上次保留的位置。
  2. 頁面切換時會有暫時的空白,過渡不正常。

改造後

## app.vue
<keep-alive :include="data">
  <router-view></router-view>
</keep-alive>
computed: {
    // 數據存放在vuex裏面
    ...mapState({
      data: state => {
        return state.global.data
      }
    })
  },
  methods: {
    // 設置Keep_alive路由
    setKeep_alive (to) {
      if (to.meta.keepAlive) {
        this.$store.dispatch({
          type: 'global/setData',
          payload: to.name
        })
      }
    }
  },
  watch: {
    '$route' (to, from) {
      // 此時從from頁面跳轉到to頁面
      this.setKeep_alive(to)
    }
  },
複製代碼
list.vue
<Scroll
  ref="scroll"
  class="scroll-home"
  :scrollbar="scrollbar"
  :probeType="3"
  :pullDownRefresh="pullDownRefresh"
  :pullUpLoad="true"
  @pullingDown="onRefresh"
  @scroll="scroll"
  @pullingUp="onLoad"
>
 <div class="contantView">
 </div>
</Scroll>
複製代碼

1.採用better-scroll後,第一個問題能夠直接解決。並且不用設置scrollBehavior,不懂能夠去看better-scroll

2.給頁面CSS添加設置「position:absolute;」,此時頁面脫離文檔流,不佔空間,這樣就不會把下一頁擠下去,完成平滑過渡。使用better-scroll給頁面CSS添加設置「position:fixed;」。

若是頁面佈局裏面有用到flex佈局,必定要給flex組件加一個position爲absolute或者fixed的div。

上述代碼中已有keep alive 動態管理緩存路由的思路。

better-scroll與vue的最佳實踐

以前在一篇文章上看到BetterScroll多是目前最好用的移動端滾動插件,因此此次就想試試,滴滴開源的cube-ui組件庫裏面大多數用到的滑動組件都是基於better-scroll,體驗了一下感受還挺好。爲何沒有用cube了?由於我的感受主題顏色有點醜。因此本身就打算基於better-scroll封裝一個vue版本的scroll組件。不說那麼多了,立立刻圖:

想用better-scroll還有另一個緣由,我想自定義上下拉的動畫。

想看demo及源碼請點擊這裏。記得給個star喲

自定義指令 vue-finger

包括點擊,長按,雙擊,拖拽移動,多點觸控,滑動,旋轉,縮放手勢

這一塊我這邊是基於別人的demo改造的,在這些指令裏面你能夠作不少在移動端手勢方面想作的事情。後續我會繼續迭代這些指令,制定出體驗接近原生的組件,你們要關注個人github

移動端適配方案

## rem.js
const baseSize = 32
// 設置 rem 函數
function setRem () {
  // 當前頁面寬度相對於 750 寬的縮放比例,可根據本身須要修改。
  const scale = document.documentElement.clientWidth / 750
  // 設置頁面根節點字體大小
  document.documentElement.style.fontSize = (baseSize * Math.min(scale, 2)) + 'px'
}
// 初始化
setRem()
// 改變窗口大小時從新設置 rem
window.addEventListener('resize', function () {
  setRem()
})

## main.js
import './rem'
複製代碼

還有最後還有一步。對於常常寫樣式的同窗,px轉rem是否是感受很煩。 我這邊處理的方式是,在項目根目錄新建一個postcss.config.js文件。這樣你只需按照設計稿的樣式,正常寫px就好。運行項目時會自動幫你轉成rem。

module.exports = {
  plugins: {
    'autoprefixer': {
      browsers: ['Android >= 4.0', 'iOS >= 7']
    },
    'postcss-pxtorem': {
      rootValue: 16,
      propList: ['*']
    }
  }
}
複製代碼

如何分狀況處理頁面置頂

上文有講到vue-router裏面scrollBehavior這個方法。

## router.js
scrollBehavior (to, from, savedPosition) {
    // keep-alive 返回緩存頁面後記錄瀏覽位置
    if (savedPosition && to.meta.keepAlive) {
      return savedPosition
    }
    // 異步滾動操做
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve({ x: 0, y: 1 })
      }, 0)
    })
  },
複製代碼

可是感受添加頁面轉場動畫後。頁面會有回彈。因此我就放棄它了。不添加動畫的能夠考慮。
我這邊用到了better-scroll後就不用擔憂這個問題。看完better-scroll文檔介紹,你就會發現better-scroll就是爲移動端運用而生的。

路由懶加載

當打包構建應用時,JavaScript 包會變得很是大,影響頁面加載。若是咱們能把不一樣路由對應的組件分割成不一樣的代碼塊,而後當路由被訪問的時候才加載對應組件,這樣就更加高效了。 這是路由懶加載就很重要了。看過官方文檔你們應該都會用了,這裏我就不介紹了。

// 路由懶加載
const _import_ = file => () => import('./views/' + file + '.vue')

routes: [
    {
      path: '/',
      name: 'home',
      component: _import_('Home/Home'),
      meta: {
        title: '首頁',
        keepAlive: true
      }
    },
]
複製代碼

終於寫完了,以上這些就是我在移動端體驗優化的實戰。但願能幫到你們。若是日後有什麼好的優化方案我會繼續更新。謝謝你們的觀看。以爲好的點個贊喲👍

相關文章
相關標籤/搜索