背景
眾所周知如今市面上端的形態(tài)多種多樣汹粤,手機(jī)Web、ReactNative田晚、微信小程序, 支付寶小程序, 快應(yīng)用等,每一端都是巨大的流量入口嘱兼,當(dāng)業(yè)務(wù)要求同時(shí)在不同的端都要求有所表現(xiàn)的時(shí)候,針對(duì)不同的端去編寫多套代碼的成本顯然非常高贤徒,這時(shí)候只編寫一套代碼就能夠適配到多端的能力就顯得極為需要芹壕。目前比較流行的框架有wepy, mpvue ,taro
WEPY tencent.github.io/wepy/document騰訊團(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 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;Taro taro.aotu.io/京東凹凸實(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;對(duì)比如下:
本文主要講一下京東凹凸實(shí)驗(yàn)室的Taro.個(gè)人感覺是這三種框架中最好用的礁击。
Taro 是什么盐杂?
Taro 是由京東 - 凹凸實(shí)驗(yàn)室打造的一套遵循 React 語法規(guī)范的多端統(tǒng)一開發(fā)框架。
Taro 是一套遵循 React語法規(guī)范的 多端開發(fā) 解決方案客税。
使用 Taro况褪,我們可以只書寫一套代碼撕贞,再通過 Taro 的編譯工具更耻,將源代碼分別編譯出可以在不同端(微信/百度/支付寶/字節(jié)跳動(dòng)小程序、QQ輕應(yīng)用捏膨、 H5秧均、React-Native React語法規(guī)范,它采用與 React 一致的組件化思想号涯,組件生命周期與 React 保持一致目胡,同時(shí)支持使用 JSX 語法,讓代碼具有更豐富的表現(xiàn)力链快,使用 Taro 進(jìn)行開發(fā)可以獲得和 React 一致的開發(fā)體驗(yàn)誉己。
import Taro, { Component } from '@tarojs/taro'
import { View, Button } from '@tarojs/components'
export default class Index extends Component {
constructor () {
super(...arguments)
this.state = {
title: '首頁(yè)',
list: [1, 2, 3]
}
}
componentWillMount () {}
componentDidMount () {}
componentWillUpdate (nextProps, nextState) {}
componentDidUpdate (prevProps, prevState) {}
shouldComponentUpdate (nextProps, nextState) {
return true
}
add = (e) => {
// dosth
}
render () {
return (
<View className='index'>
<View className='title'>{this.state.title}</View>
<View className='content'>
{this.state.list.map(item => {
return (
<View className='item'>{item}</View>
)
})}
<Button className='add' onClick={this.add}>添加</Button>
</View>
</View>
)
}
}
相關(guān)連接
Github:
https://github.com/NervJS/taro
官網(wǎng)
為什么要使用Taro?
1.一次編寫,多端運(yùn)行
既然是一個(gè)多端解決方案域蜗,Taro 最重要的能力當(dāng)然是寫一套代碼輸出多端皆可運(yùn)行的代碼巨双。目前 Taro 已經(jīng)支持一套代碼同時(shí)生成 H5 和微信小程序,App(React Native)端也即將支持霉祸,同時(shí)諸如快應(yīng)用等端也將于近期得到支持筑累。
同時(shí) Taro 也已經(jīng)投入到了生產(chǎn)環(huán)境使用,目前已經(jīng)支撐了一個(gè) 3 萬行代碼小程序 TOPLIFE 的開發(fā)丝蹭,以及部分京東購(gòu)物小程序和一起有局小程序慢宗,未來也將會(huì)支撐更多的京東核心業(yè)務(wù)小程序。
2.現(xiàn)代前端開發(fā)流程
和微信自帶的小程序框架不一樣奔穿,Taro 積極擁抱社區(qū)現(xiàn)有的現(xiàn)代開發(fā)流程镜沽,Taro 立足于微信小程序開發(fā),眾所周知小程序的開發(fā)體驗(yàn)并不是非常友好贱田,比如小程序中無法使用 npm 來進(jìn)行第三方庫(kù)的管理淘邻,無法使用一些比較新的 ES 規(guī)范等等,針對(duì)小程序端的開發(fā)弊端湘换,Taro 具有以下的優(yōu)秀特性
import Taro from '@tarojs/taro'
Taro.setStorage({ key: 'key', data: 'value' })
.then(res => console.log(res))
3.和 React 完全一致的 API 和組件化系統(tǒng)
在 Taro 中宾舅,你不用像小程序一樣區(qū)分什么是 App 組件统阿,什么是 Page 組件,什么是 Component 組件筹我,Taro 全都是 Component 組件扶平,并且和 React 的生命周期完全一致∈呷铮可以說结澄,一旦你掌握了 React,那就幾乎掌握了 Taro岸夯。而學(xué)習(xí) React 的資源也幾乎是汗牛充棟麻献,完全不用擔(dān)心學(xué)不會(huì)。
Taro 和 React 一樣猜扮,同樣使用聲明式的 JSX 語法勉吻。相比起字符串的模板語法,JSX 在處理精細(xì)復(fù)雜需求的時(shí)候會(huì)更得心應(yīng)手旅赢。
class LoginStatus extends Component {
render () {
const isLoggedIn = this.props.isLoggedIn
// 這里最好初始化聲明為 `null`齿桃,初始化又不賦值的話
// 小程序可能會(huì)報(bào)警為變量為 undefined
let status = null
if (isLoggedIn) {
status = <Text>已登錄</Text>
} else {
status = <Text>未登錄</Text>
}
return (
<View>
{status}
</View>
)
}
}
// app.js
import LoginStatus from './LoginStatus'
// 這樣會(huì)渲染 `已登錄`
class App extends Component {
render () {
return (
<View>
<LoginStatus isLoggedIn={true} />
</View>
)
}
}
Taro的安裝
1.先全局安裝@tarojs/cli
$ npm install -g @tarojs/cli
$ yarn global add @tarojs/cli
2.之后我們初始化一個(gè)名為myApp的項(xiàng)目:
$ taro init myApp
3.然后輸入你的配置:
之后等待所有依賴安裝完畢。
taro項(xiàng)目目錄如下:
├── config 配置目錄
| ├── dev.js 開發(fā)時(shí)配置
| ├── index.js 默認(rèn)配置
| └── prod.js 打包時(shí)配置
├── src 源碼目錄
| ├── components 公共組件目錄
| ├── pages 頁(yè)面文件目錄
| | ├── index index 頁(yè)面目錄
| | | ├── banner 頁(yè)面 index 私有組件
| | | ├── index.js index 頁(yè)面邏輯
| | | └── index.css index 頁(yè)面樣式
| ├── utils 公共方法庫(kù)
| ├── app.css 項(xiàng)目總通用樣式
| └── app.js 項(xiàng)目入口文件
└── package.json
Taro的基本原理
初衷
用React寫微信小程序煮盼。微信小程序原生方式開發(fā)起來太費(fèi)勁短纵。遂想用React開發(fā)微信小程序。
延伸
在React業(yè)務(wù)代碼轉(zhuǎn)微信小程序代碼這個(gè)最初的需求實(shí)現(xiàn)之后僵控,發(fā)現(xiàn)依靠同樣的轉(zhuǎn)換思路可以適配多端香到,即從1對(duì)1延伸到1對(duì)n:
P.S.其中Nerv是一種類React框架,API與React類似
P.S.Taro組件庫(kù)之所以以微信小程序?yàn)闃?biāo)準(zhǔn)报破,也是初衷使然(都做完了不能浪費(fèi)坝凭汀)
一套代轉(zhuǎn)生成多端的思路
想要一份代碼通吃n端,無非2種思路:
1.直接從1端向n - 1端轉(zhuǎn)換
2.加一層抽象泛烙,從這層抽象轉(zhuǎn)換到n端
Taro也采用了第二種思路理卑,這層抽象就是Taro業(yè)務(wù)代碼:
核心實(shí)現(xiàn)
以微信小程序?yàn)槔?部分組成:
1.配置(JSON)
2.模板(WXML)
3.樣式(WXSS)
4.邏輯(JS)
配置與樣式?jīng)]什么好說的蔽氨,難點(diǎn)在于模板的轉(zhuǎn)換和邏輯的轉(zhuǎn)換
P.S.ReactNative樣式轉(zhuǎn)換另說藐唠,也是一個(gè)難題,因?yàn)镽N在選擇器鹉究、屬性名/值及默認(rèn)值宇立,甚至CSS特性支持程度都存在較大差異
編譯轉(zhuǎn)換
要把一份代碼A轉(zhuǎn)換成另一份代碼B,需要做3件事情:
1.解析代碼A生成抽象描述(AST)
2.根據(jù)一些映射規(guī)則操作AST自赔,生成新的AST
3.根據(jù)新的AST生成代碼B
模板的轉(zhuǎn)換
以小程序?yàn)槔栲冢?JSX 語法轉(zhuǎn)換成可以在小程序運(yùn)行的字符串模板。
輸入JSX:
render () {
return (
<View className='index'>
<Button className='add_btn' onClick={this.props.add}>+</Button>
<Button className='dec_btn' onClick={this.props.dec}>-</Button>
<Button className='dec_btn' onClick={this.props.asyncAdd}>async</Button>
<View><Text>{this.props.counter.num}</Text></View>
<View><Text>Hello, World</Text></View>
</View>
)
}
經(jīng)@tarojs/transformer-wx轉(zhuǎn)換绍妨,輸出微信小程序模板:
<block wx:if="{{$taroCompReady}}">
<view class="index">
<button class="add_btn" bindtap="funPrivateEfvXr">+</button>
<button class="dec_btn" bindtap="funPrivateWnhMJ">-</button>
<button class="dec_btn" bindtap="funPrivateAdUGq">async</button>
<view><text>{{counter.num}}</text>
</view>
<view><text>Hello, World</text>
</view>
</view>
</block>
邏輯的轉(zhuǎn)換
類似于組件庫(kù)需要做多端適配润脸,各端能力差異也同樣需要適配:
組件庫(kù)以及端能力都是依靠不同的端做不同實(shí)現(xiàn)來抹平差異
運(yùn)行時(shí)框架負(fù)責(zé)適配各端能力柬脸,以支持跑在上面的Taro業(yè)務(wù)代碼,主要有3個(gè)作用:
適配組件化方案毙驯、配置選項(xiàng)等基礎(chǔ)API
適配平臺(tái)能力相關(guān)的API(如網(wǎng)絡(luò)請(qǐng)求倒堕、支付、拍照等)
提供一些應(yīng)用級(jí)的特性爆价,如事件總線(
Taro.Events
垦巴、Taro.eventCenter
)、運(yùn)行環(huán)境相關(guān)的API(Taro.getEnv()
铭段、Taro.ENV_TYPE
)骤宣、UI適配方案(Taro.initPxTransform()
)等
實(shí)現(xiàn)上,@tarojs/taro
是API適配的統(tǒng)一入口序愚,編譯時(shí)分平臺(tái)替換:
- @tarojs/taro:只是一層空殼憔披,提供API簽名
平臺(tái)適配相關(guān)的package有6個(gè):
@tarojs/taro-alipay:適配支付寶小程序
@tarojs/taro-h5:適配Web
@tarojs/taro-rn:適配ReactNative
@tarojs/taro-swan:適配百度小程序
@tarojs/taro-tt:適配頭條小程序
@tarojs/taro-qapp:適配快應(yīng)用
這些API都可以直接使用,不用關(guān)心當(dāng)前平臺(tái)是否支持展运,因?yàn)檫\(yùn)行時(shí)框架的適配工作的一部分就是抹平平臺(tái)能力API差異活逆,例如:
H5 端就無法調(diào)用掃碼精刷、藍(lán)牙等端能力拗胜,各個(gè)小程序?qū)?yè)面函數(shù)的事件支持的程度也不一樣。例如;
頁(yè)面事件函數(shù)各端支持程度如下
方法 | 作用 | 微信小程序 | 百度小程序 | 字節(jié)跳動(dòng)小程序 | 支付寶小程序 | H5 | RN |
---|---|---|---|---|---|---|---|
onPullDownRefresh | 頁(yè)面相關(guān)事件處理函數(shù)--監(jiān)聽用戶下拉動(dòng)作 | ?? | ?? | ?? | ?? | ? | ? |
onReachBottom | 頁(yè)面上拉觸底事件的處理函數(shù) | ?? | ?? | ?? | ?? | ? | ? |
onShareAppMessage | 用戶點(diǎn)擊右上角轉(zhuǎn)發(fā) | ?? | ?? | ?? | ?? | ? | ? |
onPageScroll | 頁(yè)面滾動(dòng)觸發(fā)事件的處理函數(shù) | ?? | ?? | ?? | ?? | ? | ? |
onTabItemTap | 當(dāng)前是 tab 頁(yè)時(shí)怒允,點(diǎn)擊 tab 時(shí)觸發(fā) | ?? | ?? | ?? | ?? | ? | ? |
onResize | 頁(yè)面尺寸改變時(shí)觸發(fā)埂软,詳見 響應(yīng)顯示區(qū)域變化 | ?? | ? | ? | ? | ? | ? |
componentWillPreload | 預(yù)加載 | ?? | ? | ? | ? | ? | ? |
onTitleClick | 點(diǎn)擊標(biāo)題觸發(fā) | ? | ? | ? | ?? | ? | ? |
onOptionMenuClick | 點(diǎn)擊導(dǎo)航欄額外圖標(biāo)觸發(fā) | ? | ? | ? | ??(基礎(chǔ)庫(kù) 1.3.0) | ? | ? |
onPopMenuClick | ? | ? | ? | ??(基礎(chǔ)庫(kù) 1.3.0) | ? | ? | |
onPullIntercept | 下拉截?cái)鄷r(shí)觸發(fā) | ? | ? | ? | ??(基礎(chǔ)庫(kù) 1.11.0) | ? | ? |
以上成員方法在 Taro 的頁(yè)面中同樣可以使用,書寫同名方法即可纫事,不過需要注意的勘畔,目前暫時(shí)只有小程序端支持(支持程度如上)這些方法,編譯到 H5/RN 端后這些方法均會(huì)失效丽惶。
采用微信小程序標(biāo)準(zhǔn)炫七,所以這些 API 在 H5 端運(yùn)行的時(shí)候?qū)⑹裁匆膊蛔觥?/p>
同時(shí)在業(yè)務(wù)層區(qū)分目標(biāo)環(huán)境,保證這些平臺(tái)相關(guān)的代碼僅在預(yù)期的目標(biāo)環(huán)境下執(zhí)行:
編譯時(shí):
process.env.TARO_ENV
運(yùn)行時(shí):
Taro.getEnv()
例如:
// 分平臺(tái)調(diào)用API或者分平臺(tái)處理兼容性問題
if (process.env.TARO_ENV === 'weapp') {
Taro.textToAudio()
}
// 分平臺(tái)使用不同組件
<View>
{process.env.TARO_ENV === 'weapp' && <ScrollViewWeapp />}
{process.env.TARO_ENV === 'h5' && <ScrollViewH5 />}
</View>
P.S.編譯時(shí)靜態(tài)的環(huán)境區(qū)分足夠應(yīng)對(duì)大多數(shù)場(chǎng)景了钾唬,運(yùn)行時(shí)的環(huán)境區(qū)分僅備不時(shí)之需
項(xiàng)目生命周期之間的匹配
首先一張圖看一下小程序的生命周期
1.小程序初始化完成后万哪,頁(yè)面首次加載觸發(fā)onLoad,只會(huì)觸發(fā)一次抡秆。
2.當(dāng)小程序進(jìn)入到后臺(tái)奕巍,先執(zhí)行頁(yè)面onHide方法再執(zhí)行應(yīng)用onHide方法。
3.當(dāng)小程序從后臺(tái)進(jìn)入到前臺(tái)儒士,先執(zhí)行應(yīng)用onShow方法再執(zhí)行頁(yè)面onShow方法的止。應(yīng)用生命周期和頁(yè)面生命周期不是分開的,兩者一起進(jìn)行着撩,相互交叉使用诅福,會(huì)用到相同的方法匾委,比如onShow和onHide。
然后看一下react生命周期
一個(gè)例子說明小程序和taro生命周期的映射關(guān)系為:
生命周期方法 | 作用 | 說明 |
---|---|---|
componentWillMount | 程序被載入 | 對(duì)應(yīng)微信小程序onLaunch |
componentDidMount | 程序被載入 | 對(duì)應(yīng)微信小程序onLaunch氓润,在componentWillMount之后執(zhí)行 |
componentDidShow | 程序展示出來 | 對(duì)應(yīng)微信小程序onShow |
componentDidHide | 程序被隱藏 | 對(duì)應(yīng)微信小程序onHide |
componentDidCatchError | 錯(cuò)誤監(jiān)聽函數(shù) | 對(duì)應(yīng)微信小程序 onError |
componentDidNotFound | 頁(yè)面不存在 | 對(duì)應(yīng)微信小程序 onPageNotFound |
不過當(dāng)然也包含componentWillUnmout和componentWillReceiveProps等react原始生命周期函數(shù)剩檀,用來編寫自定義組件和h5頁(yè)面。
在小程序中 旺芽,頁(yè)面還有一些專屬的方法成員沪猴,如下:
onPullDownRefresh: 頁(yè)面相關(guān)事件處理函數(shù)–監(jiān)聽用戶下拉動(dòng)作
onReachBottom: 頁(yè)面上拉觸底事件的處理函數(shù)
onShareAppMessage: 用戶點(diǎn)擊右上角轉(zhuǎn)發(fā)
onPageScroll: 頁(yè)面滾動(dòng)觸發(fā)事件的處理函數(shù)
onTabItemTap: 當(dāng)前是 tab 頁(yè)時(shí),點(diǎn)擊 tab 時(shí)觸發(fā)
componentWillPreload: 預(yù)加載采章,只在微信小程序中可用
設(shè)計(jì)稿及尺寸單位
在 Taro 中尺寸單位建議使用 px运嗜、 百分比 %莺治,Taro 默認(rèn)會(huì)對(duì)所有單位進(jìn)行轉(zhuǎn)換叠纷。在 Taro 中書寫尺寸按照 1:1 的關(guān)系來進(jìn)行書寫,即從設(shè)計(jì)稿上量的長(zhǎng)度 100px鲫构,那么尺寸書寫就是 100px抵怎,當(dāng)轉(zhuǎn)成微信小程序的時(shí)候奋救,尺寸將默認(rèn)轉(zhuǎn)換為 100rpx,當(dāng)轉(zhuǎn)成 H5 時(shí)將默認(rèn)轉(zhuǎn)換為以 rem 為單位的值反惕。
如果你希望部分 px 單位不被轉(zhuǎn)換成 rpx 或者 rem 尝艘,最簡(jiǎn)單的做法就是在 px 單位中增加一個(gè)大寫字母,例如 Px 或者 PX 這樣姿染,則會(huì)被轉(zhuǎn)換插件忽略背亥。
結(jié)合過往的開發(fā)經(jīng)驗(yàn),Taro 默認(rèn)以 750px 作為換算尺寸標(biāo)準(zhǔn)悬赏,如果設(shè)計(jì)稿不是以 750px 為標(biāo)準(zhǔn)狡汉,則需要在項(xiàng)目配置 config/index.js 中進(jìn)行設(shè)置,例如設(shè)計(jì)稿尺寸是 640px闽颇,則需要修改項(xiàng)目配置 config/index.js 中的 designWidth 配置為 640:
const config = {
projectName: 'myProject',
date: '2018-4-18',
designWidth: 640,
....
}
目前 Taro 支持 750盾戴、 640 、 828 三種尺寸設(shè)計(jì)稿兵多,他們的換算規(guī)則如下:
const DEVICE_RATIO = {
'640': 2.34 / 2,
'750': 1,
'828': 1.81 / 2
}
建議使用 Taro 時(shí)尖啡,設(shè)計(jì)稿以 iPhone 6 750px
作為設(shè)計(jì)尺寸標(biāo)準(zhǔn)。
API
在編譯時(shí)中鼠,Taro 會(huì)幫你對(duì)樣式做尺寸轉(zhuǎn)換操作可婶,但是如果是在 JS 中書寫了行內(nèi)樣式,那么編譯時(shí)就無法做替換了援雇,針對(duì)這種情況矛渴,Taro 提供了 API Taro.pxTransform
來做運(yùn)行時(shí)的尺寸轉(zhuǎn)換。
Taro.pxTransform(10) // 小程序:rpx,H5:rem
配置
默認(rèn)配置會(huì)對(duì)所有的 px
單位進(jìn)行轉(zhuǎn)換具温,有大寫字母的 Px
或 PX
則會(huì)被忽略蚕涤。
參數(shù)默認(rèn)值如下:
{
onePxTransform: true,
unitPrecision: 5,
propList: ['*'],
selectorBlackList: [],
replace: true,
mediaQuery: false,
minPixelValue: 0
}
Type: Object | Null
onePxTransform
(Boolean)
設(shè)置 1px 是否需要被轉(zhuǎn)換
unitPrecision
(Number)
REM 單位允許的小數(shù)位。
propList
(Array)
允許轉(zhuǎn)換的屬性铣猩。
replace
(Boolean)
直接替換而不是追加一條進(jìn)行覆蓋揖铜。
mediaQuery
(Boolean)
允許媒體查詢里的 px 單位轉(zhuǎn)換
minPixelValue
(Number)
設(shè)置一個(gè)可被轉(zhuǎn)換的最小 px 值
selectorBlackList
(Number)
黑名單里的選擇器將會(huì)被忽略。
配置規(guī)則對(duì)應(yīng)到 config/index.js
达皿,例如:
{
h5: {
publicPath: '/',
staticDirectory: 'static',
module: {
postcss: {
autoprefixer: {
enable: true
},
pxtransform: {
enable: true,
config: {
selectorBlackList: ['body']
}
}
}
}
},
weapp: {
// ...
module: {
postcss: {
pxtransform: {
enable: true,
config: {
selectorBlackList: ['body']
}
}
}
}
}
}
忽略
屬性
當(dāng)前忽略單個(gè)屬性的最簡(jiǎn)單的方法天吓,就是 px 單位使用大寫字母。
/* `px` is converted to `rem` */
.convert {
font-size: 16px; // converted to 1rem
}
/* `Px` or `PX` is ignored by `postcss-pxtorem` but still accepted by browsers */
.ignore {
border: 1Px solid; // ignored
border-width: 2PX; // ignored
}
文件
對(duì)于頭部包含注釋 /*postcss-pxtransform disable*/
的文件峦椰,插件不予處理龄寞。