近些年,整个前端领域发展迅速,效率型的前端框架也层出不穷,每一个团队选择的技术解决方案都不太一致,由于互联网的特性及中国自身的特点,各个产品对于多端的投放的需求是一致的。像小程序这种跨端场景和现有的开发方式也不同,为了知足业务的需求技术人员在平常开发中会由于投放平台的不同而进行多套代码的维护,效率较低且成本也高。跨端框架应景而生(这里就不过多介绍各个跨端的框架了),在我看来大前端是趋势,跨端是趋势的第一步,而对于技术人员来讲在不影响功能体验的状况下能解决维护多套代码的痛点很是重要,改编一下比较流行的一句话:“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跨端实践的介绍结束啦,有表述很差的但愿多多包含并提出建议咱们及时改进,谢谢。