背景

一开始产品找我的时候,说需求很简单,「要把常规业务的完整流程搬上小程序」,我第一时间想到的是套webview,实现一个原生小程序只是时间问题,我担心的是后续版本都需要改两套代码。这不是我的风格。加上所在业务本身就运行在多端,微信/浏览器/APPx3,本身bridge底子好,所以如果能实现一套类似native app的小程序壳,业务兼容小程序并不是很难。而且我对这个小程序业务上有更多的展望,后面会说到。

webview对比原生实现

方案 优点 缺点
原生 体验好 成本高,灵活度低,多套代码
多端编译 一套代码 成本高,平台判断困难
webview 灵活,后期维护成本低 体验较差
  • 原生实现并不是说用小程序原生代码,可能基于uni-app、taro来实现,所有组件都用小程序原生组件实现,这种方案体验好,但是成本高,后期维护成本双倍。

  • 多端编译的意思是基于uni-app、taro来实现一套代码,编译成多平台代码,这种方案体验接近原生,但是因为是一套代码兼容多端,可能API兼容起来比较繁琐,可能兼容多平台小程序还好,但跨越web和小程序的平台差异化处理多了的话其实代码也是比较丑陋,难以维护的。

  • 基于webview来实现就是说所有业务流程都用load远程web代码来实现,这种方案因为需要加载完整的静态资源,所以体验上会比前两种实现差一些,毕竟代码在远端,但是目前web实现能很好的减少这种体验差距,后面会提到。

指标 原生 webview
首屏
性能
原生能力
灵活度
  • 先说首屏,首屏速度的话,小程序有天生优势,静态资源本身就是缓存好的,完成一个页面渲染只需要接口请求的时间,这是原生小程序的天然优势。巧的是,业务完成了除了订单模块以外的所有页面SSR化,可以比较好的缩短这个差距。

  • 性能上我打了个问号,在某些情况下,小程序确实性能比web好,但是我在很多场景下发现,小程序状态同步到view上的性能很差,比如实现一个sticky的navbar,或者是长列表,复杂页面的渲染,可能在几个tick才完成渲染,如果在这个tick时间里,我再次修改了状态,这个时候渲染速度跟不上,其实状态与页面已经脱钩了。

  • 原生能力这个不用说,webview调用小程序方法,可能需要通过开关页面或者是重新加载页面,使webview的onlad时间触发来做到,甚至因为平台限制,有些方法还需要新开一个原生页面,显示原生按钮来实现,所以webview的原生能力比较差。

  • 灵活度上,web可以通过服务器文件来随时更新功能和做一些bugfix,而小程序则需要发包,发包差量就要要求接口兼容等问题,所以web灵活度占优。

如何做多端兼容

一套代码多端兼容

多个APP,小程序,微信,普通浏览器,PC

方案

小程序方法处理

小程序在配合web方法上可能有点奇技淫巧,好在调通公共部分之后,基本不需要改动,所以是可以接受的。

初始化是上,做两件事情,一是产出一个供h5调用的native对象,二是需要检测当前所处的环境,然后根据环境去异步加载sdk文件,这里的关键点是我们要做个等待sdk loading,实现起来也比较简单,维护一个loadPromise,每次调用sdk前都检测一下环境是否ready即可。也可以在在在调用的时候,判断sdk是否ready,如果没有则把任务放进队列中,等ready后遍历执行

跳转

实现了一个类似native的打开关闭webview的模型,并且同步了native bridge的参数,支持新开页面,关闭页面,在当前页面跳转。基于uni.navigateTo/redirectTo/navigateBack实现。

刷新webview

方式一,关闭页面并刷新:
在全局store那里定义一个refreshKey,webview向小程序postMessage事件,小程序端监听refreshKey变化,改变url达到刷新的目的。
web postMessage -> watch store.state.refreshKey -> 设置webview src 为空 -> 拼接新的url -> 延迟更新 webview src -> webview 刷新
可能你会觉得奇怪,为什么需要这步置空?如果不置空的话,iOS左侧边缘滑动返回/安卓返回键会原地退回到刷新前的页面

方式二,原地刷新:
原地刷新原本是可以通过location.reload来实现,但这样刷新只能刷新到当前页面,如果需要同时刷新多页的话,可以通过location.replace添加约定好的参数,小程序webview通过onload回调来做对应处理。
web location.replace -> reload -> webview onload trigger -> upgrade refreshKey -> reload all webview

登陆

实现了授权手机号,因为业务基本上都是基于手机号来确定用户,所以并没有走unionid那一套,授权流程大概是这样的。因为需要小程序原生按钮来触发授权,所以只能通过打开新的原生页面来实现。

webview调用navigateTo打开登录页 ->
显示原生登陆按钮 <button open-type="getPhoneNumber" /> ->
获取加密参数ivencryptedData ->
打开webview加载专门用于解析的页面 ->
把链接参数取下并传递接口 ->
接口调用微信接口解析后确定用户/创建用户并写入cookie ->
调用postMessage refresh刷新webview页面并关闭当前页面

为什么没有使用类似token拼接在url上,webview把token传递接口来确定用户呢?是因为后端暂时没有支持token,且安全性较差。加上后续用户体系有其他迁移实现的规划,所以我们最终决定使用setcookie的方式过渡。

支付

点击支付时,通过web调用navigateTo携带requestPayment所需参数到原生支付页,原生页加载后直接调用API发起支付。至于为什么把参数全都通过链接携带的方式而不是在带订单号去原生请求相关参数呢?是因为小程序将会包含各种业务,而不是单一业务,如果通过携带订单号和业务类型来实现,可能没接入一个新的业务,小程序原生端都需要发版来支持,web就需要针对小程序版本做区分处理。所以携带支持参数,让支付页功能脱离业务性价比较高。

展望

未来微信下可以引导用户前往小程序体验,相比web体验会好一些。后续也在跟业务沟通,把只能在native实现的功能搬到小程序实现。