小程序使用vant-dialog组件内容出不来_微信官方小程序同构新方案Kbone全解析
導語 |?
本文是Kbone作者june在云加社區微信群中的分享整理總結而成(編輯:尾尾)。同時,june將出席11月16日的TWeb騰訊前端技術大會,歡迎現場交流。
大家好,我是來自騰訊微信小程序團隊的前端開發工程師:june。小程序作為一種新興地鏈接用戶與服務的方式,相信大家都或多或少接觸過。對于開發者來說,它是一種類似 Web 但又不同于 Web 的開發模式,它提供了一套自定義的 API 和文件組織方式,這無疑帶給開發者一定的學習成本和維護成本,所以我們也在嘗試能否提供一個方案來抹平這個差異。
接下來就進入我今天要分享的話題:Kbone——微信小程序同構方案新思路。本次分享包括四個部分:背景、方案、應用和結語。
一、Kbone誕生背景
首先我們先進入背景部分的介紹。之所以會有 Kbone 這個方案出現,源自于一個需求:微信開放社區當時只有 Web 端,為了讓信息可以更方便地傳播、分享和使用,希望實現社區小程序版,交互體驗盡量貼近于 Web 端。
注微信開放社區鏈接:
https://developers.weixin.qq.com/community/develop/mixflow
此次同構到小程序端需要考慮幾個因素:多端代碼復用、盡可能支持已有的特性和性能要有保證。其實最主要的就是要在盡量不改動現有代碼的情況下來完成小程序的開發。
二、具體方案實現
接下來就來探討下具體方案的實現。
社區 Web 端是基于 Vue 實現的,使用了 Vue-router、Vuex 等插件。Vue 想必大家挺熟悉的了,它是市面上一款非常流行的 Web 框架,提供組件化等特性,其原理大致如下:
? ? ? ? ??Vue 模板可以認為是一種附加了一些特殊語法的 HTML 片段,一般來說一份 Vue 模板對應一個組件,在構建階段編譯成調用 Dom 接口的 JS 函數,調用此 JS 函數就會創建出組件對應的 Dom 樹片段進而渲染到瀏覽器上。小程序里是支持運行 JS 的,但是這里用到的 Dom 接口和渲染到瀏覽器上的功能小程序不具備,所以無法直接將 Web 端社區代碼移植到小程序中。原因就在于小程序為了安全和性能而采用了雙線程的架構,運行用戶 JS 代碼的邏輯層是一個純粹的 JSCore,沒有任何瀏覽器相關的實現,這里得想辦法將 Web 端代碼轉成小程序代碼。
業界常見做法:將 Vue 模板直接轉成小程序的 WXML 模板
那么問題來了,如何將 Vue 代碼轉成小程序代碼?這里先看下業界常見的做法:將 Vue 模板直接轉成小程序的 WXML 模板。
使用做法相當于拋棄了瀏覽器中建 Dom 樹的過程,而是直接交由小程序來對模板進行編譯創建出小程序的模板樹,進而渲染到小程序頁面中。
一般來說這個做法對于普通場景是夠用的,但是對于一些更復雜的場景就很不好處理了,比如社區中的一個簡單例子:社區帖子詳情展示富文本內容,點擊內容中的圖片可預覽。
這主要是因為 Vue 模板和 WXML 模板的語法并不是直接對等的,Vue 的特性設計也和小程序的設計無法劃等號,這自然就導致了部分 Vue 特性的丟失。比如像 Vue 中的 v-html 指令、ref 獲取 Dom 節點、過濾器等就通通用不了。當然不止是 Vue 自身的特性,一些原本依賴 Dom/Bom 接口的 Vue 插件也無法使用,比如 Vue-router 等,而這些正是社區高度依賴的,在不對社區代碼做大范圍改造的話是無法使用此方案的。
此路不通,那還有其他的方法么?
換個思路:做一個適配層
答案是有的,這里我們就得換一種思路來解決這個問題。回到最初的點上,我們無法將 Web 端代碼移植到小程序中是因為小程序沒有 Dom 接口,那么我們想辦法做出一個適配層,將這個差異給抹掉不就行了么?
有了想法就要實施,仿造出 Dom 接口并不難,事實上在 Nodejs 端就有人做過類似的事,比如 jsDom 這個庫的實現,讓我們可以在沒有真實瀏覽器環境下可以對一些依賴 Dom 接口的 Web 端代碼進行測試。
仿造了 Dom 接口給 Vue 調用,進而創建出了仿造 Dom 樹。根據前面提到的小程序架構,用戶的 JS 代碼是執行在邏輯層的,也就是說我們創建出的 Dom 樹也是存在與邏輯層的內存之中,接下來要解決的難題是如何將這棵 Dom 樹渲染到小程序頁面中。
這里需要先簡單介紹一下小程序的渲染原理:小程序的雙線程架構,邏輯層會執行用戶的 JS 代碼進而產生一組數據,這組數據會發往視圖層;視圖層接收到數據后,結合用戶的 WXML 模板創建出組件樹,之后小程序再將組件樹渲染出來。這里的組件樹和 Dom 樹很類似,只是它是由官方內置組件或自定義組件拼接而成而不是 Dom 節點。這里我們能不能將仿造出來的 Dom 樹映射到小程序的組件樹上?
小程序組件樹是根據 WXML 模板創建出來的,而仿造 Dom 樹結構是不穩定的,我們無法提前預知它會生成什么樣的結構,也就無法提前準備后可以描述任意 Dom 樹的 WXML 模板,除非直接將 Vue 模板轉換成 WXML 模板,但這樣又繞回前面的問題上了。
小程序組件樹中的組件有兩種:內置組件和自定義組件,內置組件是由官方提供的如 video、map 這樣的組件,而自定義組件是一種支持由用戶利用現有組件自行組裝的組件,能否利用它來做些什么?
使用 Web 端概念來做個簡單解釋,內置組件就像是 div、span 這些 HTML 標簽,而自定義組件就像是 Web 中的 Vue 組件。Vue 組件可以將 HTML 標簽以及其他的 Vue 組件進行組裝,自定義組件同理,主要用于功能模塊的抽象、封裝和復用。不過自定義組件有個很奇妙的特性,它支持自引用,也就是說它可以自己引用自己來進行組裝。
自定義組件可以自己引用自己,那么我們就可以利用這個特性來進行遞歸創建組件,進而創建出一棵組件樹:
比如上圖的例子,我們封裝了一個 custom-dom 組件,這個組件里面也使用了 custom-dom 組件用于渲染子組件。那么只要我們執行一下 setData,把 children 數據傳遞過去就可以創建出子組件,子組件本身也是 custom-dom 組件,它同樣可以執行這個邏輯把各自的子組件創建出來,這樣就實現了組件的遞歸創建,只要我們擁有完整的 Dom 樹結構,就可以創建出相對應的一棵組件樹。
這里遞歸的終止條件是遇到特定節點、文本節點或者孩子節點為空。然后在創建出組件樹后,將 Dom 節點和自定義組件實例進行綁定以便后續的 Dom 更新和操作即可。
如何監聽用戶操作?
接下來,如果用戶在界面上進行了操作,觸發了一些事件的話,那么代碼中要如何監聽這些事件呢?小程序本身有自己的事件系統,它和 Web 端事件系統類似,但是出于以下幾個原因導致我們無法直接使用小程序的事件系統:
小程序支持的事件表現和 Web 端不一致,比如 input 事件在小程序中不可冒泡。
小程序的捕獲冒泡是在 Webview 端,因此邏輯層在整個捕獲冒泡流程中各個節點接收到的事件不是同一個對象。
小程序事件對象和 Web 端事件對象結構不一樣。
小程序事件的捕獲冒泡以及阻止冒泡等操作必須在 WXML 模板中聲明,無法使用接口實現。
小程序本身是基于 Web Component 特性來實現的組件體系,其事件來源只能判定來自于當前 shadow tree 下的哪個節點,而不能跨 shadow tree 判斷。
綜上所述,最好的解決方法就是把事件系統也仿造一份,在仿造 Dom 樹上進行捕獲冒泡。當自定義組件監聽到用戶的操作后,就將事件發往仿造 Dom 樹,后續自定義組件監聽到的同一個事件的冒泡就直接忽略。而 Dom 樹接收到事件后,再進行捕獲和冒泡,讓事件在各個節點觸發,這樣的話整套體系都可以按照 Web 端的方式進行實現,對于用戶來說,只管按照 Web 端的用法來進行事件監聽即可。
重要細節一:如何將 Dom 樹傳遞給視圖層?
整套方案的大致思路便是如此,接下來介紹幾個實現過程中比較重要的細節,其一:如何將 Dom 樹傳遞給視圖層?
這其實就是自定義組件要如何做 setData 的問題。我們一開始想到的方式是直接將整棵 Dom 樹傳遞給自定義組件,然后自定義組件在遞歸創建子組件時一步步透傳下去。這個做法的好處是一勞永逸,只有在最頂層的自定義組件需要管理 Dom 樹和 setData,其他自定義組件只管接收數據進行渲染即可,但是這樣也帶了問題:每次更新需要做大范圍的 diff,因為 setData 是從根組件發起的;當遇到一些局部更新時可能需要 setData 大量的數據,也就是會傳輸一些不必要的數據。
那么自然而然的,我們便想到讓每個自定義組件只 setData 當前節點的數據,每個自定義組件只考慮當前綁定的 Dom 節點,然后創建出子節點,這樣雖然會增加 setData 的數量,但是帶來的好處便是可以做到最小范圍 diff,同時每次 setData 的數據量也可以降到最小。
細節其二:自定義組件實例的創建其實是會有比較大開銷的,有沒有辦法減少一些自定義組件實例的創建?
按照先前的構想,一個自定義組件綁定一個 Dom 節點,所以自定義組件實例數量等于 Dom 節點數量。
其中一個思路是對 Dom 節點進行刪減,這個實現比較簡單,只要是不展示在頁面上的節點,直接從 Dom 樹上干掉就可以了,這樣自定義組件數量也會相應減少。
另一個思路是調整映射關系,讓一個自定義組件綁定多個 Dom 節點。我們可以對 Dom 樹按照一定規則進行裁剪,拆分成多棵子樹,然后每個自定義組件管理一棵子樹,這樣的話也可以減少大部分自定義組件的創建。
除此之外,我們可以考慮對葉子節點也進行一些處理。我們使用自定義組件來渲染的初衷就是為了可以動態遞歸創建出子節點,而當一個節點沒有子節點的情況下,我們就不需要使用自定義組件來渲染了,所以葉子節點可以合并到父級棵子樹中(如上圖的藍色節點合并到黃色節點所在的子樹中),直接使用 view 內置組件來渲染即可。
當然還有其他的一些細節,比如 Dom 對象復用、對象延遲創建等等,這里就不一一展開說明了,有興趣的朋友可以通過源碼來了解。
對于這個方案,性能也需要有一定的保證,我們隨機模擬了一些類似社區首頁的 Dom 樹,對其首次渲染耗時進行測算,其對比如下:
可以看到在 500 節點內的兩個方案本身性能差不多,不過因為自定義組件實例創建的開銷,在千節點往上的情況下會落后于靜態模板方案,因為 Kbone 本身是通過犧牲性能來換取更全面的 Web 端兼容,而通常一個小程序頁面的節點數在 100-500 這個區間浮動,因此這個表現是符合預期的。
以上就是?Kbone 這個適配器方案的大致設計思路,我們將其歸納為兩個模塊:仿造接口和自定義組件。正因為這個方案是通過提供適配器的方式來仿造出 Web 環境,所以用戶代碼不需要做任何魔改,大部分特性都可以繼續使用不需要被刪減,比如 vue-router、window.location 操作等。
三、具體應用效果
方案部分以及介紹完畢,接下來說說這個方案要如何應用到我們一開始的背景——微信開放社區上。
前面有簡單提到,原本 Web 端代碼是基于 Vue 來搭建的,其中還用到了諸多插件/庫,如 Vue-router、Vuex、Markdown-it 等,同時還支持了服務端渲染。但是不管 Web 端是怎么實現的,底層終究是調瀏覽器的那些接口,所以對于用戶層面的代碼我們不做任何調整,只是將瀏覽器那一層替換掉即可。
整個構建流程是基于 Webpack 來實現的,使用 Kbone 構建出小程序代碼也是基于 Webpack 來實現,只需要在原本 Web 端構建流程上實現一個 Webpack 插件,在構建原本 Web 端代碼到小程序端時追加 Kbone 和一些小程序相關的代碼即可。
在整套方案應用的過程中,肯定也會有些定制化的需求,比如希望小程序端頭部和 H5 端不同,不同端使用不同的交互設計:
我們可以構建的時候就注入環境變量,在小程序端將 process.env.isMiniprogram 設為 true,這樣用戶代碼層面可以通過判斷這個變量來判斷不同環境,進而執行不同的邏輯。
除此之外,還希望使用小程序的一些特性,比如小程序端支持使用小程序的分享,那么除了上述的環境變量外,還需要用到小程序的 button 內置組件來實現分享按鈕。在 Kbone 上可以使用一個特殊的標簽 wx-button 來表示 button 內置組件,在調 Kbone 的仿造 Dom 接口時會將其 wx- 前綴的標簽識別成內置組件,進而進行特殊處理。
整個社區小程序的功能完善之后,便要思忖一下代碼體積的問題,因為小程序本身有個 2M 限制。縮減代碼體積的方式大家應該都了解了很多了,如:壓縮混淆、代碼分割和公共代碼復用、tree shaking、使用分包等等。
還有就是考慮到小程序端是直接復用 Web 端代碼,但是并不是所有 Web 端代碼都需要在小程序端做到,那么在處理模塊依賴時可以做點手腳。因為都使用的 Webpack 構建,所以可以編寫一個 loader,在 import/require 的時候追加上,它可以根據前面注入的環境變量來判斷要不要將代碼進行打包。
這樣就可以很方便地指定哪些代碼不要構建到小程序端。
整體實現出來的效果如下,左邊是 H5 端,右邊是小程序端:
Web 端鏈接:
https://developers.weixin.qq.com/community/develop/mixflow
小程序碼:
四、總結
這一整套方案的實現和應用大致如此,其原理并不算復雜,只是用了另一種思路來實現。目前這一套方案即名為 Kbone,現已整理并開源到 GitHub 上:https://github.com/wechat-miniprogram/Kbone。
考慮到這個方案本身是通過最底層的適配方式來完成同構,那么除了 Vue 外,它其實也可以很輕松地移植到其他的 Web 框架上,比如 React、Preact、Omi 等,下面是一些基于這些框架的簡單 demo:
在上述 GitHub 倉庫內也可以找到這些框架的 demo,盡管各個 Web 框架的實現、語法都有所不同,但畢竟其本質上是相同的,最終都會轉化為 Dom 接口調用來渲染頁面。
也正因如此,可以看到?Kbone 這套方案最大的優勢:擴展性強、對各個特性的支持全面、對代碼編寫的要求少以及自由度高、不需要魔改 Web 框架的底層實現,這樣對于代碼的維護、升級也都更為簡單方便。
我的分享就到這里了,謝謝各位!
五、群內QA
Q:目前支持到vue那個版本?Vue3.0支持嗎?
A:目前主要的測試用例都是 vue 2.x 版本,大部分特性都能完整使用。vue 3.x 版本的支持在規劃中,因為還沒有完整的測試還不清楚直接上 vue 3.x 版本會有哪些坑,不過理論上只要底層仍舊是調用那些基礎的 dom 接口,那就是支持的。
Q:小程序的插件支持嗎?
A:插件目前暫不支持。
Q:請問wxs支持嗎?
A:wxs 目前暫不支持,使用 wxs 有很多情況下就是為了實現過濾器和一些簡單的純函數句柄,這些 vue 本身就已經支持了,就不是很有必要再使用 wxs 了,不然再反向兼容到 Web 端就會很困難。wxs 響應動畫? =》 wxs 響應事件來實現動畫
不過 wxs 響應動畫這塊是一個性能優化點,這個未來會考慮支持的。
Q:小程序原生對位置經緯度的獲取好像不太精準,有其他好的處理方案嗎?這個在我畢業設計的答辯中差點翻車。
A:增加了高精度定位的參數
Q:這塊的實現對小程序事件響應的性能有影響嗎?「 綜上所述,最好的解決方法就是把事件系統也仿造一份,在仿造 Dom 樹上進行捕獲冒泡。當自定義組件監聽到用戶的操作后,就將事件發往仿造 Dom 樹,后續自定義組件監聽到的同一個事件的冒泡就直接忽略。而 Dom 樹接收到事件后,再進行捕獲和冒泡,讓事件在各個節點觸發,這樣的話整套體系都可以按照 Web 端的方式進行實現,對于用戶來說,只管按照 Web 端的用法來進行事件監聽即可。」
A:和原生的小程序事件相比會有一點損耗但影響不大,小程序事件本身也不是直接使用 Web 端的事件冒泡機制,而是在視圖層的組件樹上自己實現的一套事件系統進行冒泡。kbone 的做法相當于把最初的那一個事件接過邏輯層來自己做一遍 Dom 樹上的冒泡,后續小程序自己的冒泡事件就忽略掉。簡單來說,相當于把冒泡這一套流程從視圖層拿到邏輯層來做。
Q:小程序開放接口或小程序獨有的API(例如:授權,文件操作等),應該如何處理?直接再vue中使用wx.***嗎?
A:是的,小程序環境的接口直接照常使用即可,比如 wx.xxx 等接口。但是如果要同構兼容到 Web 端的話,可能需要判斷一下環境,通常我們可以在構建時注入一個 process.env.isMiniprogram,這樣在 Vue 代碼里就可以通過判斷環境來做兼容處理。后續這邊也會嘗試提供一些兼容兩個環境的 API,比如現有的 wx.setStorage 等就可以直接使用 localStorage 來代替,kbone 底層會將 localStorage 的實現轉成 wx.setStorage 等 API。
Q:? kbone有開發交流群或者客服群嗎?
A:這個先前也有人提過,在近期會提供開發交流群來方便開發者們交流。
近期文章:
飛冰對于活動引擎的可借鑒之處
解析 Chameleon 小程序的運行時性能問題
總結
以上是生活随笔為你收集整理的小程序使用vant-dialog组件内容出不来_微信官方小程序同构新方案Kbone全解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 指定路径_Workbench中如何创建指
- 下一篇: cpu多核 node 单线程_node单