与JDReact的第一次亲密接触 ——加油卡项目总结

avatar
UX @京东

JDReact 平台是在 Facebook ReactNative 开源框架基础上,进行了深度二次开发和功能扩展。不仅打通了 Android/iOS/Web 三端平台,而且对京东移动端基础业务能力进行了 SDK 级别的封装,提供了统一、易于开发的 API 。基于以上种种优势,加油卡壳牌业务决定使用 JDReact 技术进行开发。

由于这是我们团队首次使用 JDReact ,在开发过程中遇到了很多问题:不熟悉 JDReact 提供的 API 、如何调用后台数据接口?怎样切换测试、预发环境?开发完后怎样打包?如何上线?等等每个流程都不熟悉。因此,在完成项目之后,很想做一个总结,从前期准备到最终项目上线的整个流程做一个分享,希望能为初次使用 JDReact 的同事有所帮助!如有错误,还请多多指教!

1 前期准备

1.1 项目背景

加油卡壳牌业务是京东APP中的一个项目,可以从首页的「充值缴费」入口进去,如下视频演示:

Video Player下载文件

1.2 项目环境配置

1.2.1 开发分支以及文件结构

初始项目中只包含一个 dev 分支,最后打包 APP 时会从该分支中抽取代码,所以我们新建了一个 trunk 分支,平时在该分支上进行开发,最后打包 APP 的时候再切换到 dev 分支,合并代码。

对于文件结构,如上图所示,其中 Navigation.js 设置页面路由信息,以及打开项目时进入的默认页面;pages 文件夹下存放项目的每个页面代码;images 存放项目开发中使用到的图片;components 文件夹下存放页面中用到的各个组件。

1.2.2 安装依赖环境

按照 JDReact 团队给出的安装依赖环境的步骤,当执行 npm start 命令之后,理想状态下会出现下面的提示:

很不幸的是,有时候会出现如下警告:

根据提示,可以看到打开 .babel.json 文件的时候出错,其实如果忽略这些警告,也可以打开京东 APP 进行调试的,但是如果想解决这个问题可以参考下面的步骤:
找到C:\Users\用户名.babel.json文件右击——属性——安全——SYSTEM的权限全部改为允许(如果已经改过权限,可以来回切换一下),重新执行 npm start 就可以了。

1.2.3 安装 React 语法高亮插件

项目是在 sublime 中开发的,但是在编写 JDReact 代码时,整个代码的高亮是混乱的,如下图所示:

因此,需要安装 Babel Snippets 插件来解决高亮问题,该插件可以使 React.js、JSX 语法代码高亮。安装方式:在 sublime 中,ctrl+shift+p 打开面板选择 Install Package ,输入 babel Snippets 点击安装,安装完毕后再次 ctrl+shift+p ,选择 set syntax JavaScript(babel) ,则 React 语法代码可以高亮,方便我们接下来的开发。执行完毕后效果如下图所示:

1.3 在无线持续构建平台打包项目

手机安装 debug 版本的京东 APP 之后,可以在手机端实时看到开发的代码效果,那么如何生成 APP 呢?因为构建平台会抽取项目中 dev 分支的代码,所以如果代码有所改动,先把 trunk 分支代码合并到 dev 分支。整体思路是把业务代码以插件形式打包进JDReact,然后再打包包含该插件的 APP 。打包流程如下:

对于 Android 版本,首先打开无线构建平台网址 ,选择一键打包—— JDReact,依次选择插件名称——平台选择(android)—— dev 分支——点击开始构建。

打包好JDReact后,再打包京东 APP:

选择京东商城—— android ——其中 debug 版本可以晃动手机进行调试,release 版本不可以调试,近似于真实环境,所以如果平时开发调试的话选择 debug 版本——选择 dev 分支——开始构建。

一般来说 Android 版本的打包过程还算顺利,但是 IOS 版本的打包过程就让人心碎了,下面我们来聊聊 IOS 版本:

首先仍旧是插件的打包,类似 Android 版本的打包过程,对于 IOS 不同的是版本选择 IOS ,分支选择 dev 点击开始构建。

IOS 的插件构建完成之后,类似的在京东商城选择 IOS——这里的 IOS JRdebug 版本是用来调试的版本,遗憾的是我们多次下载 JRdebug 版本在手机端总是打开之后闪退,还好 releaseP 版本也可以进行调试,虽然不是很方便,但也可以解燃眉之急:

1.4 手机安装APP

按照上述过程生成 APP, Android 版本的 APP 和 IOS 版本的 APP 接下来的步骤有所不同:对于 Android 版本:直接扫描构建平台——历史版本生成的二维码,安装京东 APP ,注意下面的步骤

打开 JDReact 调试模式;

如果使用的是真实环境,还需要在服务器设置中取消选择“前两项”,测试环境需勾选“前两项”;

每次安装完新的 APP 之后,一定要记得晃动手机,dev Seetings——设置 Debug server host & port for device 这个选项端口8081,如果报错 could not connect to development server ,需要检查一下防火墙是否打开了 8081 这个端口:控制面板——windows防火墙——高级设置——入站规则——新建规则——端口——特定本地端口:8081——选择允许联机连接——一直点击下一步。

对于 IOS 的 releaseP 版本:需要在构建平台——历史版本中找到打包好的 ipa 后缀文件,使用 itoos4 软件安装到 iphone 手机,该过程需要注意的是:

电脑端开启 npm start 服务(同样保证防火墙对端口 8081 的不限制);

手机连接电脑的 wifi ,手机浏览器登录跳转协议页面:

在列表输入框的链接改为自己电脑的 ip 地址和端口,文件名字改为自己的项目名,点击 React 按钮打开。

如果是真实环境,打开手机 APP 时,选择 api.jd.com 的环境,否则选择测试环境;

每次改动代码之后,需要退出后台的京东 APP ,然后先打开 APP ,选择 host 环境,最后在跳转协议列表页面重新用“ React 打开”按钮打开项目。

2 项目开发中的问题

在项目开发过程中遇到了很多问题,这里总结了几个具有代表性的问题:

2.1 JDImage 加载图片问题

使用 JDImage 组件加载本地图片和线上图片的方式是不一样的,如果不加注意很容易把两者混淆:

线上的图片直接使用uri:

<JDImage source={{uri: 'http://xxx.jpg'}} style={styles.img0} />

加载本地的图片使用的是require:

<JDImage source={require('./images/xx1.jpg')} style={styles.img1} />

并且每次新增本地图片,控制台都会报错:…Unexpected character ‘◆'(1:0) at …

这是因为新增加的图片必须重新npm start。

2.2 通过路由完成页面之间的跳转

由于文档不全,路由之间如何传递参数,如何获取参数没有说明,导致在这里也花费了不少力气。对于页面之间的跳转,我们刚开始使用 JDRouter 中的 resetTo 方式进入某个路由:

this.context.router.reset([
   { routeName: 'index', params: { title: 'apis' } },
   { routeName: 'disCountPage', params: { datas: this.props.discountData } }
], 1);

则下一个页面获取路由参数的方式是:

this.context.router.getCurrentRoutes()[1].params.title;

随着项目开发,发现这样有个问题,例如从 A 页面跳转到 B 页面,如果从 B 页面回退到 A 页面,A 页面之前的操作状态会全部清空,因此,为了保留 A 页面的状态,改为使用 push 方式进入某个路由,用popToWithProps 返回上一个路由,这样的方法可以保留原页面的状态,但是如何获取路由携带的参数,文档中则没有介绍,经过多次尝试,我们发现需要这样设置:

A 页面通过 push 跳转到 B 页面并传参:

this.context.router.push({ routeName: 'B', props:
{ datas: this.props.discountData }});

B 页面获取参数方式:

this.props.datas.data
,B 页面返回上一页面使用 popToWithProps,并传参:
this.context.router.popToWithProps('A',{trandatas:null});
 。A 页面获取参数方式:
this.context.router.getCurrentRoutes()[0].params.trandatas

2.3 修改底层组件

诚然 JDReact 底层封装的组件可以满足项目开发中的大部分需求,但是有些特殊需求,暂时不能满足,比如加油卡项目中设置密码的弹窗,设置按钮的字体在不同状态下要有对应的颜色,

该密码弹窗组件是在 JDConfirmDialog 组件的基础上开发的,但是该组件并没有提供确定按钮位置样式的变化,于是我们找到底层封装 JDConfirmDialog 组件的目录node_modules/@jdreact/Libraries/JDConfirmDialog/index.js文件,为了不影响底层组件,我们复制出该文件,在

<JDText style={styles.confirmText}>
{this.props.confirmText}
</JDText>

的位置增加可以传入设置文本的样式 textStyle:

<JDText style={[styles.confirmText,this.props.textStyle]}>
{this.props.confirmText}
</JDText>

这样就可以在组件外面设置按钮的样式了。

2.4 JDNetwork使用方法

  • 在调取后台数据的时候走了很多弯路,首先是无法获取到后台发出的数据,debug 版本的京东 APP 默认的 host 是 api.m.jd.com.care ,后端使用 JSF 杰夫服务平台提供数据,前端使用 JDNetwork 组件给出的API:fetchWithoutHost 却无法调取后台数据,在此过程中,为了打通接口多次和不同团队进行了沟通咨询,最后发现,JSF 提供的数据前端无法直接使用,后端研发还需要再进行解析封装。
  • 本以为数据接口只要调通了一个,其他的接口就问题不大了,然而却发现有的接口可以调通,有的接口却总是抛出错误,经过排查发现,只要后端返回的字段 code 不等于 0,就会抛出异常。这是怎么回事呢?经过询问才知道 code 作为保留字段,必须为 0 ,而不能作为业务含义(比如 code 表示用户的状态等等),否则一旦不等于 0 ,就表示 API 网关返回异常。
  • 项目在测试环境中测试的差不多了,需要放在预发环境上,然而如何切换到预发环境上呢?需要配置 host 吗?需要修改代码吗?答案是不需要修改代码,从底层封装的组件可以看出我们使用的fetchWithoutHost ,其封装的 host 是 api.m.jd.com ,和 beta-api.m.jd.com , 分别对应着正式环境和测试环境,JDNetwork 写一套代码就可以。

对于 Android 版本的 APP:在 debug 的客户端中选择设置—— debug 配置——服务器设置——如果勾选前两项就是测试环境 api.m.jd.care(经过咨询得知 beta-api.m.jd.com 和 api.m.jd.care 都对应着测试环境,区别只在于 beta-api.m.jd.com 支持 https ,而 api.m.jd.care 这个网关是不支持 https ),如果去掉勾选前两项,host 就是 api.m.jd.com ;

对于 IOS 的 releaseP 版本则需要在点击“ React 打开”按钮启动 APP 时选择需要的 host 环境。

2.5 JDtext 换行问题

JDreact 组件开发的样式布局和常用的 H5 开发样式布局,多少还是有所不同。例如,项目中用户勾选协议部分如下图所示:

因为黑色字体使用 <JDText> 组件包裹,红色字体点击需要出现弹窗,所以红色字体部分需要使用点击组件 <JDTouchable> 标签包裹,查看 <JDTouchable> 底层代码可以看到里面是用 <View> 标签搭建的组件,所以 <JDText> 生成的黑色字体(行内元素)和 < JDTouchable> 包裹的红色字体(块级元素)无法像上图一样,只能分成多行显示:

这显然无法满足样式的需求,解决方法是红色的字体部分也使用 <JDText> ,点击事件放在 <JDText> 上面,并且用上一级的 <JDText> 嵌套,代码如下所示:

<JDText style={{fontSize:JDDevice.getFontSize(24)}}>请您确认京东将收集您上述信息实现本服务,本服务约定请详见<JDText onPess={this._lookdeal}
style={{fontSize:JDDevice.getFontSize(24),color:'red'}}>
《壳牌加油卡充值协议》
</JDText>
</JDText>

这样就达到了我们想要的样式。

2.6 组件之间的层级问题

按照最初的构思,每个功能块作为一个组件进行封装,如上图所示,“电话输入框”组件和“选择面值”组件是兄弟关系,其中“电话输入框”包含了历史号码列表(如右图所示),这里把电话输入框设置为相对定位,历史列表设置为绝对定位,在 Android 系统下历史列表可以正常显示,但是在 IOS 系统下,原本在“选择面值”组件层级之上的历史列表却被“选择面值”组件覆盖掉了,为了兼容两个系统,把“选择面值”组件合并到“电话输入框”组件之中,这样,在同一个组件中,确保了历史列表的层级就在“选择面值”之上,从而可以在两个系统中正常显示了。

2.7 Context 的使用

在项目中,每个组件都需要获取用户登录信息,如果每个组件都调用获取用户的登录信息,显然是冗余的,那么有什么方法可以优化吗?我们可以在父组件中获取一次用户登录信息,然后分发到每个组件中,这样无疑减少了很多重复,但是从父组件传递到后代组件,数据层层下传也会造成很多的麻烦。如果有方法可以把数据从父组件直接下传到后代组件就好了,这时 Context 就派上用场了。例如需要把顶层组件的 color 数据传递到底层组件 Button 中,结构示意图如下:

Context 犹如在顶层组件和后代组件 Button 之间打开了一条直通大道,可以从父组件中把数据直接传递到后代组件而不需通过中间组件传递,其使用方法简化如下:

(1)首先在顶层组件 ParentInfo 中定义 color 的类型:

ParentInfo.childContextTypes = {
color: React.PropTypes.string
};

定义顶层组件所拥有的子类 Context 对象——该顶层组件所拥有的子类 Context 对象为 color ,且必须为字符串类型。

然后通过 getChildText 方法,来给子 Context 对象的属性赋值:

getChildContext() {
return {color: "red"};
}

这样就完成了顶层组件中,Context 对象的赋值。

(2)越级传递,因为 color 属性只在最底层使用,在一级子组件(图中的中间子组件)中并没有直接用到,因此我们可以直接传递到最底层(越级),在 Button 组件中使用。

首先 Button 组件中,再次声明了所接受到的 Context 的子组件 color 类型,声明必须为字符串:

Button.contextTypes = {
color: React.PropTypes.string
};

然后可以通过 this.context.color 这种方式调用:

<button style={{background: this.context.color}}>子组件</button>
 。

综上所述,我们通过 Context 就能实现值的越级传递。从而简化了逻辑和代码复杂度。

3 调试

关于手机 APP 调试代码的具体步骤,详见 1.4。使用 debug 版本的 APP,晃动手机可以出现调试的选项,这里我们解释一下常用的几个功能:

  • reload 手动重新加载;
  • debug js remotely 可以在手机连接的电脑上打开浏览器,按 F12 可以打开调试模式,方便看到log出来的数据;
  • Enable live reload 和 enable hot reloading 虽然可以在修改代码后保持热更新,但是由于我用到的测试机反应太慢了,这个功能没怎么用到,不得不一直晃动手机手动 reload ;
  • Debug server host & port for device 设置手机的 ip 地址和端口

4 上线

上线流程其实按照 JDReact 团队给出的发布规范走就可以了,作为前端的工作也就是要压缩图片,保证生成的升级包不要大于 1M。升级包由 JDReact 团队上线,后端研发部署 H5 页面,上线完毕后,测试人员在无线持续集成平台的历史版本中下载安装相对应测试版本的 Android 和IOS 版本进行测试。在这个过程中要注意的是:

安装好客户端之后,需要把系统时间往后调整30分钟才能看到效果,一般上线往往延迟到凌晨左右,如果跨夜上线的话,不但要往后修改30分钟,一定要注意还要往后修改一天日期。 例如系统时间为11月4日23:40,需要将时间改为11月5日00:10。

集成平台上的安卓版本目前分为 dev 分支、master 分支、发版分支。其中 dev 分支和 master 分支都是从 git 库中的 dev 分支上抽取代码的,而每次客户端升级的发版分支是从 master 分支上抽取代码的,所以上线测试完成之后,还需要在集成平台上打包 master 分支,保证该分支是最新的代码,这样下次客户端升级才会包含本次上线的业务代码。

值得注意的是每次客户端升级之后对应的 git 库都要新拉出一个对应版本号的分支,如果有问题,可以在该分支下修改代码,然后在集成平台上对应的发版分支上抽取代码,因为此时你再修改 git 库中的 dev 分支无法影响发版分支;换句话说,分为两种情况:

  • 在集成平台上发版分支创建之前,master 分支已经包含了你的业务代码,发版分支就会从 master 中抽取代码,这样就包含了你的业务;
  • 在集成平台上发版分支创建之后,如果在发版客户端中发现你的业务出了问题,需要在 git 库中对应的发版分支下修改问题,然后直接打包到发版分支上去,因为此时发版分支已经不再从 master 分支上抽取代码了,所以修改完git库中的发版分支代码之后,还需再次打包 master 分支,这样才能保证下一次发版分支从 master 分支中提取到你的业务代码。

综上所述,你需要做的如下所示:

  1. 对于之前已经上线的客户端,你需要生成升级包,由JDReact团队上线;
  2. 然后更新集成平台上的master分支,保证之后版本的客户端包含你的业务;
  3. 每次客户端更新之后,你需要在git库中新拉一个对应版本的分支,修改完毕后,将其打包到正在灰度的发版分支上;
  4. 最后再次更新git库中dev分支,更新集成平台中的master分支;

IOS 系统和 Android 系统类似,IOS 系统目前打包到 dev 分支(之后可能会有改动),这样才能保证以后每次的新版本发布,带上此次开发的项目。

5 总结

加油卡壳牌项目是我们团队第一次使用 JDReact 技术来开发,从开始的一头雾水,到最后的项目上线,中间遇到了许多的困难与迷惑,一次次求助 JDReact 团队,一次次联系无线持续集成平台团队,一次次询问杰夫服务平台团队,加班加点的终于完成了项目的上线,由于时间紧迫,项目还有许多需要优化的地方,我们后续也会迭代优化,但一路走来,收获颇多,感慨颇多,实践才是夯实知识的最好的方法!最后感谢在项目开发过程中不厌其烦的帮我解答疑问的同事们,感谢你们的无私付出,谢谢!