cover_image

如何提升互动开发效能 - 一码多端互动开发引擎介绍

赵越 黄善波 京东零售技术
2022年10月21日 10:00

前言




在平台业务中,互动游戏和一码多端有着千丝万缕的联系。互动一方面意味着人与机之间的交互,另一方面是人与人之间的交互。京东和微信某种合作也促进了互动业务更容易通过微信域进行裂变和传播。然而,由于互动页面的自带传播力天赋,容易受到腾讯重点照顾,使用H5内嵌方式往往更是雪上加霜。所以核心互动游戏通常使用原生小程序开发。互动页面在App和微信域中的业务需求方面往往有较大比例是相同的,尤其是UI层面。在过去,通常采用小程序和H5分开开发的模式,以确保业务稳定上线。但分开开发在有限的时间面前,往往需要做出功能降级的妥协。

本文会着重介绍如何继承H5端互动开发引擎中的优秀设计,逐步拓展出一码多端的互动开发引擎。



面临的问题




一码多端引擎在设计及应用场景等方面主要存在以下问题:

1、如何选择一个合适的跨端框架,使得现有的H5代码能快速迁移到一码多端引擎中?

2、如何兼收并蓄各运行环境中的基础能力,如获取登录态,加购,埋点,跳转等?

3、如何设计一套适合互动业务的通用模型,灵活高效的复用各项目中类似的业务场景?

4、如何处理跨端项目中UI适配、兼容性处理及异常监控等开发边界问题?



做了什么以及如何做



01
跨端框架选型


目前业界已经有许多支持“一套代码,多端运行”的跨端框架,最为流行的主要有Taro、uni-app、chameleon,三者的差异对比如下:


图片


通过对比上述框架,我们最终选择Taro作为UI框架,主要原因有以下几点:

◆ Taro对微信小程序支持度非常高,官网文档丰富,同时还有一系列的配套解决方案,社区活跃,有专门的团队负责维护。

◆ 目前团队以React为主要技术栈,Taro框架对React支持度较好,团队成员学习成本低;对于已经沉淀的组件和业务模块,也可以快速稳定地迁移到一码多端引擎中。

◆ 在Taro+React框架下,我们可以直接将H5端互动引擎中基于React沉淀的Dva状态管理模块移植到一码多端引擎中。


02
数据管理



状态管理库

我们借鉴的是Dva(redux+saga)状态管理库。由于Dva中的react-router和fetch不支持小程序,因此我们不能直接照搬H5的引入方式,需要剔除掉依赖H5环境的react-router和fetch库。最终我们基于Dva的核心库dva-core做了一层轻量的封装,详细如下:


图片


跨模块数据更新

在多人协作开发时,各模块间数据同步成了难题,例如任务模块和抽奖模块都对积分有影响,但这两个模块是不同的开发人员负责的,那做完任务该如何将最新的积分值传递给抽奖模块呢?如果两个模块各自维护一份积分数据,更新时相互同步,会使得沟通成本非常高,而且逻辑复杂脆弱,很难保证积分的正确性。索性我们不如轻装上阵,做状态的搬运工,抽离一个统一的数据更新模块,专门负责请求后端最新的公共数据并存放在全局store中,所有模块可按需读取。


图片


03
通用模型



团队作战并非单打独斗,打造一个易于协同开发的通用模型至关重要。经过对过往互动项目分析总结,我们认为可以从页面场景、初始化流程、业务模块这三个维度,制定一套统一的开发规范,建设一个适合互动业务的通用开发模型。如此一来,不仅可以提升跨业务项目的开发效率,也能形成统一的知识体系,便于团队成员学习和交流。


场景抽象

通过分析以往互动项目的PRD和设计稿,我们发现互动主玩法页面主要可以划分为预加载、主玩法和异常界面三种渲染场景。这3个场景在互动业务中均扮演较重要角色,进入页面首先渲染的是预加载场景,在资源和数据都加载完成后,进入主玩法场景,在这个过程中如果发生异常,则切换至活动异常场景。设计这个机制的主要目的是为了寻求沉淀互动开发的方法论,利于开发任务拆解、让代码可以按更小的颗粒度进行复用,提高代码可维护性。在技术实现中,我们发现如果采用路由,当用户在活动异常场景手动刷新页面时,页面无法切换到预加载场景,也重复执行以上过程。并且基于Taro开发的传统路由页面,会编译出独立的小程序页面,无法在同一个页面中渲染这3个场景。因此无法采用路由方式实现场景管理,为了解决这个问题,我们自研了一个基于redux的轻量级场景管理器。根据“约定大于配置”的设计范式,我们将所有的页面场景归纳在scenes文件夹下,并内置了预加载、主玩法、活动异常三个场景。各场景代码相互隔离,可并行开发,易于团队分工协作。


图片


初始化流程模式设计

在互动项目开发过程中,页面初始化往往至关重要,不仅逻辑复杂,而且通常情况下都需要提前介入。它是各模块并行开发的基础,逻辑处理不当会拖累其他模块的开发进度,影响整体排期。

在经历了长期大量的互动实践后,我们发现虽然各个项目页面千差万别,但整个初始化过程却是大同小异的,究其本质可以分为三个阶段,资源预加载阶段、个性化业务处理阶段和页面渲染阶段。而其中个性化业务处理阶段中包含的业务非常类似,例如,在618T级热爱奇旅项目中,需要拉取广告素材和好友助力,而2021年年货节的压岁钱项目中,则是收卡和好友助力,这两个项目都存在好友助力业务,但又不完全相同。我们对个性化业务处理阶段中的业务功能进行拆分后发现,其主要差异在于业务功能的数量和执行顺序不同。因此我们借鉴了业界通用的中间件模式,将个性化业务处理阶段中的业务功能原子化,将所需要的业务功能按顺序添加到中间件数组中,以此来解决无法动态扩展和调整执行顺序的问题。有了这个中间件,我们就能将整个初始化流程固化并沉淀到一码多端引擎中,开发人员可以开箱即用,配置好首页接口就能打通整个初始化流程,完全不阻塞其他模块的开发;而且对于个性化业务处理阶段的各业务功能,可以进行独立开发,互不影响,便于协作分工和code review。


图片


功能模块化

通过对以往做过的项目进行分析总结,我们整理出使用频率较高的业务功能,再对这些功能进行分析,设计成独立的业务模块,以减少开发成本,最大限度实现业务功能的复用。以下是我们整理出的常用业务模块:


图片


模块化的意义在于最大化的设计重用,以最少的模块,更快速地满足更多的个性化需求。以上模块,大部分可以直接复用,个别可采用半开发的方式复用。同时,各模块相互独立,彼此之间互不影响,降低了代码耦合度,也可以独立去更新和改进,便于维护。


04
UI规范



移动端适配

对于早期PC端的页面,由于各PC厂商生产的屏幕比较大,宽于页面内容,页面中的内容都能正常地显示,无需额外的适配。而如今移动终端(手机)正处于快速更新的时代,每个品牌的手机都有着不同的物理分辨率,这样就会导致每台设备的逻辑分辨率也不尽相同。如果将一个固定大小的页面,通过不同屏幕尺寸的手机端进行访问,一部分机型可能会出现页面内容挤压、布局错乱或者横向滚动条的情况,导致用户体验受到影响。对于以上问题,业界通用方案是页面内容根据设备分辨率使用不同像素进行展示。然而我们在开发时,收到的设计稿通常只有一种规格。如何让一种规格的设计稿,在不同尺寸的设备上都保持最佳的交互体验,正是移动端适配要做的事。我们希望在手机屏幕较大时,页面的内容能够放大到屏幕大小;手机屏幕较小时,页面的内容能够缩小到屏幕大小。通过调研发现,常见的适配方案主要有rem、vw/vh、百分比布局和媒体查询。通过对比分析,我们采用了Taro默认支持的rem方案,其他方案主要存在以下问题:

◆ 媒体查询无法穷举所有的屏幕尺寸。

◆ 百分比方案的值不好计算,一般需要固定高度,但高度并不好设置。

◆ vw/vh方案不能精准的换算,有一定的兼容性问题,第三方的组件库也必须采用vw/vh方案。

在rem方案中,rem是相对长度单位,可以做到一样的取值,在不同尺寸的设备上按比例缩放大小。根据不同屏幕的宽度,通过js计算动态修改根元素的字体大小,页面内容可以很好地根据根元素的字体大小进行适配,从而达到各种屏幕一致的用户体验。


页面布局

页面布局我们依然沿用了H5 Sirius互动引擎的方式。类比会场页面的平铺展示(以 y 轴方向延伸),互动类更偏向于 z 轴方向层叠,层次关系更为复杂。我们可以借鉴游戏的分层思路对互动页面进行分层。


图片


◇ 背景层:背景图,页面底色

◇ 动画层:也是主体层,用于放置内容主体、banner、feeds 流等

◇ HUD 层:主要是各种交互按钮,用于触发主线动画、支线任务以及跳转其他页面

◇ 弹框层:有一层透明遮罩,放置各类弹框

◇ Toast:最上层,反馈操作提示、loading等

可以看到大的分层已经有 5 层了,如果涉及到一些复杂动画,需要图层进行相互叠加,那层级关系可能会多达 10+ 层。在多人参与开发的情况下,就会出现模块相互覆盖的问题。这里建议在团队内部进行一些简单的规范,例如:

◇ 动画层 z-index 范围在 1-100 之间

◇ HUD 层的 z-index 在 100-1000 之间

◇ 弹框层的 z-index 在 1000 以上

这样,可以减少许多样式覆盖问题。如果使用 React 开发,还能通过 Portal 将弹框层和 toast 从主体内抽离出来,进一步减少冲突发生。


动画方案

在互动页面中,动画是必不可少的。合理的动画能增加界面活力和体验舒适度,让用户认知过程更加自然。对于一些需要重点突出的内容,能够借助动画吸引用户的注意力。


图片


在长期的互动业务实践中,我们总结出常用的动画主要有以下几类:

◇ CSS3 transition(过渡动画):transition是指控制最开始的状态和最末的状态的动画。一般只能在某个标签元素样式或状态改变时进行平滑的动画效果过渡。

◇ CSS3 animation(帧动画):animation是指通过对关键帧和循环次数的控制,页面标签元素会根据设定好的样式改变进行平滑过渡。而且关键帧状态的控制是通过百分比来控制的。

◇ WebGL/Canvas动画:一般对一些2D或者3D沉浸式体验的场景运用的比较多,并且也会在业界一些常用动画库的基础之上进行开发(比如pixiJS,ThreeJS等)。

目前,小程序对WebGL/Canvas动画支持不是特别完善(比如:canvas过多调用draw进行绘制点和线,会出现延时),再结合当前的业务,一码多端项目中动画场景我们一般都是采用过渡动画和帧动画来支持。


05
适配层



适配层主要作用是标准化抹平不同环境的功能差异,降低业务逻辑和环境功能的耦合性,提高业务逻辑的可重用性和开发效率。如登录、埋点、跳转、网络请求等功能在不同运行环境的调用方式和传递的参数并不完全相同。如果业务开发人员通过process.env.TARO_ENV环境变量自行适配,这样将存在一定的工作量,并且当环境底层能力重构时,随之而来的可能是成百上千处改动,那将是一场灾难。为此我们封装了一个适配层,将一些常用的基础能力都纳入到了适配层进行统一适配,业务开发人员无需再关注各环境基础能力的差异,这样不仅可以提高开发效率,同时也易于维护和扩展。


图片


通过梳理过往互动项目的常见运行环境,我们归纳总结出以下三类需要适配的环境:


跨端环境

对于跨平台开发,Taro给出了一套统一的编译时解决方案,开发者通过内置环境变量(process.env.TARO_ENV)或采用不同文件后缀的方式,可以将两端不一致的基础能力进行抹平。详情可以参考Taro官网的跨平台开发章节的文档,这里就不再多做赘述了。以下是我们适配的能力:

● lbs获取

● 消息订阅

● 风控参数获取

● 音视频播放

● 埋点


跨小程序环境

在京购、赚赚、哥伦布小程序中,相同的基础能力实现方式存在一定的差异,导致调用方式有所不同。三个小程序基础能力的主要差异如下:

登录

获取cookie

网络请求

跳转首页、店铺、商详、H5

我们借鉴了Taro处理跨平台的方式,创建了一个新的环境变量APP_RUNTIME_PLATFORM_ENV,它类似于process.env.TARO_ENV,在Taro在打包编译阶段,会移除不属于当前环境编译类型的代码,只保留当前环境下的代码,达到适配的效果。


06
工程化



工程化主要是为了提升开发效率,辅助开发人员快速地完成一些非开发相关事宜,避免过多的占用开发人员宝贵的工时。以下是目前一码多端工程化主要提供的功能:


mock环境

为了最大程度地贴近真实数据下发,我们摒弃了ajax拦截或者硬编码的mock方案,采用了在Taro框架中加入了mock插件的方案,mock插件会在本地拉起一个node服务,并按照规则将当前项目mock文件夹中的文件添加到路由中,开发人员可按需在mock文件夹中添加相关接口的mock数据,通过npm run mock命令即可拿到对应接口的mock数据进行开发。这样一来,我们无须等待后端接口开发完成,就可以完成部分联调工作和异常边界场景的模拟,这样使得前后端从并行开发过渡到联调开发更加顺畅,提高开发效率,保证开发质量。


环境变量

环境变量可以作为运行时的一种常量,但有别于普通的常量,它在不同环境中的取值可以不同,这其实是在打包编译时根据环境参数动态替换代码中环境常量的值。比如接口服务地址,我们采用环境变量来设置,在mock环境中,服务器地址设置为本地的mock服务,在测试环境中设置为测试环境的网关,在生产环境中则设置为生产环境的网关,当然还可以区分其他的环境,比如H5环境和各小程序环境(京购、赚赚、哥伦布)中携带登录信息的方式也可以环境变量设置。另一方面,环境变量也可以作用于编译时,对不同环境的编译提供差异化的能力,例如对于小程序的打包产物,我们通过脚本自动拷贝到了对应的小程序中,但是不同的小程序相对当前项目的路径并不相同,此时我们就可以将目标小程序的路径定义为一个环境变量,根据不同的编译时环境设置不同的目标小程序路径。环境变量可以同时设置所有环境对应的值,在编译时却仅采用当前环境的值,无需手动切换,避免人工操作失误导致bug或发生线上事故,提高开发交付的稳定性。


其他常规功能

为了减少图片的请求次数,我们可以将一些图片合成的雪碧图,但是制作或更新雪碧图时,较为复杂和繁琐。为此,我们开发了自动合成雪碧图功能的打包插件,开发人员只需要将需要合并的雪碧图按照规则放入对应的文件夹,在打包构建时,自动合成雪碧图插件会按照一定的规则合成雪碧图,同时合成后的雪碧图也会自动上传到cdn,最终插件生成的雪碧图样式可以覆盖原有样式,达到替换的效果,整个过程都由插件完成,高效稳定,可以节省雪碧图的制作和开发成本。此外,框架中也集成了代码风格规范、git提交规范及H5兼容性处理等通用的工程化能力,提高沟通和协作效率。


07
疑难杂症



一码多端引擎的建设并非一帆风顺,也遇到了一些棘手的问题,以下是我们遇到的几个比较有代表性的问题:


跨页面组件样式不生效

公共(跨页面)组件在部分页面中会出现样式失效,导致组件无法正常渲染。最终通过研究,我们发现是由于Taro打包时,将所有跨页面组件的样式都统一提取到了common.wxss中,common.wxss最终会被app.wxss引入,app.wxss在Taro运行时只会执行一次,导致非首次打开的页面common.wxss都丢失了。最终我们通过编写Taro打包插件,将common.wxss在各页面的wxss中都进行了引入,插件主要代码如下:


图片


跨页面模块功能异常

对于有数据存储的公共(跨页面)模块或组件,偶尔会多出一些脏数据,导致功能异常。这是由于Taro打包时,会将公共模块或组件提取到common.js中,而common.js只会被app.js引入一次,如果采用单例模式管理公共模块或组件的数据,导致现页面之间共享一份数据,相互污染。最终,采用了Class方式重写了单例模式的公共组件,在各页面中进行实例化,从而达到公共组件的数据在各页面之间互不影响。


部分环境打包异常

代码一模一样,有的环境能跑起来,部分开发者的环境一直报“Cannot convert undefined or null to object” 或 “Cannot find module 'is-interactive'”错误。通过排查对比发现,Taro(3.3版本)对项目文件和所处路径有一定的要求,项目文件不能以taro结尾,项目放置的路径中不能包含中文。



总结




业务孵化技术,技术服务业务。结合互动业务特色及多端环境差异,逐步完善通用模型、适配层、模块化及工程化等功能。目前一码多端引擎已经支持了满天星、万人团、好玩节等重要的互动项目,在开发效能上有显著的提升(约30%),同时我们也发现一码多端引擎还存在一些问题需要解决,未来我们会进一步深入分析一些特定的互动业务场景,沉淀更多的业务模块,不断完善框架建设,快速支撑业务发展,在满足业务需求的同时,为团队提供更优的开发体验,提高开发效率和稳定性。

继续滑动看下一个
京东零售技术
向上滑动看下一个