部落详情页RN化(ReactNative实现高度不确定的评论类列表)
问题及思路
如上视频所示,上述效果里,有很多的无法确定字体数量及高度的回复类消息,此类列表消息,通过FlatList等控件,实现后的效果会很差。
针对此问题的优化思路:
- 使用recyclerlistview控件替换flatlist控件,实现对Item的复用
- 提前通过Native计算Text的高度
具体实现过程
框架搭建
框架结构:
- 依据官方文档,基于0.57.8创建ReactNative项目
- 安装依赖库:
- 状态管理:(注意:要安装对应版本,不能安装最新版本)
- redux-3.7.2(4.0.1不会起效果)
- react-redux-5.0.7(6.0.0,7.0.0会报错)
- 中间件:
- 异步:redux-thunk
- 日志:redux-logger
- 路由:react-navigation
- Prop类型:prop-types
- 状态管理:(注意:要安装对应版本,不能安装最新版本)
- 目录结构,并实现基础框架能力:入口,Route,Redux,中间件等等。
recyclerlistview
详情页整体是一个列表,可选择的列表控件有:ListView,FlatList,RecyclerListView。其中RecylerListView的性能最好,其灵感来源于Android-RecyclerView和iOS-UICollectionView的实现思路。
recyclerlistview的使用很简单,主要是三个属性:
- dataProvider:数据源
- layoutProvider:指定Item的type,同时指定对应Item的width和height
- rowRenderer:具体Item的render
recyclerlistview高性能的原因:
- 对View的复用
- 通过提前得到的Item宽度和高度,当快速滑动时,只绘制显示区域的内容
对于无法提前准确预估Item高度的情况下,通过forceNonDeterministicRendering=true,会通过实际高度进行纠正,当预估值与实现值差距较大时,对性能影响很大。
具体的代码:
1 | export default class BaseDetail extends PureComponent { |
对应Item组件的模板代码:
1 | import React, {PureComponent} from 'react'; |
Item: UserInfo
数据
1 | { |
效果
实现
通过Item模板,创建UserInfo组件类,通过Flexbox布局即可实现,注意两个细节:
- 圆角图片:通过css属性roundAsCircle即可实现
- 按钮:ReactNative的Button组件的定制能力很差
具体代码:
1 | import React, {PureComponent} from 'react'; |
图文混排控件:FacialText
数据
1 | { |
效果
实现
在RN实现图文混排,主要是通过Text里的嵌套功能:
1 | <Text> |
主要的工作量是在对传入的文本进行转换处理,把传入的文本字符串,转换为数组,具体代码如下:
1 | import React, {PureComponent} from 'react'; |
Item: Content
数据
1 | { |
效果
实现
此文本控件非常简单,注意点:
- 需要在文本控件上嵌套一层View,原因:用于使用onLayout时,获取的是其实际高度。
- 预估的高度很不准确,改进思路:
- 通过js的方式,通过文本预估实际高度
- 通过module,由native提前预估实际高度
1 | import React, {PureComponent} from 'react'; |
Item: PictureArea
数据
1 | { |
效果
实现
图片控件的布局很简单,关键点:
- 通过图片的长宽比,屏幕的宽度,计算出实际高度
- 图片的圆角,通过css的borderRadius属性实现
1 | import React, {PureComponent} from 'react'; |
Item: Related
数据
1 | { |
效果
实现
此控件实现非常简单,直接上代码:
1 | import React, {PureComponent} from 'react'; |
Item: Interactive
数据
1 | { |
效果
实现
关键点:
- 叠加布局的实现(相当于Android里的相对布局):
- 通过position:’absolute’,实现叠加
- 通过相对于parent的top, bottom, left, right来实现定位
- Android里的Shape的属性stoke和corners的实现方案:使用css的borderWidth,borderColor,borderRadius属性
1 | import React, {PureComponent} from 'react'; |
Item: SectionGap
数据
1 | { |
效果
实现
实现非常简单,高度由数据控制:
1 | import React, {PureComponent} from 'react'; |
Item: ReplyTitle
数据
1 | { |
效果
实现
效果简单,直接帖代码:
1 | import React, {PureComponent} from 'react'; |
Item: Replay
数据
1 | { |
效果
实现
此控件的效果实现不难,由于回复文字高度不确定,无法提前预估整理高度,其实现代码:
1 | import React, {PureComponent} from 'react'; |
RecyclerListView自动纠错
上面的代码实现完后,有两个问题:
- Content组件的高度无法正解预估
- Reply组件的高度无法正解预估
利用RecyclerListView的forceNonDeterministicRendering=true属性,可以实现实际渲染后,自动纠正。
使用forceNonDeterministicRendering=true的注意点:
- 默认Item的width不是fill_parent,需添加flex:1
- 预估值可以通过实际绘制成功后,通过onLayout回调纠正预估值 —– 经测试后,作用不大,无法纠正首次显示
1 | import React from 'react'; |
提前计算Text的高度
提前计算Text的高度有两种方案:
- Js计算
- 通过Native计算
这里主要通过Native计算的方式:
- 安装开源库react-native-text-size,添加计算Module
- 在给recyclerlistview设置数据之前,遍历计算data里的text的高度,并保持到data里
- 图文混排的文本计算方式:因为图片的显示大小与单个汉字的显示大小一样,只需要把表情符替换为一个汉字就行
整体效果还是很不错,但还是有两个问题:
- 预处理数据,导致白屏时间较长
- Native计算的结果与实现的绘制还是有一些误差,误差比较,在接受范围内
- 针对误差的改进办法:利用onLayout的回调,纠正提前预估值。经测试,此方法不推荐,原因如下:
- 要超过一定的范围时,才进行纠正,不然滑动其间会有抖动
- 极速滑动时,onLayout的回调结果也不一致 —- 这个无法解决
1 | import React from 'react'; |
总结
开发中遇到的问题
- 每次重新加载,都需要拿起来摇一摇
- 只有调试状态,才能看到console.log的日志
- 调试时,需要把ip地址改为localhost
- 基于0.57.8的ReactNative项目,依赖第三方库时,要选择对应版本,不能直接通过npm install xxx安装
- 颜色区别:Android里是ARGB,RN里是RGBA,不能直接复制android的颜色值
性能上遇到的问题
- 当非常快速滑动时,虽然会快速显示出内容,但还是会看到白屏,无法与纯Native开发的效果对比
- 当js与native通过jsbridge频繁交互时(如通过native埋点交互),js的帧率会下降的很快
思考
- 为了提升发性能,应尽量减少js与native的交互,如减少交互频率,交互数据量等等
- 对开发效率与性能问题应整体思考:
- 学习Expo,完善脚手架:
- 提前包含第三方的依赖库,解决依赖版本的问题
- 支持状态管理,路由支持,Prop类型支持
- Component的模板
- 学习Expo,实现界面化开发工具,具备如下功能:
- 显示关键信息:
- 自动显示issues信息
- 非调试模试下,显示console.log()的日志
- 显示metro的编译信息
- 显示测试手机信息
- 通过二维码扫描,打开RN页面,不用手动查IP再输入
- 可切换到生产模式,方便测试生产模式的效果
- 直接与发布平台对接,可直接进行发布,同时发布时,自动把图片等资源上传到CDN(通过固定资源的目录,如assets目录实现)
- 显示关键信息:
- 学习Expo,实现手机的工具功能(iOS-工具页面,android-通知栏):
- 通知栏支持Reload与当前的bundle项目名称
- 同步显示bundle的编译进度,同时显示编译结果
- 提供Module与ReactNative原生组件与自定义组件的Demo页面
- 学习Expo,优化文档平台和跨平台
- 文档融合ReactNative对应版本的文档,如Module,View等等
- 文档上的Demo,可支持二维码扫描后,直接运行
- 实现支持跨平台的基础组件,高级组件由基础组件实现
- 学习Expo,支持两套ReactNative版本,减少版本升级的影响,实现平稳过渡
- 学习Expo的异常处理流程:
- dev状态:出错后,直接显示红色的出错页面
- prod状态:
- JS使用Sentry来捕获js的异常 —- 重点
- 重大js异常:出错后,重新reload;reload还出错,显示出错页面,让用户手动reload
- 学习Expo,完善脚手架:
结论:
- 要追求极致体验,还是Native最合适,只有当ReactNative像Flutter一样,真正改变交互方式,不要过渡依赖jsbridge,才会有比较大的改善
- ReactNative的开发效率比Native要快很多,基于MVVM的组件化开发,比Native的开发方式更加合理