前端進階 -- 微信小程式基礎

Bill發表於2023-01-12

什麼是小程式

小程式頁面本質上是網頁,使用了hybrid技術進行原生與web的混合開發,hybrid技術使用webview做渲染,透過js bridge作為web與native的資料互動橋樑
  • 使用技術棧與網頁開發是一致的,都用到HTML、CSS、JS
    區別:不支援瀏覽器API,比如window、document,只能使用微信提供的API

微信小程式的發展歷程

  • 微信小程式形態

    • 小程式更像是公眾號開發的升級
    • 早期微信透過sdk方式增強了開發者開發公眾號網頁的能力,小程式的誕生是微信邁向平臺化超級app的業務行為,並且幫助使用者更好的實現了「輕量級APP」
  • 開發標準

    • 最初微信小程式自己定義了一套“標準”,最開始的框架沒有元件、npm、和web生態嚴重脫節
    • 由於特殊的雙執行緒模型與四不像語法,小程式開放只是對三方業務的開放
  • 商家湧入

    • 小程式業務的開放性 -> 平臺型app
    • 比如:支付寶小程式、百度、淘寶、360、快應用
    • 小程式設計的目的:大多數選擇了和微信類似的架構,框架,更多不是從技術角度考慮,而是想盡可能蹭微信小程式的福利,讓開發者更快的投放到自己的平臺

    微信小程式框架

    目錄結構每個頁面資料夾都包含了wxml、wxss、js、json幾個字尾的檔案,分別是模板檔案,樣式檔案,js指令碼檔案,json頁面配置檔案。在目錄最外層還有全域性的app.js、app.json、app.wxss

小程式型別應用的技術選型

渲染頁面的技術方案

  1. 用純客戶端原生技術渲染
  2. 用純web技術渲染
  3. 用客戶端原生技術與web技術結合的混合開發(hybrid)渲染

    方案對比

  4. 開發門檻:web門檻低,原生也有像RN這樣的框架支援
  5. 體驗:原生體驗比web好太多,hybrid在一定程度上比web接近原生體驗
  6. 版本更新:web支援線上更新,如果小程式用原生開發,則需要打包到微信一起稽核
  7. 管控與安全:web可跳轉或是改變頁面內容,存在一些不可控因素和安全風險

    方案確定

  8. 小程式的宿主環境是微信等手機app,用純客戶端原生技術來編寫小程式,那麼小程式程式碼每次都需要與手機app程式碼一起發版。所以該方案不可取
  9. web支援有一份副本資源包放在雲端,透過下載到本地,動態執行後即可渲染出介面,但純web技術在一些複雜的互動上可能有效能問題。所以該方案不可取

    • 在web技術中,UI渲染跟指令碼執行都在一個單執行緒中執行,這就容易導致一些邏輯任務搶佔UI渲染的資源
  10. 兩者結合的hybrid技術來渲染小程式,用一種近似web的方式來開發,並且可以實現線上更新程式碼

    • 擴充套件web的能力,比如輸入框元件(input、textarea)有更好的控制鍵盤的能力
    • 體驗更好,同時減輕webview的渲染工作
    • 用客戶端原生渲染內建一些複雜元件可以提供更好的效能

雙執行緒模型

小程式的渲染層和邏輯層分為兩個執行緒管理

  • 檢視層(Web View) -> WebView進行渲染

    • 介面渲染相關的任務都在WebView執行緒裡執行,透過邏輯層程式碼區控制渲染哪些介面。一個小程式存在多個介面,所以檢視層存在多個WebView執行緒
  • 邏輯層(App Service) -> JsCore執行緒執行js指令碼

    • 建立一個單獨的執行緒去執行js程式碼,在這裡執行的都是有關小程式業務邏輯的程式碼,負責邏輯處理、資料請求、介面呼叫等
  • JSBridge:上層開發與Native(系統層)的橋樑,使小程式可透過API使用原生的功能,且部分元件為原生元件實線,從而有良好體驗
    設計目的:為了管控和安全等問題,阻止開發者使用一些,例如瀏覽器的window物件,跳轉頁面,操作DOM、動態執行指令碼的開放性介面
使用沙箱環境提供純javascript的解釋執行環境
  1. 客戶端系統,在pc執行時用javascript引擎執行js程式碼
  2. ios:在ios系統時使用ios提供的Js core框架執行js程式碼
  3. 安卓:在安卓系統時使用騰訊的x5核心提供的JsCore執行js程式碼

資料驅動檢視變化

問:js邏輯程式碼放到單獨的執行緒中執行,在WebView執行緒無法直接操作DOM,開發者如何實現動態更改介面

DOM的更新透過簡單的資料通訊來實現,邏輯層和檢視層的通訊由native(微信客戶端)做中轉,邏輯層傳送網路請求也經由native轉發。

graph LR
物件模擬DOM樹 --> 比較兩顆虛擬DOM樹的差異 --> 更新有差異的真實DOM樹
  • 在渲染層把wxml轉化為對應的js物件
  • 在邏輯層發生資料變化的時候,透過宿主環境提供的setData方法把資料從邏輯層傳遞到native,再轉發到渲染層
  • 經過對比,更新真實dom內容
    總結:這部分和mvvm框架類似,轉化了ast樹,透過native建立了虛擬dom,透過setData的方法更新資料,再透過native做了diff差異化更新真實dom樹

事件處理

微信做了特殊的處理,將所有的事件攔截後丟到邏輯層交給js處理
  • 程式碼描述互動事件
  • 檢視層使用者互動觸發事件
  • native攔截丟給邏輯層
  • 邏輯層收到回撥處理事件

事件的派發處理包括事件捕獲和冒泡兩種:
互動流程就如同上述的資料驅動檢視變化一樣,native攔截丟給邏輯層後,透過js響應事件,透過setData修改資料,虛擬dom發生變化,native diff更新真實dom

執行機制

  • 冷啟動:使用者首次開啟或者小程式被微信主動銷燬後再次開啟的情況(當手機系統五秒內兩次以上記憶體告警,小程式會主動銷燬),此時小程式需要重新載入啟動
  • 熱啟動:加入使用者已經開啟過某小程式,在一定時間內(目前是五分鐘)再次開啟該小程式(小程式前後臺切換),此時無需重新啟動
    注意
  • 小程式沒有重啟的概念

小程式框架對比

小程式原生語法

  • 目前小程式已經能夠做到前端工程化,並且植入前端生態中已有的一些理念,比如狀態管理,CLI工程化等等
  • 前端能力基本在小程式上可以複用,比如狀態管理,柯里化管理元件狀態,ts等

    增強型框架

    指小程式引入npm後,有了更開放的能力所帶來的收益

以微信小程式為例,騰訊開源了omi框架

  • 整體保留了小程式現有語法,在此之上對它進行了擴充和增強
  • 比如引入了computed hook,比如能直接透過this.store.data.logs[0] = 'changed'修改狀態,徹底將微信小程式vue化

轉換型框架

能夠一碼多端的框架,比如rax、taro、uniapp,讓開發者幾乎不用感受小程式原生語法,更大程度對接前端已有生態,只是最後構建產物為小程式程式碼
這些框架實際都是透過轉換產物1:1轉換成對應的小程式程式碼

graph LR
開發者程式碼 --> AST樹 --> 新AST樹 --> 小程式程式碼
編譯時

優勢

  • 執行時效能損耗低
  • 目的碼明確,開發者所寫即所得
  • 執行時、編譯時最佳化:比如框架會給予開發者更多的語法支援以及預設的效能最佳化處理,比如避免多次setData,或者長連結串列最佳化等等
    劣勢
  • 語法限制高:需要完全命中開發者在模板部分所用到的所有語法,語法受限,由於是1:1編譯轉換,開發者在開發的時候還是不得不去遵循小程式的開發規範,比如一個檔案只能定義一個元件之類的

    執行時

    比起編譯時,最大優勢是可以幾乎沒有任何語法約束的去完成程式碼編寫
    優勢:沒有語法限制

微信小程式

文件必讀

推薦將文件的指南、框架、元件、API、工具相關的通讀一遍

基本內容

示例程式碼github地址:https://github.com/wechat-miniprogram/miniprogram-demo

基礎

官方文件:https://developers.weixin.qq.com/miniprogram/dev/framework/

基礎核心

graph
A[小程式冷啟動] -- 啟動 --> B[前臺] -- 切後臺 --> C[後臺] -- 五秒後 --> D[掛起] --  30分鐘後 --> E[小程式銷燬]
C -- 切前臺 --> B
D -- 切前臺 --> B
  • 更新機制

    • 啟動時同步更新

      • 定期檢查小程式版本
      • 長時間未使用小程式
    • 啟動時非同步更新

      • 開啟發現有新版本,非同步下載,下次冷啟動時載入新版本
    • 開發者手動更新

  • 程式碼注入

    • 按需注入:“lazyCodeLoading”:“requiredComponents”配置路徑進行打包
    • 用時注入:在開啟「按需注入」特性的前提下,指定一部分自定義元件不在小程式啟動時注入,而是在真正渲染的時候才進行注入,使用佔位元件在需要渲染但注入完成前展示
  • 分包載入

    • 原則

      • 宣告subpackages後,將俺subpackages配置路徑進行打包,subpackages配置路徑外的目錄將被打爆到app(主包)中
      • app(主包)也可以有自己的pages(即最外層的pages欄位)
      • subpackage的根目錄不能是另外一個subpackage內的子目錄
      • tabBar頁面必須在app(主包)內
    • 獨立分包

      • 當小程式從普通的分包頁面啟動時,需要受限下載主包
      • 獨立分包執行時,App並不一定被註冊,因此getApp()也不一定可以獲得App物件。基礎庫2.2.4版本開始getApp支援[allowDefault]引數,在App未定義時返回一個預設實現,當主包載入,App被註冊時,預設實現中定義的屬性會被覆蓋合併到真正的App中
  • 除錯小程式

    • 使用vconsole
    • 開啟sourceMap配置項
    • 實時日誌:重寫log,使用wx.getRealtimeLogManager封裝,在運營後臺“開發->開發管理->運維中心->實時日誌"檢視
    • errno:開發者自己丟擲異常,針對API的cb err進行狀態碼的判斷,針對業務場景語義化展示

    如何相容版本

    版本號比較
    const version = wx.getSystemInfoSync().SKDVersion
    if (compareVersion(version, '1.1.0') >= 0) {
    wx.openBluetoothAdapter()
    } else {
    wx.showModal({
      title: '提示',
      content: '當前微信版本過低,無法使用該功能,請升級到最新微信版本後重試'
    })
    }
    判斷API是否存在
    if (wx.openBluetoothAdapter) {
    wx.openBluetoothAdater()
    } else {
    wx.showModal({
      title: '提示',
      content: '當前微信版本過低,無法使用該功能,請升級到最新微信版本後重試'
    })
    }
wx.canIUse

判斷當前環境是否能使用api,和jsdk的api許可權判斷類似

wx.showModal({
success: function(res) {
  if (wx.canIUse('showModal.success.cancel')) {
    console.log(res.cancel)
  }
}
})
最低基礎庫版本

運營後臺設定最低基礎庫版本

框架

微信原生小程式框架官方文件

  • 小程式配置

    • 全域性配置
    • 頁面配置
    • sitemap配置
  • 框架介面

    • App:必須在app.js中呼叫且只能呼叫一次

      • 一些全域性的生命週期
    • getApp:外部訪問App中資料的方式
  • 頁面

    • Page

      • data
      • 一些頁面中的生命週期
    • getCurrentPages:獲取當前頁面棧,可以獲取當前頁面資訊,比如路由,也可以透過該方法實現跨頁面賦值
    // 跨頁面賦值
    let pages = getCurrentPages() // 當前頁面棧
    let prePage = pages[pages.length - 2] // 上一頁面
    prevPage.setData({
    // 直接給上一頁面複製
    })
    // 頁面跳轉後自動重新整理
    wx.switchTab({
     url: '../index/index',
     success: function (e) {
       const page = getCurrentPages().pop(); // 當前頁面
       if (page == undefined || page == null) return;
       page.onLoad(); //或者其他操作
    } })
  • Router

    • 頁面路由器有 switchTab reLaunch redirectTo navigateTo navigateBack 五個方法
    • 與 wx 物件向同名的五個方法 switchTab reLaunch redirectTo navigateTo navigateBack 功能相同;唯一的區別是,頁面路由器中的方法呼叫時,相對路徑永遠相對於 this 指代的頁面或自定義元件。
    Page({
    wxNavAction: function () {
      wx.navigateTo({
        url: './new-page'
      })
    },
    routerNavAction: function () {
      this.pageRouter.navigateTo({
        url: './new-page'
      })
    }
    })

    自定義元件

  • Component:在微信開發工具右鍵新建元件自動建立了自定義元件模板,整體包裹在一個Component物件下
  • Behavior:behaviors是用於元件間程式碼共享的特性, 類似一些程式語言中的'mixin'或者'traits',它可以將behavior模組的程式碼混入元件程式碼中,一般是抽離出邏輯程式碼和部分屬性
// 官方示例
module.exports = Behavior({
  behaviors: [],
  properties: {
    myBehaviorProperty: {
      type: String
    }
  },
  data: {
    myBehaviorData: {}
  },
  attached: function(){},
  methods: {
    myBehaviorMethod: function(){}
  }
})

基礎用法,先新建一個元件,再透過元件自帶的behaviors屬性混入behavior模組,這個屬性類似vue的mixins

// 新建個元件
<view>屬性: {{myBehaviorProperty}} --- {{myCompProperty}}</view>
<view>資料: {{myBehaviorData}} --- {{myCompData}}</view>
<view bind:tap="myBehaviorMethod">觸發behavior的自定義方法</view>
<view bind:tap="myCompMethod">觸發元件的自定義方法</view>

// 元件級js
import testBehavior from './testBehavior'
Component({
  behaviors: [testBehavior],
  properties: {
    myCompProperty: {
      type: String,
      value: ''
    }
  },
  data: {
    myCompData: 'myCompData'
  },
  created: function (){
    console.log('[my-component]- created')
  },
  attached: function (){
    console.log('[my-component]- attached')
  },
  ready: function (){
    console.log('[my-component]- ready')
  },
  methods: {
    myCompMethod: function () {
      console.log('[my-component]- method')
    }
  }
})
// behavior.js
export default Behavior({
  behaviors: [],
  properties: {
    myBehaviorProperty: {
      type: String,
      value:  'myBehaviorProperty'
    }
  },
  data: {
    myBehaviorData: 'myBehaviorData'
  },
  created: function () {
    console.log('[my-behavior]- created')
  },
  attached: function () {
    console.log('[my-behavior]- attached')
  },
  ready: function () {
    console.log('[my-behavior]- ready')
  },
  methods: {
    myBehaviorMethod: function () {
      console.log('[my-behavior]- method')
    }
  }
})

面試會問到的

為什麼要分包

目前小程式主包有限制大小不能超過2M,整體(包括分包)不能超過20M,對小程式分包可以最佳化首次啟動的下載時間,以及在多團隊共同開發時可以更好的解耦

頁面通訊 -- 元件高階用法

  • 跨頁面賦值

    • 類似vue中的ref,直接操控元件的屬性值
    let pages = getCurrentPages() // 當前頁面棧
    let prePage = pages[pages.length - 2] // 上一頁面
    prevPage.setData({
    // 直接給上一頁面複製
    })
  • 使用behavior進行資料共享
  • 使用wx.nativgateTo開啟,這兩個頁面將建立一條資料通道,類似父子元件,可以透過emiton互相傳送

    • 被開啟的頁面可以透過this.getOpenerEventChannel()方法來獲取一個EventChannel物件
    • wx.navigateTosuccess回撥中也包含一個EventChannel物件
    • 這兩個EventChannel物件間可以使用emiton方法互相傳送、監聽事件

    seo最佳化

    小程式如何seo最佳化官方文件

  • 配置小程式sitemap
  • 小程式裡跳轉的頁面(url)可被直接開啟
  • 頁面跳轉優先採用navigate元件
  • 必要的時候才請求使用者進行授權、登入、繫結手機號登

    • 開發中遇到過如果要透過企微群發公眾號頁面時,企微爬取不到頁面資訊,因為頁面使用了靜默授權,爬取頁面的時候直接跳轉到授權頁了,所以獲取不到頁面資訊
  • 設定一個清晰的標題和頁面縮圖

    效能相關

  • 小程式啟動流程官方文件
  • 小程式切換頁面流程官方文件
  • 如何提升小程式效能

    如何進行埋點

  • 手動設定自己的埋點系統(需要埋點量少的時候)
  • 如果是vue、react等框架則可以在webpack中載入自己寫的babel外掛,在轉化ast這步自動插入埋點
  • 小程式中可以用騰訊官方提供的埋點工具,也可以透過開發者工具自定義分析設定埋點上報

相關文章