強烈推介的幾個微信小程式開發小技巧,簡單又實用

SHERlocked93發表於2020-08-25

Eu_62tl0bcU

前段時間在下開發了個微信小程式,開發過程中總結了一些我覺得對我有用的小技巧,提煉出來,相當於一個總結覆盤,也希望可以幫助到大家。如果對大家確實有幫助,別忘了點贊哦 ? ~

  1. 微信開發者工具版本:1.03.2006090(2020-06-19)
  2. 基礎庫版本: v2.12.1 (2020-08-04)

1. 開發中可能遇到的坑以及 Tips

本來想寫個小技巧的,結果我總結了一堆坑,沒上手之前完全想象不到微信小程式的開發體驗是如此之差、如此之爛,從微信開發者工具到所謂的「全新語言」,都有一種濃濃的半成品的 five 即視感,實在讓我 emmm.... 另外我發現網上的小程式文章大部分都是如何使用和如何避坑的實用文,而不是技巧文,這也從側面反映了小程式的坑多。

在微信小程式原生開發過程中,我不斷髮出這樣的疑問「為什麼堂堂技術人才多如牛毛的騰訊,會推出如此 laji」,很多弱智反人類的地方,在兩三年前社群就已經提出來,官方回覆已經反饋正在修復中,但幾年過去了,還是沒有音信,官方回覆仍然是一句冷冰冰的「已反饋」 ?

  1. 微信開發者工具經常熱更新不起作用甚至白屏,重新編譯也不行,只能強行退出後再次開啟;
  2. 跟上一條類似,有時候一點樣式出錯,預覽整個都白屏,偵錯程式裡也不說哪裡的問題,直接就給你棄療不顯示,重新編譯也無法解決問題,只能強行退出後再次開啟;
  3. 跟上一條類似,偵錯程式裡報的錯經常沒什麼用,驢頭不對馬嘴,讓人很難定位問題;
  4. Android 端自定義 Tabbar 在下拉重新整理的時候,也會跟著螢幕一起往下移,而且是無法繞過的 Bug,自定義 Tabbar 樣式都寫好了的我又改成自帶的 Tabbar 了!
  5. import 的路徑不支援絕對路徑,比如你希望引用 utils/fetch.js,在不管多深的元件裡面你都要慢慢 ../ 點到根目錄,同樣 .wxss 檔案 @import 匯入檔案時也只能使用相對路徑,所以就會出現 ../../../../../../utils/fetch.js 這種東西;
  6. 靜態資源路徑不能有漢字,有漢字就無法載入;
  7. .wxs 檔案不支援 ES6,只能使用蹩腳的 ES5 寫法;
  8. .wxml 中只能引入 .wxs 檔案不能引入 .js 檔案???
  9. 模板 {{}} 中連方法都不能執行,只能處理簡單的運算如 + - * /,如果遇到資料需要 filter 的場景,需要在 .js 檔案中預先格式化好再一個個 setData,比如經常寫的 [2,3,4].includes(type),居然都跑不起來!
  10. .wxs 檔案中無法使用 Date 物件,所以不能 new Date(),只能使用蹩腳的 getDate 方法,正則也是一樣,生成正則物件需要使用 getRegExp 函式 getRegExp(pattern[, flags])
  11. .wxs 中可以呼叫其它 .wxs 檔案,並且只能 require 呼叫 .wxs 檔案,引入的檔案必須使用相對路徑;
  12. setData 連一個物件合併都懶得做,如果 data: {a: {b: 1, c: 1}},那麼 setData({a: {b: 2}}) 就會丟失 a.c 的值,真是讓人火冒三丈啊,還要 setData({['a.b': 2]}) 這樣才行;
  13. IOS 上 Date 物件獲取任意時間引數比如 getDaygetTime 都為 NaN,是因為 IOS 的 Date 建構函式不支援 2018-04-26 這種格式的日期,必須轉換為 2018/04/26 這種格式才會顯示正常;
  14. 開發版小程式有時候請求莫名其妙發不出去,右上角三個點 enable debug 開啟「開發除錯」之後就莫名其妙能發出去請求了,在多部手機上都是這樣,不明真相。

2. 微信請求 Promise 化

2.1 使用現成的庫

安裝 Promise 庫 wx-promise-pro,記得一定要帶 -s--production,要不然無法構建成功。

npm i -S wx-promise-pro

然後在 app.js 中:

import { promisifyAll } from 'wx-promise-pro'

promisifyAll()  // promisify all wx api

App({ ... })

之後就可以正常使用了:

wx.pro.showLoading({
    title: '載入中',
    mask: true
})
  .then(() => console.log('in promise ~'))

2.2 自己實現

其實我們可以自己來實現一個這樣的庫,原理很簡單,以原生 API 的 wx.request 為例:

// 原生 API 使用方式
wx.request({
    url: '',     // 請求的 url
    data: {},    // 引數
    method: '',  // post、get
    success: res => {
        // 請求成功回撥函式,res為回撥引數
    },
    fail: res => {
        // 請求失敗回撥函式,res為回撥引數
    }
})

如果我們將其 Promise 化,應該的呼叫方式希望是:

// Promise 化後的期望使用方式
wx.pro.request({
    url: '',     // 請求的 url
    data: {},    // 引數
    method: ''   // post、get
})
  .then(res => {
      // 請求成功回撥函式,res為回撥引數
  })
  .catch(res => {
      // 請求失敗回撥函式,res為回撥引數
  })

並且 then 函式返回的是一個 Promise 物件,讓這個函式可以不斷鏈式呼叫下去,所以首先需要 new 出來一個 Promise 物件:

function request(opt) {
    return new Promise((resolve, reject) => {
        wx.request({
            ...opt,
            success: res => { resolve(res)},
            fail: res => {reject(res)}
        })
    })
}

這裡程式碼我們可以進一步改進,由於 successfail 這裡傳入的引數只是由 resolvereject 方法執行了下,所以可以直接傳入 resolvereject 方法即可。

另外,由於其他小程式原生 API 格式一致,所以我們可以使用柯里化方法,來將其他需要進行 Promise 化的 API 進行處理:

function promisify(api) {
    return (opt = {}) => {
        return new Promise((resolve, reject) => {
            api({
                ...opt,
                fail: reject,
                success: resolve
            })
        })
    }
}

然後,將柯里化方法執行的結果作為新的 Promise 化的 API 掛載到 wx.pro 物件上:

// 將指定 API 進行 Promise 化
wx.pro.request = promisify(wx.request)

// 使用
wx.pro.request({...})
    .then(...)

然後為了方便我們使用其他方法,可以迴圈將 wx 物件上可以被 Promise 化的方法比如 requestscanCodeshowToastgetUserInfo 等一一掛載到 wx.pro 物件上,使用時可以直接 wx.pro.xx,由於這個方法執行返回的是一個 Promise 物件,因此可以像其它 Promise 化的物件那樣使用。

事實上,不知不覺,我們就自己實現了 wx-promise-pro 的原始碼,這個庫的核心程式碼也就是上面那這幾行 ?

2.3 在專案中使用

有了上面的工具後,我們可以將其使用在專案中,為了不在專案中遍佈 wx.requestwx.pro.request 這裡可以簡單進行封裝,新建兩個檔案如下:

// utils/api/fetch.js 封裝請求方法、請求攔截器

const app = getApp()

const BaseUrl = 'http://172.0.0.1:7300/mock'

const TokenWhiteList = [
    '/app/user/get-by-code'     // 不需要鑑權的api手動新增到這裡
]

/**
 * 設定請求攔截器
 * @param params 請求引數
 */
const fetch = (params = {}) => {
    // 攔截器邏輯
    if (!TokenWhiteList.includes(params.url)) {
        params.header = {
            'content-type': 'application/json',             // 預設值
            'token': app.globalData.token || ''
        }
    }

    if (params.url.startsWith('/')) {    // 拼接完整URL
        params.url = BaseUrl + params.url
    }

    // 返回promise
    return wx.pro.request({ ...params })
      .then(({ data: { code, message, data } }) => {
          // ... 各種異常情況的邏輯處理
          // 與後端約定 code 20000 時正常返回
          if (code === 20000) return Promise.resolve(data)
          return Promise.reject(message)
      })
}

export { fetch }

然後再將所有 API 封裝到單獨的檔案中集中管理:

// utils/api/apis.js 封裝所有請求 API

import { fetch } from './fetch'

/* 根據微信code獲取使用者資訊 */
const appUserGetByCode = ({ code } = {}) => fetch({
    url: '/app/user/get-by-code',
    data: { code }
})

/* 掃碼登入 */
const appUserQrLogin = ({ qrCode } = {}) => fetch({
    method: 'POST',
    url: '/app/user/qr-login',
    data: { qrCode }
})

/* 個人資訊 */
const appUserInfo = () => fetch({
    url: '/app/user/info'
})

/* 系統引數獲取,資料字典 */
const appSysParamListByParam = () => fetch({
    url: '/app/sys-param/list-by-param'
})

/* 資料字典所有 */
const appSysParamListAll = () => fetch({
    url: '/app/sys-param/list-all'
})

export {
    appSysParamListAll,   // 資料字典所有
    appSysParamListByParam,   // 系統引數獲取,資料字典
    appUserGetByCode,   // 根據微信code獲取使用者資訊
    appUserQrLogin,   // 掃碼登入
    appUserInfo   // 個人資訊
}

在要使用 API 的地方就可以這樣引入:

import * as Api from '../../utils/api/apis.js'   // 相對路徑

// 使用方式
Api.appSysParamListAll()
  .then(({ dataList }) => this.upData({ sysParamList: dataList }))
  .then(() => {
      const keyList = this.data.sysParamList.map(T => T.key)
      this.upData({
          keyList,
          formData: { keys: keyList }
      })
  })

使用方式就很舒服,這裡使用到了 upData,就是下面我要介紹的內容,是在下非常推介的小程式工具~ ?

3. setState 修改 data 中想修改物件的屬性

在小程式中,data 是不能直接操作的,需要使用 setData 函式。鑑於微信小程式開發時 setData 的使用體驗十分蹩腳,我使用了個庫函式 wx-updata,這個庫函式在開發的時候對我很有幫助,這裡特意推介給大家。

3.1 為什麼要使用 wx-updata

你在使用 setData 的時候,是不是有時候覺得很難受,舉個簡單的例子:

// 你的 data
data: {
    name: '蠟筆小新',
    info: { height: 140, color: '黃色' }
}

如果要修改 info.height 為 155,使用 setData 要怎麼做呢:

// 這樣會把 info 裡其他屬性整不見了
this.setData({ info: { height: 155 } })

// 你需要取出 info 物件,修改後整個 setData
const { info } = this.data
info.height = 155
this.setData({ info })

似乎並不太複雜,但如果 data 是個很大的物件,要把比較深且不同的物件、陣列項挨個改變:

data: {
    name: '蠟筆小新',
    info: {
        height: 140, color: '黃色',
        desc: [{ age: 8 }, '最喜歡大象之歌', '靚仔', { dog: '小白', color: '白色' }]
    }
}

比如某個需求,需要把 info.height 改為 155,同時改變 info.desc 陣列的第 0 項的 age 為 12,第 3 項的 color 為灰色呢?

// 先取出要改變的物件,改變數字後 setData 回去
const { info } = this.data
info.height = 155
info.desc[0].age = 12
info.desc[3].color = '灰色'
this.setData({ info })

// 或者像某些文章裡介紹的,這樣可讀性差,也不太實用
this.setData({
    'info.height': 155,
    'info.desc[0].age': 12,
    'info.desc[3].color': '灰色'
})

上面這兩種方法,是我們平常小程式裡經常用的,和其他 Web 端的框架相比,就很蹩腳,一種濃濃的半成品感撲面而來,有沒有這樣一個方法:

this.upData({
    info: {
        height: 155,
        desc: [{ age: 12 }, , , { color: '灰色' }]
    }
})

這個方法會幫我們深度改變巢狀物件裡對應的屬性值,跳過陣列項裡不想改變的,只設定我們提供了的屬性值、陣列項,豈不是省略了一大堆蹩腳的程式碼,而且可讀性也極佳呢。

這就是為什麼我在上線的專案中使用 wx-updata,而不是 setData

wx-updata 的原理其實很簡單,舉個例子:

this.upData({
    info: {
        height: 155,
        desc: [{ age: 12 }]
    }
})

// 會被自動轉化為下面這種格式,
// this.setData({
//    'info.height': 155,
//    'info.desc[0].age': 12,
// })

原來這個轉化工作是要我們自己手動來做,現在 wx-updata 幫我們做了,豈不美哉!

3.2 wx-updata 使用方式

在一般情況下,我們可以將方法直接掛載到 Page 建構函式上,這樣就可以在 Page 例項中像使用 setData 一樣使用 upData 了:

// app.js 中掛載
import { updataInit } from './miniprogram_npm/wx-updata/index'  // 你的庫檔案路徑

App({
    onLaunch() {
        Page = updataInit(Page, { debug: true })
    }
})

// 頁面程式碼中使用方式
this.upData({
    info: { height: 155 },
    desc: [{ age: 13 }, '帥哥'],
    family: [, , [, , , { color: '灰色' }]]
})

有的框架可能在 Page 物件上進行了進一步修改,直接替換 Page 的方式可能就不太好了,wx-updata 同樣暴露了工具方法,使用者可以在頁面程式碼中直接使用工具方法進行處理:

// 頁面程式碼中
import { objToPath } from './miniprogram_npm/wx-updata/index'  // 你的庫檔案路徑

Page({
    data: { a: { b: 2}, c: [3,4,5]},

    // 自己封裝一下
    upData(data) {
        return this.setData(objToPath(data))
    },

    // 你的方法中或生命週期函式
    yourMethod() {
        this.upData({ a: { b: 7}, c: [8,,9]})
    }
})

針對修改陣列指定項的時候,可能存在的跳過陣列空位的情況,wx-updata 提供了 Empty 的 Symbol 型別替位符,還有陣列的物件路徑方式,感興趣可以看看 wx-updata 的文件,也可以參考 <開發微信小程式,我為什麼放棄 setData,使用 upData> 這篇介紹文章。

另外,使用了 wx-updata 也還可以使用原來的 setData,特別是有時候要清空陣列時,靈活使用,可以獲得更好的小程式開發體驗,祝大家小程式開發愉快 ?

4. 使用 scss 寫樣式

4.1 Webstorm 配置方法

關於蹩腳的 .wxss 樣式,我使用 webstorm 的 file watcher 工具把 scss 檔案監聽改動並實時編譯成 .wxss 檔案,感覺比較好用,這裡給大家分享一下我的配置:

然後記得在 .gitignore 檔案中加入要忽略的樣式:

*.scss
*.wxss.map

這樣在上傳到 git 的時候,就不會上傳 scss 檔案了~ 當然如果你的團隊成員需要 scss 的話,還是建議 git 上傳的時候也加上 scss 檔案。

這樣設定之後,一個元件在本地的會是下面這樣

本地檔案

其中我們需要關注的就是 .js.json.scss.wxml 檔案,另外的檔案 .wxss 會在你改動 .scss 檔案之後自動生成並更新,而 .wxss.map 是外掛自動生成的對映關係,不用管。

如果不是使用 webstorm,可以直接執行命令 sass --watch index.scss:index.wxss -s expanded,命令列如果關閉,sass 命令就不會監聽檔案的變動然後編譯,所以最好用編輯器的外掛。

同理,也可以使用 less、stylus 等預編譯語言。

4.2 Visual Studio Code 配置方法

萬能的 VSC 當然也可以做到這個功能,搜尋並下載外掛 easy sass,然後在 setting.json 中修改/增加配置:

"easysass.formats": [
  {
    "format": "expanded",
    "extension": ".wxss"
  },
  {
    "format": "compressed",
    "extension": ".min.wxss"
  }
]

上面 expanded 是編譯生成的 .wxss 檔案,下面 compressed 是壓縮之後的 .wxss 樣式檔案,下面這個用不到可以把下面這個配置去掉,然後在 .gitignore 檔案中加入要忽略的中間樣式:

*.scss

當然也可以不新增,如果你的同事也是實用 scss 來開發小程式的話,其他跟上面一樣,至此你就可以在小程式開發中快樂使用 scss 了~

5. 使用 iconfont 圖示字型

在 Web 開發中 iconfont 可謂是最常用的靈活圖示字型工具了,這裡介紹一下如何在微信小程式中引入 iconfont 圖示。

首先找到你想使用的圖示們,點選購物車之後下載到本地。

下載icon

下載到本地是一個壓縮包,解壓縮之後將 iconfont.css 檔案複製到微信小程式的 styles 資料夾中 (在下的習慣,也可以放到你想放的地方比如 fonts),將字尾改為 .wxss

放到本地

app.wxss 中引入樣式:

@import "styles/iconfont.wxss";

然後在 .wxml 中就可以使用剛剛你新增的圖示了,Web 使用 i 標籤,小程式中使用 text 標籤:

<text class="iconfont icon-my-edit" style="color: blue"></text>

如果後面要加新的圖示,要下載新的 iconfont.css 的檔案到本地重新命名並覆蓋,重新走一遍這個流程。

當然,如果你使用的樣式庫提供的一些 icon 能滿足你的要求,那更好,就不用引入外部圖示字型檔案了,不過大部分情況下是不滿足的 ?


網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出,如果本文幫助到了你,別忘了點贊支援一下哦(收藏不點贊,都是耍流氓 ?)~

參考文件:

  1. youngjuning/wx-promise-pro: ✨強大、優雅的微信小程式非同步庫?
  2. 小程式開發坑之-IOS時間顯示為NaN - 漠小飛
  3. 【微信小程式】效能優化
  4. 微信小程式使用Promise - 簡書
  5. 開發微信小程式,我為什麼放棄 setData,使用 upData

PS:本人部落格地址 Github - SHERlocked93/blog,也歡迎大家關注我的公眾號【前端下午茶】,一起加油吧~

另外可以加入「前端下午茶交流群」微信群,長按識別下面二維碼即可加我好友,備註加群,我拉你入群~

相關文章