app vue 真机运行_uni-app黑魔法:小程序自定义组件运行到H5平台
引言
移動(dòng)互聯(lián)網(wǎng)的初期,囿于設(shè)備硬件性能限制,流量以原生App為主,iOS、Android是當(dāng)時(shí)兩大平臺(tái)。
隨著硬件及OS的更新?lián)Q代,H5可承載的體驗(yàn)逐步完善,為提高開發(fā)效率、節(jié)約資源(復(fù)用代碼)以及熱更新等目的,Hybrid模式成為主流;以及輕應(yīng)用、服務(wù)號(hào)等平臺(tái)的助推,H5網(wǎng)頁(yè)流量暴漲,成為第三大平臺(tái)。
2017年1月9日,微信發(fā)布小程序,歷經(jīng)3年發(fā)展,在今年主題為”未完成 Always Beta“的微信公開課 PRO上,微信團(tuán)隊(duì)披露,2019年小程序日活躍用戶超過 3 億,全年累計(jì)成交額達(dá)8000億,同比增長(zhǎng)超160%。
看到小程序如此驚人的增長(zhǎng)力,我們有理由相信,有中國(guó)特色的小程序互聯(lián)網(wǎng)時(shí)代已經(jīng)到來(lái),微信小程序也已成為繼iOS、Android、H5之后的第四大流量平臺(tái)。
平臺(tái)分裂,為不同平臺(tái)編寫相同的業(yè)務(wù)代碼,是件無(wú)趣的事情。
有追求的程序員,一直在探索代碼復(fù)用的方案,Hybrid App即是代表。
而在如今的小程序時(shí)代,對(duì)于同樣基于WEB技術(shù)的H5和小程序,如何實(shí)現(xiàn)代碼復(fù)用,是很多前端工程師探索的方向。業(yè)內(nèi)也已有不少成熟方案,從場(chǎng)景上來(lái)說(shuō),大致分為三類:
- 美團(tuán)的mpvue框架是早期探索解決這個(gè)問題的代表,但因小程序不支持dom操作,故mpvue適用于vue的無(wú)dom操作的H5代碼轉(zhuǎn)換;
- 最近微信官方推出的kbone,也是為了解決“把 Web 端的代碼挪到小程序環(huán)境內(nèi)執(zhí)行”;不過,kbone 相比 mpvue 前進(jìn)了一步(當(dāng)然也有了新的性能缺陷),因?yàn)?#xff1a;
3. 復(fù)用小程序代碼,轉(zhuǎn)換小程序代碼在web環(huán)境中運(yùn)行;適用于有小程序代碼沉淀,未開發(fā)H5或H5平臺(tái)完善度較低的開發(fā)者;這個(gè)方向業(yè)內(nèi)成熟的方案還比較少。
uni-app近期支持了小程序自定義組件運(yùn)行到H5平臺(tái),是對(duì)如上第三種場(chǎng)景的一種探索。
需求場(chǎng)景
鑒于小程序的低成本獲客特征,很多廠商選擇先開發(fā)小程序,驗(yàn)證業(yè)務(wù)模式后,再擴(kuò)展至H5、App等其它平臺(tái)。
開發(fā)者雖可借助轉(zhuǎn)換器將小程序代碼轉(zhuǎn)換為uni-app項(xiàng)目(或其它跨端框架項(xiàng)目),快速實(shí)現(xiàn)多平臺(tái)發(fā)行;但不少開發(fā)者是不敢輕易決策將跨端版本替換之前線上的小程序版本的,畢竟線上版本已穩(wěn)定運(yùn)行了一段時(shí)間。
常選的方案是:讓原生小程序版本和uni-app跨端版本并行一段時(shí)間,微信平臺(tái)繼續(xù)使用原生版本,其它平臺(tái)使用uni-app跨端版本;經(jīng)過一段時(shí)間驗(yàn)證uni-app版本穩(wěn)定后,再使用uni-app版替換掉原生小程序版本。
在這段并行的時(shí)間內(nèi),開發(fā)者需要同時(shí)維護(hù)微信原生、uni-app兩個(gè)版本,新增業(yè)務(wù)需編寫兩份邏輯相同的代碼,重復(fù)勞動(dòng),成本疊加,如何改善?
借助uni-app 支持將微信小程序組件運(yùn)行到H5平臺(tái)的特性,我們給出一種思路:
- 開發(fā)者在原生小程序項(xiàng)目中,將新增業(yè)務(wù)以自定義組件的方式開發(fā),優(yōu)先上線小程序;
- 拷貝小程序組件的wxml/wxss/js/json文件到uni-app 項(xiàng)目下,通過uni-app的編譯器及運(yùn)行時(shí),保證小程序自定義組件在H5平臺(tái)的正確運(yùn)行。
這個(gè)方案的好處是:
- 優(yōu)先小程序開發(fā),畢竟小程序早已上線,有存量用戶
- 復(fù)用小程序組件,新增業(yè)務(wù)僅需開發(fā)一套代碼即可,降低開發(fā)成本
不止自己開發(fā)的小程序組件,業(yè)內(nèi)開源的三方小程序組件,均可復(fù)制到uni-app項(xiàng)目項(xiàng)目中,運(yùn)行到H5平臺(tái)。
另外,部分公司的產(chǎn)品經(jīng)理,會(huì)要求不同平臺(tái)有不同的交互,但核心業(yè)務(wù)邏輯是相同的,開發(fā)者常會(huì)通過維護(hù)不同項(xiàng)目的方式來(lái)滿足產(chǎn)品經(jīng)理需求。此時(shí),采取如上方案,同樣可滿足多個(gè)項(xiàng)目復(fù)用相同業(yè)務(wù)邏輯的訴求。
實(shí)際上,uni-app之前已支持將小程序自定義組件運(yùn)行到App平臺(tái),對(duì)于有小程序組件沉淀或優(yōu)先小程序的開發(fā)者來(lái)說(shuō),這是個(gè)好消息,一套業(yè)務(wù)組件,快速運(yùn)行到iOS、Android、H5、微信小程序這四大流量平臺(tái)(實(shí)際上也可運(yùn)行到QQ小程序平臺(tái))。
uni-app 引用小程序組件演示
uni-app項(xiàng)目中使用自定義組件的方法很簡(jiǎn)單,分為三步:
1、拷貝小程序自定義組件到uni-app項(xiàng)目根目錄下的wxcomponents文件夾下
2、在 pages.json 對(duì)應(yīng)頁(yè)面的 style -> usingComponents引入組件,如:
{"pages": [{"path": "index/index","style": {"usingComponents": {"custom": "/wxcomponents/custom/index"}}}] }3、在頁(yè)面中使用自定義組件,如:
<!-- 頁(yè)面模板 (index.vue) --> <view><!-- 在頁(yè)面中對(duì)自定義組件進(jìn)行引用 --><custom name="uni-app"></custom> </view>方案實(shí)現(xiàn)思路
簡(jiǎn)單介紹下uni-app的多端發(fā)行原理。
uni-app基于Vue.js runtime,頁(yè)面文件遵循Vue.js 單文件組件 (SFC) 規(guī)范,天然對(duì)H5的支持比較好,發(fā)行到H5平臺(tái)時(shí),先通過vue-loader解析.vue文件,導(dǎo)出Vue.js 組件選項(xiàng)對(duì)象,然后在運(yùn)行時(shí)補(bǔ)充規(guī)范實(shí)現(xiàn):
- 組件:框架提供內(nèi)置組件(view/swiper/picker等)的實(shí)現(xiàn),保證平臺(tái)UI及交互的一致性
- 接口:在H5平臺(tái)封裝框架接口,比如路由跳轉(zhuǎn),showToast等界面交互
- 生命周期:Vue.js的理念是一切皆為組件,沒有應(yīng)用和頁(yè)面的概念;框架需創(chuàng)造出應(yīng)用及頁(yè)面的概念,模擬onLaunch、onShow等鉤子
uni-app發(fā)行到小程序平臺(tái)時(shí),邏輯又有不同,主要工作有2塊:
- 編譯器:將.vue文件拆分成wxml/wxss/js/json4個(gè)原生頁(yè)面文件
- 運(yùn)行時(shí):Vue.js和小程序都是邏輯視圖層框架,都有數(shù)據(jù)綁定功能;運(yùn)行時(shí)會(huì)實(shí)現(xiàn)Vue.js到小程序的數(shù)據(jù)同步,及小程序到Vue.js的事件代理
小程序自定義組件類似小程序原生的頁(yè)面開發(fā),一個(gè)自定義組件同樣由wxml/wxss/js/json 4個(gè)文件組成,另有單獨(dú)的組件規(guī)范(如Component 構(gòu)造器、Behaviors特性等)。
所以,小程序自定義組件運(yùn)行到H5平臺(tái),可借助uni-app已有平臺(tái)功能快速實(shí)現(xiàn):
- 編譯階段:將wxml/wxss/js/json4個(gè)文件合并為.vue文件(類似 uni-app 發(fā)行到小程序的逆過程),然后調(diào)用uni-app發(fā)行H5平臺(tái)的編譯過程,通過vue-loader解析.vue文件,導(dǎo)出 Vue.js 組件選項(xiàng)對(duì)象
- 運(yùn)行階段:實(shí)現(xiàn) Component 構(gòu)造器、Behaviors特性,模擬自定義組件特有的生命周期
編譯:轉(zhuǎn)換文件(mp2vue)
小程序自定義組件發(fā)行到H5平臺(tái),在編譯環(huán)節(jié)主要有2項(xiàng)工作:
其中,步驟2是Vue.js項(xiàng)目的標(biāo)準(zhǔn)編譯過程,略過不提;我們重點(diǎn)闡述步驟1。
mp2vue將4個(gè)獨(dú)立wxml/wxss/js/json 的文件合并成一個(gè).vue文件,并組裝成template、script、style 這種三段式的結(jié)構(gòu),流程包括:
具體實(shí)現(xiàn)上,uni-app編譯前先掃描wxcomponents目錄,若存在則認(rèn)為有小程序自定義組件,啟動(dòng)文件轉(zhuǎn)換工作(uni-migration插件來(lái)完成):
//加載轉(zhuǎn)換器 const migrate = require('@dcloudio/uni-migration') //掃描wxcomponents目錄 const wxcomponents = path.resolve(process.env.UNI_INPUT_DIR, 'wxcomponents') if (fs.existsSync(wxcomponents)) { migrate(wxcomponents, false, {silent: true }) // 轉(zhuǎn)換 mp-weixin 小程序組件 }接著開始對(duì)wxml/wxss/js/json文件逐個(gè)解析,并合并為一個(gè).vue文件:
module.exports = function transformFile(input, options) {//首先轉(zhuǎn)換json文件,判斷是否為組件const [jsCode, isComponent] = transformJsonFile(filepath + '.json', deps)options.isComponent = isComponent//轉(zhuǎn)換 wxml 文件const [templateCode, wxsCode = '', wxsFiles = []] = transformTemplateFile(filepath + templateExtname, options)//轉(zhuǎn)換wxss文件const styleCode = transformStyleFile(filepath + styleExtname, options, deps) || ''//轉(zhuǎn)換js文件const scriptCode = transformScriptFile(filepath + '.js', jsCode, options, deps)// 生成合并后的.vue文件源碼return [`${commentsCode}<template> ${templateCode} </template> ${wxsCode} <script> ${scriptCode} </script> <style platform="mp-weixin"> ${styleCode} </style>`,deps,wxsFiles] }進(jìn)一步細(xì)節(jié)說(shuō)明,wxml文件轉(zhuǎn)為template節(jié)點(diǎn)時(shí),需完成各項(xiàng)指令、事件等模板語(yǔ)法的轉(zhuǎn)換,例如:
將一個(gè)最簡(jiǎn)自定義組件,按照如上流程轉(zhuǎn)換,結(jié)果示意如下:
運(yùn)行時(shí):模擬小程序組件環(huán)境
uni-app的編譯器并不轉(zhuǎn)換小程序組件的 JS 代碼,依然保留Component構(gòu)造器的寫法,甚至其中的API依然是wx.開頭的方式,這些都依賴uni-app在H5平臺(tái)的運(yùn)行時(shí)來(lái)解決,主要有如下幾部分內(nèi)容:
- Component構(gòu)造器:解析小程序組件的各種選項(xiàng)配置,轉(zhuǎn)換為Vue組件定義,包括變通實(shí)現(xiàn)其中的差異部分,如小程序組件特有的”組件所在頁(yè)面的生命周期“
- Behaviors特性:轉(zhuǎn)換為Vue的混入(mixin)
- 數(shù)據(jù)響應(yīng):在H5平臺(tái)實(shí)現(xiàn)setData接口及this.data.xx = yy的數(shù)據(jù)通訊機(jī)制
- API前綴:可在運(yùn)行時(shí)通過代理機(jī)制,自動(dòng)將wx.xx替換為uni.xx,這個(gè)比較簡(jiǎn)單,不詳述
Component構(gòu)造器
uni-app在H5平臺(tái)定義了一個(gè)Component函數(shù),執(zhí)行到小程序的Component構(gòu)造器函數(shù)后,開始循環(huán)解析其屬性,并轉(zhuǎn)換成Vue組件屬性,流程示意代碼如下:
export function Component (options) {const componentOptions = parseComponent(options)componentOptions.mixins.unshift(polyfill)componentOptions.mpOptions.path = global['__wxRoute']initRelationsHandler(componentOptions)global['__wxComponents'][global['__wxRoute']] = componentOptions }export function parseComponent (mpComponentOptions) {const {data,options,methods,behaviors,lifetimes,observers,relations,properties,pageLifetimes,externalClasses} = mpComponentOptionsconst vueComponentOptions = {mixins: [],props: {},watch: {},mpOptions: {mpObservers: []}}// 開始逐個(gè)解析所有屬性parseData(data, vueComponentOptions)parseOptions(options, vueComponentOptions)parseMethods(methods, vueComponentOptions)parseBehaviors(behaviors, vueComponentOptions)parseLifetimes(lifetimes, vueComponentOptions)parseObservers(observers, vueComponentOptions)parseRelations(relations, vueComponentOptions)parseProperties(properties, vueComponentOptions)parsePageLifetimes(pageLifetimes, vueComponentOptions)parseExternalClasses(externalClasses, vueComponentOptions)parseLifecycle(mpComponentOptions, vueComponentOptions)parseDefinitionFilter(mpComponentOptions, vueComponentOptions)// 返回 Vue 組件return vueComponentOptions }在這個(gè)過程中,需處理小程序自定義組件和 Vue組件的屬性對(duì)應(yīng)關(guān)系及細(xì)節(jié)差異,如小程序組件的lifetimes:
小程序的pageLifetimes(組件所在頁(yè)面的生命周期)在Vue中是沒有的,需要映射為uni-app封裝的頁(yè)面生命周期:
Behaviors特性的實(shí)現(xiàn)過程,類似Component構(gòu)造器,不再贅述。
數(shù)據(jù)響應(yīng)
Vue和小程序都有一套數(shù)據(jù)綁定系統(tǒng),但機(jī)制不同,比如在Vue體系下,數(shù)據(jù)賦值是這樣的:
this.a = 1但在小程序中,數(shù)據(jù)賦值方式則是這樣的:
this.setData({a:1 }) //響應(yīng)式 this.data.a = 2 //非響應(yīng)式另外,小程序和Vue在數(shù)據(jù)的properties、observer等方面都存在不少差異,經(jīng)過我們?cè)u(píng)估,若將小程序的數(shù)據(jù)響應(yīng)用法直接映射到Vue體系下,復(fù)雜度較高且有性能壓力,故uni-app在H5平臺(tái)按照微信的語(yǔ)法規(guī)范,單獨(dú)實(shí)現(xiàn)了一套數(shù)據(jù)響應(yīng)系統(tǒng)。
// 小程序的setData在H5平臺(tái)的實(shí)現(xiàn) function setData (data, callback) {if (!isPlainObject(data)) {return}Object.keys(data).forEach(key => {if (setDataByExprPath(key, data[key], this.data)) {!hasOwn(this, key) && proxy(this, SOURCE_KEY, key);}});this.$forceUpdate();//數(shù)據(jù)變化,強(qiáng)制視圖更新(響應(yīng)式)isFn(callback) && this.$nextTick(callback); }將setData掛載到 vm 對(duì)象上,可通過this.setData這種小程序的方式調(diào)用;同時(shí)將數(shù)據(jù)綁定到data屬性上,支持this.data.xx的訪問方式。
export function initState (vm) {const instanceData = JSON.parse(JSON.stringify(vm.$options.mpOptions.data || {}))vm[SOURCE_KEY] = instanceData//vm對(duì)象上掛載 setData 方法,實(shí)現(xiàn)小程序方法vm.setData = setData const propertyDefinition = {get () {return vm[SOURCE_KEY]},set (value) {vm[SOURCE_KEY] = value}}//小程序用法,可通過this.data.xx訪問Object.defineProperties(vm, {data: propertyDefinition,properties: propertyDefinition})Object.keys(instanceData).forEach(key => {proxy(vm, SOURCE_KEY, key)}) }雖然數(shù)據(jù)響應(yīng)是uni-app自己實(shí)現(xiàn)的,但渲染依然使用了Vue框架的render函數(shù),此時(shí)需小程序規(guī)范中的this.data.xx和Vue規(guī)范中的this.xx保持一致,通過代理的方式實(shí)現(xiàn):
// mp/polyfill/state/proxy.js const sharedPropertyDefinition = {enumerable: true,configurable: true };function proxy (target, sourceKey, key) {sharedPropertyDefinition.get = function proxyGetter () {return this[sourceKey][key]};sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val;};Object.defineProperty(target, key, sharedPropertyDefinition); }這里僅列出了主要的幾步,中間涉及細(xì)節(jié)很多;部分無(wú)法通過Vue擴(kuò)展機(jī)制實(shí)現(xiàn)的功能,只好修改Vue.js的內(nèi)核源碼,比如updateProperties支持、小程序wxs、externalClasses等功能在H5平臺(tái)的支持,都需要定制部分 Vue.js runtime 源碼。
結(jié)語(yǔ)
本文分享了uni-app將微信小程序自定義組件發(fā)行到H5平臺(tái)的實(shí)現(xiàn)思路,希望對(duì)大家有所啟發(fā)。
但這種方案,歸根到底是為了解決多套項(xiàng)目并存時(shí)的業(yè)務(wù)重復(fù)開發(fā)的問題。
如果你是從頭開發(fā),我們建議直接選擇業(yè)內(nèi)成熟的跨端框架,既可以保持一套代碼,更省力的維護(hù),還可以借助框架的成熟生態(tài)(如跨端UI庫(kù)及插件市場(chǎng)),基于成熟輪子,快速完成業(yè)務(wù)的上線開發(fā);
uni-app框架代碼,包括小程序組件發(fā)行到H5平臺(tái)的代碼,全部開源在github,如果大家對(duì)本文邏輯有疑問,歡迎提交issue交流。
總結(jié)
以上是生活随笔為你收集整理的app vue 真机运行_uni-app黑魔法:小程序自定义组件运行到H5平台的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华三交换机配置多个镜像口_配置本地端口镜
- 下一篇: iap升级问题 stm32f103r8_