近些年,整個前端領域發展迅速,效率型的前端框架也層出不窮,每一個團隊選擇的技術解決方案都不太一致,由於互聯網的特性及中國自身的特點,各個產品對於多端的投放的需求是一致的。像小程序這種跨端場景和現有的開發方式也不同,爲了知足業務的需求技術人員在平常開發中會由於投放平臺的不同而進行多套代碼的維護,效率較低且成本也高。跨端框架應景而生(這裏就不過多介紹各個跨端的框架了),在我看來大前端是趨勢,跨端是趨勢的第一步,而對於技術人員來講在不影響功能體驗的狀況下能解決維護多套代碼的痛點很是重要,改編一下比較流行的一句話:「lean once,write once,run anywhere」。前端
通過將近兩年的發展,小程序已經深刻用戶的平常生活,小程序應用數量超過了百萬量級,覆蓋200多個細分的行業,日活用戶達到兩個億。與此同時,像支付寶、百度、頭條、手Q等等都開始了自家的小程序生態,百家爭鳴應景而生。青桔單車做爲便民的出行工具,對於用戶使用方式上也是成本越低體驗越友好,即用即走的小程序已然成爲平臺選擇的趨勢。vue
高效、穩定、多入口就是業務如今的要求,青桔單車是日活相對較高的小程序(目前在阿拉丁小程序TOP榜前10),這也要求咱們對小程序的性能(加載、渲染、響應的時間)、穩定及安全有較高的標準。react
同時業務也須要在各個平臺上得到更多的入口,這就直接致使咱們在選擇框架,業務開發時要求比較嚴謹。webpack
從用戶角度出發,爲了減小用戶使用成本(下載安裝或更新APP),咱們選擇了市場上比較符合單車特性的平臺做爲入口。那麼這時候對於研發來講就會有不少問題,咱們在選擇框架時,會對如下點有較高的指望或要求:web
引用chameleon官網的簡介:Chameleon 不只僅是跨端解決方案,讓開發者高效、低成本開發多端原生應用。基於優秀的前端打包工具Webpack,吸取了業內多年來積累的最有用的工程化設計,提供了前端基礎開發腳手架命令工具,幫助端開發者從開發、聯調、測試、上線等全流程高效的完成業務開發。從chameleon框架的架構設計圖看:vuex
綜合來看chameleon的設計模式比較適合咱們作跨端項目的開發,提高咱們的效率,不維護多套重複的代碼……npm
青桔單車簡單的業務流程圖:json
青桔單車業務相對複雜,包括登陸、認證、電子圍欄、宣教、狀態扭轉等超過 30 個頁面(不包括各類H5實現),用戶主動打開/微信掃碼進入小程序,完成登陸後開始掃碼開鎖,開鎖成功後 === 發單成功,用戶開始騎行,騎行結束後完成支付整個流程結束(這裏只提到核心流程),由於業務須要,咱們須要維護微信、支付寶、高德(快應用、百度接入中)等衆多小程序,對於研發來講最快的是copy一套代碼而後針對性進行修改,當進行新功能開發的時候就痛苦了。小程序
基於業務和多端的差別抹平方案考慮,最終CML青桔單車小程序技術方案設計圖以下設計模式
從青桔單車如今模塊看,爲了能真正的實現跨端開發,須要解決各平臺間的差別問題:
因爲各端組件化的實現方式不同(微信webcomponents、百度模擬的組件化、支付寶是react框架),多端界面的一致性是一個比較麻煩的事情,從cml自己的設計及實際的體驗來講,不論樣式的單位換算仍是組件的統一封裝都作了較好的統一。
來點gif圖,預覽一下青桔單車小程序基於cml改版後三端的效果吧:
咱們按照業務場景拆分了幾個公用的模塊包括用戶相關、發單、硬件/藍牙通訊、訂單管理、營銷等,每一個模塊都單獨store、action及暴露的commonApi,配合各個頁面邏輯實現整個產品功能,針對差別化咱們列舉一個登陸的例子經過多態方式來兼容微信、支付寶登陸接口。咱們在項目中src/componets中創建一個API的空間做多態管理API,針對login咱們按照cml的規範創建一個login.interface文件
實現以下:
<script cml-type="interface">
// 定義入參
type RESObject = {
code: string,
errMsg: string
}
type done = (res: RESObject) => Void
interface MethodsInterface {
login(timeout: String, opt: done): Void
}
</script>
<script cml-type="wx">
class Login implements LoginInterface {
login (timeout, opt) {
wx.login(timeout, code => done({code: ''12', errMsg: '成功'})); } } export default new Login(); </script> <script cml-type="alipay"> class Login implements LoginInterface { login (timeout, opt) { my.getAuthCode(timeout, code => done({code: ''12', errMsg: '成功'}));
}
}
export default new Login();
</script>複製代碼
接口定義後就能夠用統一的方式進行調用這裏想着重提一個很是容易被忽略的問題:interface中必定要定義輸入輸出,規範各端實現規範,這是爲了不在修改某個端的方法入參(增長或減小)卻沒有考慮其餘端的實現,若是全量測試會浪費很大人力且也不能保證都能覆蓋到測試,爲了可維護性,建議從一開始就堅持寫多態組件的interface,在程序層面上亡羊補牢有時候真的爲時已晚……
import login from '../../components/Api/login/login.interface';
export const login = function ({commit}) {
return new Promise((resolve, reject) => {
login.login()
.then(({code}) => {
commit(types.SET_USER_CODE, {code});
resolve({code});
})
.catch(e => {
reject();
});
});
};複製代碼
另外一方面,PM若是在某一端提個需求,例如針對支付寶用戶在登陸時候加一些特殊功能,咱們能在不影響其餘流程比進行改造,同時這是在物理上進行了隔離,能夠發佈單獨npm包,可維護性比較高。
再好比針對藍牙通訊的API微信須要端來作ArrayBuffer到HexString的轉換而支付寶不須要,這裏咱們也採用多態進行接口方式抽離,微信進行轉換,支付寶直接return原數據,整個BLE的過程很是複雜,爲了提升鏈接,通訊的成功率咱們作了不少優化,若是直接在代碼中hack會影響整個流程甚至形成整個藍牙動做的不穩定,影響開鎖率,作這層多態封裝既不影響原有邏輯,改動也相對較少成本很低。
工程化是使用軟件工程的技術和方法對項目的開發、上線和維護進行管理,由於前端過程式的開發比較低效,能夠經過模塊化、組件化、本地開發、上線部署自動化來提升研發效率。
經常使用執行命令
前端開發的過程當中,數據mock是一個比較重要的功能,在驗證邏輯、研發效率上及線上線下環境環境切換都起着很重要的做用。cml這裏也提供數據mock的功能。
import cml from "chameleon-api";
cml.get({
url: '/api/getUserInfo'
})
.then(res => {
// ...省略部分實現邏輯
cml.setStorage('user', ...res)
},
err => {
cml.showToast({
message: JSON.stringify(err),
duration: 2000
})
});複製代碼
調用方法的參數url中只須要寫api的路徑。那麼本地dev開發模式如何mock這個api請求以及build線上模式如何請求線上地址,就須要在配置文件中配置apiPrefix。
cml支持本地mock,使用方式是在/mock/api/
文件夾下建立mock數據的js文件,啓動dev模式後(apiPrefix留空表示本地ip+端口),能夠直接實現本地mock。
1.chameleon的構建過程是配置化的,項目的根目錄下提供一個chameleon.config.js文件,在該文件中可使用全局對象cml的api去操做配置對象,針對不一樣端的配置方法,添加一個端仍是比較簡單的,在編譯層配置的擴充性也有暴露
2.頁面路由配置,chameleon項目內置了一套各端統一的路由管理方式,爲區分小程序,用url表示h5,path表示小程序,同時提供mock服務接口,在路由的管理上相對容易,在小程序端構建時會將router.config.json
的內容,插入到app.json的pages字段,實現小程序端的路由。
針對首頁的消息流卡片咱們會有登陸、認證、訂單、騎行卡等等不一樣的形式,咱們封裝了一個組件來處理,靜態效果以下:
相對來講這個組件比較通用,咱們大體分爲2種類型,通知型與動做型,如上圖2就是一個純顯示的消息,其餘的是帶有按鈕的消息,在組件設計上咱們根據調用組件時傳的type不同來區分,組件使用上用props進行data傳遞。
通知型:TipsCard
動做型:ActionCard
下面是動做型的實現方式:
<template>
<view class="action-card">
<view class="{{'content '+(remindActive && 'bigSmall')}}">
// ...
</view>
</view>
</template>
<script>
class ActionCard {
mounted () {
EventBus.on(REMIND_CARD, ({index}) => {
if (index === this.index) this.remind();
});
}
methods = {
callback (e) {
EventBus.emit(ACTION_CARD, {index: this.index});
}
}
}
export default new ActionCard()
</script>複製代碼
在自定義事件的處理上,咱們經過c-bind:action綁定了一個componentAction,經過EventBus事件來傳遞執行調用上,咱們動態傳遞 component is 中的type來指定不一樣的消息流類型
<template>
<view>
<component is="{{item.type+'-card'}}" />
// ...
</view>
</template>
<script>
class Home {
beforeMount () {
this.tipsList = []
EventBus.on(ACTION_CARD, (data) => {
this.componentAction(data)
});
}
methods = {
componentAction(data) { ...// 執行實例 }
}
}
export default new Home();
</script>
複製代碼
最終實現的效果以下圖
cml框架做爲數據驅動視圖的框架,應用由數據驅動,數據邏輯變得很複雜,必須要一個好用高效的數據管理框架,咱們可能在各個頁面都須要mapState,mapAction來作組件的狀態獲取,事件分發等
import createStore from 'chameleon-store';
import user from './user';
import location from './location';
// …………這裏省略其餘狀態管理器
const store = createStore({
modules: {
user,
location,
bicycle,
// …………這裏省略其餘狀態管理器
}
});
export default store;複製代碼
這裏就列出對user狀態的入口操做
import mutations from './mutations';
import * as actions from './actions';
import * as getters from './getters';
const user = {
state: {
},
mutations: {
...mutations
},
actions: {
...actions
},
getters: {
...getters
}
};
export default user;複製代碼
cml框架設計cml提供了的chameleon-store模塊包,且用法、寫法和vuex同樣,這點很是感人,手動點贊
cml提供了計算屬性——computed、偵聽屬性——watch、類vuex的數據管理及DSL語法,從學習成本上來看較小,符合當前的開發習慣。
<script>
class Home {
data = {
remindActive: false
}
watch = {
lockState: function(cur, old) {
return this.remindActive
}
}
computed = {
...store.mapState(['location', 'user'])
}
}
export default new Home();
</script>複製代碼
性能上,cml作了array diff,由於小程序的主要運行性能瓶頸是webview和js虛擬機的傳輸性能,cml儘量diff出修改的部分進行傳輸,性能相對會更好,貼一下源碼
源碼實現比較簡單,但帶來實際的用處仍是比較大的,單車小程序有很多列表頁面,當對於列表data進行從新賦值先進入diff函數過濾出實際改變的每一項,再進行view-render,性能上會提升很多
包大小上,cml相對以前開發的版本並無大的變化,1.2mb的編譯後包大小基本保持不變,這歸功於代碼的壓縮及其自己的runtime代碼並不大,給個好評。
「苦盡甘來」是青桔單車小程序接入chameleon跨端框架的整體感覺。
苦:這裏的」苦「咱們到如今看實際上是由於一種爲將來作鋪墊而須要作更多工做的「苦」,本來只須要考慮一端的CASE但如今要本身抽象出來一系列例如接口參數,通用組件,以及須要掌握更多的平臺技術方案的「苦」。
甘:當咱們經過chameleon將青桔單車小程序上線後,應對業務拋出來的新需求,咱們不再用維護多套代碼了,也把由於維護多套代碼致使可能某一端不一致性的風險給完全排除了。不只僅是RD同窗受益,QA同窗驗收的時候由於是一套代碼邏輯因此能夠節省一半的時間。原本3天要完成的需求如今1.5天就能夠完成也極大的知足了業務方的需求,咱們認爲這就是技術驅動業務發展的一個例子,技術不能只是一個工具而是要從業務角度出發去實現纔有真正的價值。
cml作了DSL編譯轉化從根本上實現了各端的統一,引入組件多態、方法多態並抽象各端的配置能夠更靈活抹平差別,方便配置、擴展。經過深刻了解chameleon框架及項目實踐,咱們基於cml能夠再抽象出一些公共層的組件、接口,固然這裏是指的跨平臺的組件、接口,由於只有這樣才能更大化提高開發的效率,給業務帶來更多的可能。
咱們在選擇框架的時候要考慮不只要考慮其自己的性能、穩定、安全,包大小等,還須要看一下複用性及發展,好比是否能基於框架自己擴展出適合業務甚至公司級別的通用組件、API,是否方便隨着業務的變化而能作到及時響應式的擴展、變更。
到這裏青桔單車chameleon跨端實踐的介紹結束啦,有表述很差的但願多多包含並提出建議咱們及時改進,謝謝。