小程序第三方框架對比 ( wepy / mpvue / taro )
眾所周知如今市面上端的形態(tài)多種多樣拆又,手機(jī)Web驮俗、ReactNative、微信小程序, 支付寶小程序, 快應(yīng)用等,每一端都是巨大的流量入口匙姜,當(dāng)業(yè)務(wù)要求同時(shí)在不同的端都要求有所表現(xiàn)的時(shí)候,針對不同的端去編寫多套代碼的成本顯然非常高涎显,這時(shí)候只編寫一套代碼就能夠適配到多端的能力就顯得極為需要崔兴。但面對目前市面上成熟的小程序第三方框架如何針對自己的需求進(jìn)行選擇也是一個(gè)麻煩事,本文針對當(dāng)前市面上的三大轉(zhuǎn)譯框架進(jìn)行一個(gè)綜合對比,希望能對大家的技術(shù)選擇有所幫助,如有哪里不妥的地方希望指正;
小程序開發(fā)有哪些痛點(diǎn)?
頻繁調(diào)用 setData及 setData過程中頁面跳閃
組件化支持能力太弱(幾乎沒有)
不能使用 less、scss 等預(yù)編譯器
request 并發(fā)次數(shù)限制
為什么使用第三方框架?
只要熟悉vue或react即可快速上手,學(xué)習(xí)成本低
一套代碼可在多端編譯運(yùn)行(微信,支付寶,h5,RN) 支付寶小程序暫不完善
組件化開發(fā)著蛙,完美解決組件隔離,組件嵌套耳贬,組件通信等問題
支持使用第三方 npm 資源
使小程序可支持 Promise,解決回調(diào)煩惱
可使用 Generator Fu-nction / Class / Async Function 等特性猎唁,提升開發(fā)效率
對小程序本身的優(yōu)化咒劲,如生命周期的補(bǔ)充,性能的優(yōu)化等等
支持樣式編譯器: Scss/Less诫隅,模板編譯器腐魂,代碼編譯器:Babel/Typescript。
第三方框架對比 wepy mpvue taro
在這里我通過對目前已開源的三種常用小程序框架做一個(gè)綜合對比, 還有一個(gè)叫nanchi的基于react的小程序轉(zhuǎn)譯框架,由于沒來的及研究暫不做比較;
騰訊團(tuán)隊(duì)開源的一款類vue語法規(guī)范的小程序框架,借鑒了Vue的語法風(fēng)格和功能特性,支持了Vue的諸多特征逐纬,比如父子組件蛔屹、組件之間的通信、computed屬性計(jì)算豁生、wathcer監(jiān)聽器兔毒、props傳值、slot槽分發(fā)甸箱,還有很多高級的特征支持:Mixin混合育叁、攔截器等;WePY發(fā)布的第一個(gè)版本是2016年12月份,也就是小程序剛剛推出的時(shí)候芍殖,到目前為止豪嗽,WePY已經(jīng)發(fā)布了52個(gè)版本, 最新版本為1.7.2;
-
**MpVue **http://mpvue.com/mpvue/#-html
美團(tuán)團(tuán)隊(duì)開源的一款使用 Vue.js 開發(fā)微信小程序的前端框架。使用此框架,開發(fā)者將得到完整的 Vue.js 開發(fā)體驗(yàn)龟梦,同時(shí)為 H5 和小程序提供了代碼復(fù)用的能力隐锭。mpvue在發(fā)布后的幾天間獲得2.7k的star,上升速度飛起,截至目前為止已經(jīng)有13.7k的star;
京東凹凸實(shí)驗(yàn)室開源的一款使用 React.js 開發(fā)微信小程序的前端框架。它采用與 React 一致的組件化思想计贰,組件生命周期與 React 保持一致成榜,同時(shí)支持使用 JSX 語法,讓代碼具有更豐富的表現(xiàn)力蹦玫,使用 Taro 進(jìn)行開發(fā)可以獲得和 React 一致的開發(fā)體驗(yàn)赎婚。,同時(shí)因?yàn)槭褂昧藃eact的原因所以除了能編譯h5, 小程序外還可以編譯為ReactNative;
Star
![image](http://upload-images.jianshu.io/upload_images/14807846-2ac10827b3ac82ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
生命周期
同為vue規(guī)范的mpvue和wepy的生命周期和各種方法不盡相同
wepy
wepy生命周期基本與原生小程序相同,再此基礎(chǔ)上糅合了一些vue的特性; 對于WePY中的methods屬性,因?yàn)榕cVue中的使用習(xí)慣不一致樱溉,非常容易造成誤解挣输,這里需要特別強(qiáng)調(diào)一下:WePY中的methods屬性只能聲明頁面wxml標(biāo)簽的bind、catch事件福贞,不能聲明自定義方法撩嚼,這與Vue中的用法是不一致的。
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">import wepy from 'wepy';
export default class MyPage extends wepy.page { // export default class MyComponent extends wepy.component {
customData = {} // 自定義數(shù)據(jù)
customFunction () {} //自定義方法
onLoad () {} // 在Page和Component共用的生命周期函數(shù)
onShow () {} // 只在Page中存在的頁面生命周期函數(shù)
config = {}; // 只在Page實(shí)例中存在的配置數(shù)據(jù)挖帘,對應(yīng)于原生的page.json文件
data = {}; // 頁面所需數(shù)據(jù)均需在這里聲明完丽,可用于模板數(shù)據(jù)綁定
components = {}; // 聲明頁面中所引用的組件,或聲明組件中所引用的子組件
mixins = []; // 聲明頁面所引用的Mixin實(shí)例
computed = {}; // 聲明計(jì)算屬性(詳見后文介紹)
watch = {}; // 聲明數(shù)據(jù)watcher(詳見后文介紹)
methods = {}; // 聲明頁面wxml中標(biāo)簽的事件處理函數(shù)拇舀。注意逻族,此處只用于聲明頁面wxml中標(biāo)簽的bind、catch事件骄崩,自定義方法需以自定義方法的方式聲明
events = {}; // 聲明組件之間的事件處理函數(shù)
}</pre>
](javascript:void(0); "復(fù)制代碼")
mpvue
mpvue 除了 Vue 本身的生命周期外聘鳞,還兼容了小程序生命周期,這部分生命周期鉤子的來源于微信小程序的 Page要拂, 除特殊情況外抠璃,不建議使用小程序的生命周期 鉤子。
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1Vue
3 beforeCreate
4 created
5 beforeMount
6 mounted
7 beforeUpdate
8 updated
9 activated 10 deactivated 11 beforeDestroy 12 destroyed 13 app 部分
15 onLaunch脱惰,初始化 16 onShow搏嗡,當(dāng)小程序啟動,或從后臺進(jìn)入前臺顯示 17 onHide拉一,當(dāng)小程序從前臺進(jìn)入后臺 18 page 部分
20 onLoad采盒,監(jiān)聽頁面加載 21 onShow,監(jiān)聽頁面顯示 22 onReady舅踪,監(jiān)聽頁面初次渲染完成 23 onHide纽甘,監(jiān)聽頁面隱藏 24 onUnload,監(jiān)聽頁面卸載 25 onPullDownRefresh抽碌,監(jiān)聽用戶下拉動作 26 onReachBottom悍赢,頁面上拉觸底事件的處理函數(shù) 27 onShareAppMessage决瞳,用戶點(diǎn)擊右上角分享 28 onPageScroll,頁面滾動 29 onTabItemTap, 當(dāng)前是 tab 頁時(shí)左权,點(diǎn)擊 tab 時(shí)觸發(fā) (mpvue 0.0.16 支持)</pre>
](javascript:void(0); "復(fù)制代碼")
簡單示例
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">new Vue({
data: {
a: 1 },
created () { // this
指向 vm 實(shí)例
console.log('a is: ' + this.a)
},
onShow () { // this
指向 vm 實(shí)例
console.log('a is: ' + this.a, '小程序觸發(fā)的 onshow')
}
}) // => "a is: 1"</pre>
](javascript:void(0); "復(fù)制代碼")
taro與react生命周期完全相同
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">class Clock extends Component {
constructor (props) {
super(props) this.state = { date: new Date() }
}
componentDidMount() {
}
componentWillUnmount() {
}
render () { return ( <View>
<Text>Hello, world!</Text>
<Text>現(xiàn)在的時(shí)間是 {this.state.date.toLocaleTimeString()}.</Text>
</View>
)
}
}</pre>
](javascript:void(0); "復(fù)制代碼")
列表渲染
在列表渲染上三者也分別有不同的應(yīng)用方法
wepy當(dāng)需要循環(huán)渲染W(wǎng)ePY組件時(shí)(類似于通過wx:for
循環(huán)渲染原生的wxml標(biāo)簽)皮胡,必須使用WePY定義的輔助標(biāo)簽<repeat>
](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"><template>
** <repeat for="{{list}}" key="index" index="index" item="item">**
<child :item="item"></child>
** </repeat>**
</template>
<script> import wepy from 'wepy'; // 引入child組件文件
import Child from '../components/child';
export default class Index extends wepy.component {
components = { // 聲明頁面中要使用到的Child組件的ID為child
child: Child
}
data = {
list: [{id: 1, title: 'title1'}, {id: 2, title: 'title2'}]
}
} </script></pre>
[](javascript:void(0); "復(fù)制代碼")
mpvue使用v-for與vue一致,只是需要注意一點(diǎn),嵌套列表渲染赏迟,必須指定不同的索引屡贺!
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">
<template>
<ul v-for="(card, index) in list">
<li v-for="(item, itemIndex) in card"> {{item.value}} </li>
</ul>
</template></pre>
](javascript:void(0); "復(fù)制代碼")
taro的列表循環(huán)用法基本與react相同,有一點(diǎn)需要注意,在 React 中,JSX 是會編譯成普通的 JS 的執(zhí)行锌杀,每一個(gè) JSX 元素甩栈,其實(shí)會通過 createElement
函數(shù)創(chuàng)建成一個(gè) JavaScript 對象(React Element),因此實(shí)際上你可以這樣寫代碼 React 也是完全能渲染的:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">const list = this.state.list.map(l => { if (l.selected) { return <li>{l.text}</li>
}
}).filter(React.isValidElement)</pre>
但是 Taro 中糕再,JSX 會編譯成微信小程序模板字符串量没,因此你不能把 map
函數(shù)生成的模板當(dāng)做一個(gè)數(shù)組來處理。當(dāng)你需要這么做時(shí)突想,應(yīng)該先處理需要循環(huán)的數(shù)組殴蹄,再用處理好的數(shù)組來調(diào)用 map 函數(shù)。例如上例應(yīng)該寫成:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">const list = this.state.list
.filter(l => l.selected)
.map(l => { return <li>{l.text}</li>
})</pre>
事件處理
mpvue目前全支持小程序的事件處理器,引入了 Vue.js 的虛擬 DOM 猾担,在前文模版中綁定的事件會被掛在到 vnode 上袭灯,同時(shí) compiler 在 wxml 上綁定了小程序的事件,并做了相應(yīng)的映射绑嘹,所以你在真實(shí)點(diǎn)擊的時(shí)候通過 runtime 中 handleProxy
通過事件類型分發(fā)到 vnode 的事件上稽荧,同 Vue 在 WEB 的機(jī)制一樣,所以可以做到無損支持圾叼。同時(shí)還順便支持了自定義事件和 $emit
機(jī)制
](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">// 事件映射表蛤克,左側(cè)為 WEB 事件,右側(cè)為 小程序 對應(yīng)事件
{
click: 'tap',
touchstart: 'touchstart',
touchmove: 'touchmove',
touchcancel: 'touchcancel',
touchend: 'touchend',
tap: 'tap',
longtap: 'longtap',
input: 'input',
change: 'change',
submit: 'submit',
blur: 'blur',
focus: 'focus',
reset: 'reset',
confirm: 'confirm',
columnchange: 'columnchange',
linechange: 'linechange',
error: 'error',
scrolltoupper: 'scrolltoupper',
scrolltolower: 'scrolltolower',
scroll: 'scroll' }</pre>
](javascript:void(0); "復(fù)制代碼")
踩坑注意(官方文檔):
- 列表中沒有的原生事件也可以使用例如 bindregionchange 事件直接在 dom 上將bind改為@
@regionchange
,同時(shí)這個(gè)事件也非常特殊夷蚊,它的 event type 有 begin 和 end 兩個(gè),導(dǎo)致我們無法在handleProxy
中區(qū)分到底是什么事件髓介,所以你在監(jiān)聽此類事件的時(shí)候同時(shí)監(jiān)聽事件名和事件類型既<map @regionchange="functionName" @end="functionName" @begin="functionName"><map>
- 小程序能力所致惕鼓,bind 和 catch 事件同時(shí)綁定時(shí)候,只會觸發(fā) bind ,catch 不會被觸發(fā)唐础,要避免踩坑箱歧。
- 事件修飾符
-
.stop
的使用會阻止冒泡,但是同時(shí)綁定了一個(gè)非冒泡事件一膨,會導(dǎo)致該元素上的 catchEventName 失效呀邢! -
.prevent
可以直接干掉,因?yàn)樾〕绦蚶餂]有什么默認(rèn)事件豹绪,比如submit并不會跳轉(zhuǎn)頁面 -
.capture
支持1.0.9
-
.self
沒有可以判斷的標(biāo)識 -
.once
也不能做价淌,因?yàn)樾〕绦驔]有 removeEventListener, 雖然可以直接在 handleProxy 中處理,但非常的不優(yōu)雅,違背了原意蝉衣,暫不考慮
-
- 其他 鍵值修飾符 等在小程序中壓根沒鍵盤括尸,所以。病毡。濒翻。
wepy事件綁定區(qū)別于vue,根據(jù)原生小程序事件提供了語法優(yōu)化
綁定事件 bindtap="click" 替換為 @tap="click",
取消冒泡 原catchtap="click"替換為@tap.stop="click"啦膜。
捕獲監(jiān)聽事件 capture-bind:tap="click" 替換為 @tap.capture="click"有送,
中斷捕獲監(jiān)聽 capture-catch:tap=“click"替換為 @tap.capture.stop="click"。
**Taro **元素的事件處理和 DOM 元素的很相似僧家。但是有一點(diǎn)語法上的不同:
Taro 事件綁定屬性的命名采用駝峰式寫法雀摘,而不是小寫。 如果采用 JSX 的語法你需要傳入一個(gè)函數(shù)作為事件處理函數(shù)啸臀,而不是一個(gè)字符串 (DOM 元素的寫法)届宠。 例如,傳統(tǒng)的微信小程序模板:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"><button onclick="activateLasers"> Activate Lasers </button></pre>
Taro 中稍稍有點(diǎn)不同:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"><button onClick={this.activateLasers}> Activate Lasers </button></pre>
在 Taro 中另一個(gè)不同是你不能使用 catchEvent
的方式阻止事件冒泡乘粒。你必須明確的使用 stopPropagation
豌注。例如,阻止事件冒泡你可以這樣寫:
](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">class Toggle extends React.Component {
constructor (props) {
super(props) this.state = {isToggleOn: true}
}
onClick = (e) => {
e.stopPropagation() this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}))
}
render () { return ( <button onClick={this.onClick}> {this.state.isToggleOn ? 'ON' : 'OFF'} </button>
)
}
}</pre>
](javascript:void(0); "復(fù)制代碼")
** request請求**
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word;">wepy對wx.request做了接受參數(shù)的修改,值得一提的是它提供了針對全局的intercapter攔截器 </pre>
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">// 原生代碼:
wx.request({
url: 'xxx',
success: function (data) {
console.log(data);
}
}); // WePY 使用方式, 需要開啟 Promise 支持灯萍,參考開發(fā)規(guī)范章節(jié)
wepy.request('xxxx').then((d) => console.log(d)); // async/await 的使用方式, 需要開啟 Promise 和 async/await 支持轧铁,參考 WIKI
async function request () {
let d = await wepy.request('xxxxx');
console.log(d);
}</pre>
](javascript:void(0); "復(fù)制代碼")
攔截器
[](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">import wepy from 'wepy';
export default class extends wepy.app {
constructor () { // this is not allowed before super()
super(); // 攔截request請求
this.intercept('request', { // 發(fā)出請求時(shí)的回調(diào)函數(shù)
config (p) { // 對所有request請求中的OBJECT參數(shù)對象統(tǒng)一附加時(shí)間戳屬性
p.timestamp = +new Date();
console.log('config request: ', p); // 必須返回OBJECT參數(shù)對象,否則無法發(fā)送請求到服務(wù)端
return p;
}, // 請求成功后的回調(diào)函數(shù)
success (p) { // 可以在這里對收到的響應(yīng)數(shù)據(jù)對象進(jìn)行加工處理
console.log('request success: ', p); // 必須返回響應(yīng)數(shù)據(jù)對象旦棉,否則后續(xù)無法對響應(yīng)數(shù)據(jù)進(jìn)行處理
return p;
}, //請求失敗后的回調(diào)函數(shù)
fail (p) {
console.log('request fail: ', p); // 必須返回響應(yīng)數(shù)據(jù)對象齿风,否則后續(xù)無法對響應(yīng)數(shù)據(jù)進(jìn)行處理
return p;
}, // 請求完成時(shí)的回調(diào)函數(shù)(請求成功或失敗都會被執(zhí)行)
complete (p) {
console.log('request complete: ', p);
}
});
}
}</pre>
](javascript:void(0); "復(fù)制代碼")
taro對request進(jìn)行了二次封裝,可以使用Taro.request(OBJECT)發(fā)起網(wǎng)絡(luò)請求,支持 Promise
化使用绑洛。
](javascript:void(0); "復(fù)制代碼")
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">import Taro from '@tarojs/taro' Taro.request({
url: 'http://localhost:8080/test',
data: {
foo: 'foo',
bar: 10 },
header: { 'content-type': 'application/json' }
})
.then(res => console.log(res.data))</pre>
](javascript:void(0); "復(fù)制代碼")
mpvue沒有對request做特殊優(yōu)化,與原生相同,可以自己根據(jù)需要進(jìn)行封裝
狀態(tài)管理
wepy 可引用Redux和Mbox,目前wepy的腳手架內(nèi)已經(jīng)集成了redux,選擇需要即可;
mpVue使用vuex
taro使用Redux
如何選擇適合自己的項(xiàng)目
- 如果只需要做一個(gè)微信小程序則根據(jù)自己的擅長框架選擇mpvue或taro
- 如果是當(dāng)前老項(xiàng)目想像向程序遷移同時(shí)老項(xiàng)目又是使用vue開發(fā),建議使用mpvue或wepy
- 如果是老項(xiàng)目使用react開發(fā)且需要部分遷移小程序,建議使用taro
- 如果是新項(xiàng)目且新項(xiàng)目需要同時(shí)支持微信小程序和支付寶小程序, 建議使用原生開發(fā),因?yàn)槟壳翱蚣艿霓D(zhuǎn)譯支付寶小程序支持并不是很好,且出了問題不好定位修改, 但如果是小demo不涉及太多邏輯的項(xiàng)目都可以使用框架作為嘗鮮; 但如果是涉及太多交互邏輯的則不建議使用框架轉(zhuǎn)譯,由于支付寶小程序在視圖層基本與小程序一致所以建議手動更改替換部分方法和全局替換一些屬性或文件名,如wxml替換為axml這種, 手動轉(zhuǎn)換時(shí)間比大概是四比一; 當(dāng)然如果人手足夠一端開發(fā)一個(gè)是最好的...