歷經了接近兩個月的摸索滾爬,抓頭吃瓜,各種優化(單押X3),我主導開發的第一個小程序終于要上線了(SKR SKR!)!當然首先要感謝老板沒有殺了我——因為在6月初我剛拿到小程序PRD的時候老板問我多久可以做好,我看了看之后說“兩周”吧,咳咳,然后。。。一直到現在,我還能活著很Amazing有沒有???當然這其中也有一些為了追求“精品小程序”而一改再改所用的時間。好了,廢話還是不多說了,下面就開始總結下吧~
原生的小程序我本人并沒有學習過,更別提拿來開發一款商用的小程序了,剛好還在前公司時,當時的前端團隊在提到小程序的解決方案時有分享了mpvue,到了新公司之后技術老大也有提到mpvue,而我本人過去一年多也一直在寫vue,對vue寫法比較熟悉,而且新公司團隊對小程序期待已久,希望盡快上架,所以選擇mpvue來開發也是最快最合理的了!
看了mpvue的官方文檔,項目的搭建自然也選擇了官方推薦的 vue-cli , 在看了五分鐘上手教程后,使用命令
vue init mpvue/mpvue-quickstart my-project生成了基本的項目,在后來的開發中,項目的配置基本沒做改動,只是添加了less-loader。
基本上是vue-cli生成的目錄結構,加了部分文件夾,主要是與后臺進行數據交互所使用的框架flyio的配置文件夾(api文件夾),以及整個項目數據管理所使用的vuex(store文件夾),整體目錄結構如下:
project └───build └───config └───dist └───node_modules └───src └───api | ajax.js // flyio請求與響應攔截器的配置文件 | config.js // 請求的配置文件 | index.js // 生成請求api實例文件 | Server.js // 項目的數據請求統一管理文件 └───components └───pages └───store └───modules // vuex模塊文件夾 | index.js // vuex處理文件 | App.vue | config.js | main.js └───static └───images └───lib └───weui │ README.md │ package.json │ package-lock.json 復制代碼
相信很多使用過mpvue的同學都或多或少猜到了一些坑,我也是踩到了不少的坑浪費了不少的寶貴時間,雖然網上關于mpvue的踩坑的文章一搜一籮筐,但我還是要寫一下。。。下面就是我在本次小程序開發過程中遇到的坑(們)以及針對它們的解決方案:
### tabBar圖標問題 復制代碼
在 配置小程序原生的底部tabBar 時,遇到了第一個問題:在將設計師給我的圖標icon路徑設置正確的情況下, 開發者工具上的tabBar的圖標總是會很大,而且幾乎占滿了整個高度 ,相當難看,搜了很多博客都沒有找到解決辦法,期間還嘗試了自己實現tabBar,但是在看到那令人嘔嘔嘔的效果之后,我還是放棄了,又回到原生的tabBar,然后靜下心來想了想,最后在對比github上的一些mpvue的項目之后,發現原來是圖標icon的問題,最后成功解決: 就是icon尺寸保持不變,然后四周留出合適的透明(?)空白 ...很簡單有木有?就這浪費我很多腦細胞,原諒我的愚鈍(智障臉)。。。當然了,原生的tabBar其實還有一個問題就是, tabBar的標題文字在真機上會離底部特別特別近 ,這個我沒找到解決辦法,除了自己實現tabBar。。。
### 詳情頁數據保留之前舊數據的問題 復制代碼
這個問題我想很多同學都遇到過了,而且我看到mpvue github上的issues里面有很多人都遇到了這個問題并且都在持續關注,足以說明這是個痛點問題,誰讓它會影響小程序的用戶體驗呢。。。到目前為止看到的比較統一的解決辦法就是:在(詳情)頁面onLoad的時候,將要在本頁面展示的數據初始化并且進行新的賦值,舉:chestnut:如下:
<template> <html-text :text="htmltext"></html-text> </template> <script> import htmlText from xxxxx export default { components: { htmlText }, data () { return { htmltext: '' } }, onLoad () { this.htmltext = '' this.$http.get('xxxxxxxx').then((res) => { this.htmltext = res.htmltext }) } } </script> 復制代碼
其他數組或者對象類型的處理可能會麻煩一些,但是方法類似,在數據請求返回之前的這段時間內不想留空白尬對用戶的話就自己做一些loading,總是要強過用戶先面對舊數據再一閃跳到新數據的體驗。。。
### created鉤子函數在項目初始化時就全部執行的問題 復制代碼
這個我想應該是mpvue的一個bug吧?該鉤子函數在頁面內還是不要隨便用的好。。。
### 目前mpvue對于復雜富文本的支持目前性能較差的問題 復制代碼
這個微信原生的路由跳轉navigateTo(),redirectTo(),navigateBack(),switchTab(),reLaunch()等, 不能甩鍋給mpvue,對于展示“相當復雜”的富文本(內容較長,且由多張圖片甚至多張動圖)的需求,一般不會有很多用戶會遇到,但是很不巧的是,我遇到了。。。誰讓我們致力于做一個有逼格的品牌呢?有需求了就要解決,光能展示遠遠不夠,還得展示的優雅,目前的 mpvue-wxParse 其實已經能解決大部分問題了,也有一些github上的項目基于該項目開發得到了數百star,但是我用該項目做出來的效果老板和技術老大都相當不滿意,圖片沒法優雅的加載,而且由于htmltext太長在圖片全部解析顯示出來之前有著相當長的白屏尷尬時間,所以最后還是放棄該方案。
然后在github上找到了另一個在 mpvue-wxParse 的基礎上改進的針對復雜富文本的項目mpvue-htmlParse ,試了下稍微好了點,但離老板的要求還是差很遠,最后不得已在此項目基礎上fork出一份代碼針對老板的需求親自來改,最終得以過關,項目地址 mpvue-htmlParse ,該項目里主要針對 圖片的加載 做了改進,在第一張圖片加載完成后,通知主頁可以關閉preLoading效果,然后給每張圖片添加了菊花的加載效果,在圖片完全加載完成之前會顯示菊花圖,然后再根據設備屏幕寬度和圖片信息對圖片進行適當放大或者縮小,這樣整體下來的效果基本可以達到“破產版”微信公眾號推文的效果,該項目適用范圍有限,有需要的同學可以自己在此基礎上改進。
### 微信原生的路由跳轉navigateTo(),redirectTo(),navigateBack(),switchTab(),reLaunch()等,在真機上的表現較為怪異 復制代碼
對于參數的傳遞,我也遇到過類似于舊數據的問題,最后不得已借助于vuex才得以解決。另外小程序的頁面棧個數實在有限,所以在開發時一定要注意頁面棧的管理。
### onShow()的使用要注意 復制代碼
要記得該鉤子函數里的js代碼不只是剛進入頁面時會執行,在息屏后再次點亮后也將會執行。
對于mpvue的坑突然能想起來的不多了,目前就先寫這么多,后面想起來了再來更新吧。
在小程序的開發中,并沒有使用小程序原生的wx.request()來進行數據交互,而是選擇了mpvue文檔里推薦使用的Flyio,Flyio的介紹就不多做介紹,打架可以自己看文檔,這里我主要說一下的 請求和響應攔截器的構造 :
文檔里其實有很詳細的介紹以及代碼,但是我根據代碼寫下來之后在遇到登錄失效的問題時并沒有按照預想的解決:先鎖住請求然后重新請求拿到新的cookie之后再重新進行之前的請求,再和其他人討論之后使用promise解決了這一問題,具體可見代碼:
src/api/ajax.js:
/** * http請求攔截器 */ const Fly = require('flyio/dist/npm/wx') const config = require('./config') const ajaxUrl = process.env.NODE_ENV === 'development' ? config.Host.development : process.env.NODE_ENV === 'production' ? config.Host.production : config.Host.test let fly = new Fly() let loginFly = new Fly() // 定義公共headers const headers = { ... } Object.assign(fly.config, { headers: headers, baseURL: 'xxxxxx', timeout: 10000, withCredentials: true }) loginFly.config = fly.config // session失效后本地重新登錄 const login = () => { return new Promise((resolve, reject) => { wx.login({ success: res => { let loginParams = { ... } loginFly.post('/api/locallogin/url', loginParams).then(d => { if (d.headers && typeof d.headers['set-cookie'] !== 'undefined') { // 更新session wx.setStorageSync('sessionid', d.headers['set-cookie']) } resolve() }).catch(error => { log(error) reject(res.data) }) }, fail: res => { console.error(res.errMsg) }, complete: res => {} }) }) } // 請求攔截器 fly.interceptors.request.use(request => { if (wx.getStorageSync('sessionid')) { request.headers.cookie = wx.getStorageSync('sessionid') } return request }) // 響應攔截器 fly.interceptors.response.use( response => { // session已經失效,需要重新登錄小程序 if (response.data.errCode === 100009) { // log('session失效,根據之前存儲在本地的用戶信息重新請求session...') // 鎖定響應攔截器 fly.lock() return login().then(() => { fly.unlock() // log(`重新請求:path:${response.request.url},baseURL:${response.request.baseURL}`) return fly.request(response.request) }).catch(err => { log(err) }) } else { return response.data.data } }, err => { log('error-interceptor', err) if (err.status) { wx.showToast({ title: '出現未知錯誤', icon: 'none', duration: 3000 }) } } ) export default fly 復制代碼
因為是生活購物類小程序,涉及到 購物車 + 地址選擇 等較為復雜的邏輯,很多地方都需要數據共用,在本期項目中vuex起了很大的作用,因為模塊較多,如果將所有數據寫在一個文件里無疑會為后期維護帶來巨大困難,所以將各模塊的數據單獨劃分寫在各自的文件里,這樣整體流程就清晰了很多,下面是劃分模塊的主文件的代碼
src/store/index.js:
import Vue from 'vue' import Vuex from 'vuex' import modules1 from './modules/modules1' import modules2 from './modules/modules2' import modules3 from './modules/modules3' ... Vue.use(Vuex) export default new Vuex.Store({ // 做模塊化處理,每個功能一個store.js文件,然后統一在這邊引入 modules: { modules1, modules2, modules3, ... } }) 復制代碼
src/store/modules/modules1.js:
import api from '@/api' // actions里請求用到 const state = { aaaa, ... } const getters = { aaaa (state) { return state.aaaa }, bbbb (state, getters, rootState) { return getters.aaaa }, ... } // actions里可進行異步操作 const actions = { async anExample ({state, getters, dispatch, commit}, {params}) { let res = await api.requestFunction({params}) ... return res }, ... } const mutations = { setStateX (state, Y) { state.X = Y }, ... } export default { namespaced: true, // 很重要 state, getters, actions, mutations } 復制代碼
在.vue文件中調用
src/pages/xxx.vue
<script> import { mapState, mapGetters } from 'vuex' export default { computed: { // 調用getters ...mapGetters('modules', [ 'aaaa', 'bbbb' ]) }, methods: { // 調用action funcA () { this.$store.dispatch('modules1/anExample', {params}).then(res => { ... }) }, // 調用mutation funcB () { this.$store.commit('modules1/setStateX', Y) } } } </script> 復制代碼
本次總結目前先寫這么多吧,主要介紹了 項目結構 , 遇到的坑 (項目中遇到的問題很多,但是寫的時候突然覺得那些都不是問題了?) ,Flyio的使用 (重點為攔截器的配置),以及 vuex的簡單介紹 。其實項目開發完成之后想了想也沒那么多東西,只是期間走了不少的彎路,做了很多“無用功”,其實說是無用功,但也從中收獲了相當多,畢竟自己從無到有從0到1構建一個項目,其中的煩惱很多,但是真的能讓人成長很多,也讓我覺得相當充實。由于小程序是公司商用的不是我個人的項目,所以項目代碼就沒法開源了,如果有問題的話可以聯系我,為防廣告之嫌這里也不說明小程序的名字了,想來體驗下的可以私信我,也歡迎大家來指正!
老板來找我過第二期的需求了,Incoming!