探索react native首屏渲染最佳实践
文 / 腾讯 龚麒
0.前言
react native给了我们使用javascript开发原生app的能力,在使用react native完成兴趣部落安卓端发现tab改造后,我们开始对由react native实现的界面进行持续优化。目标只有一个,在享受react native带来的新特性的同时,在体验上无限逼近原生实现。作为一名前端开发,本文会从前端角度,探索react native首屏渲染最佳实践。
1.首屏耗时计算方法
1.1我们关注的耗时
优化首屏渲染耗时,需要先定义首屏耗时的衡量方法。将react native集成至原生app中时,可以将首屏耗时定义为如下
首屏耗时=react native上下文初始化耗时+首屏视图渲染耗时
其中,react native上下文初始化耗时为一个固定开销,通过将初始化过程提前至app启动后异步进行,在安卓端,这一耗时已经可以降低到约70ms。本文关注的是首屏视图渲染耗时,文中优化探索是在安卓端react native结合版app中进行,但其思路和方法,可以复用至iOS端。
1.2渲染耗时衡量方法
关注首屏视图渲染耗时,需要理解react框架视图渲染流程,相应的需要了解其生命周期方法。下图是一张react组件完整的声明周期图,从图中可以看出,上方虚线框内为生命周期第一阶段,这个阶段完成初始化,并第一次渲染组件。左下角虚线框为组件运行和交互阶段,这里可能会再次渲染组件。右下角虚线框为第三阶段,组件这一阶段卸载消亡。
对应上述生命周期方法,我们在初始阶段首先渲染loading视图,并开始拉取数据。获取数据后,通过改变状态(state),触发视图的再次渲染,在屏幕绘制出视图。
结合上述分析,我们将首屏视图渲染耗时定义为如下
首屏视图渲染耗时=compontDidUpdate结束时间 – compontWillMount开始时间
2.开启渲染优化探索之路
2.1输出当前渲染耗时
既然已经明确了视图渲染耗时的计算方法,那么就可以打时间点日志输出这一耗时看看。这里查看耗时有两种方法。
使用调试模式,通过console.log,在chrome调试工具中输出时间差值
封装native层日志方法封装给react native层调用,将日志输出到app日志文件中。
推荐使用第二种方法,采用这种方法,可以以release模式运行app,输出结果与我们普通的安装运行app表现一致。
这时我们的耗时输出是怎样的呢?查看日志得知,在wifi环境下,荣耀4X手机上,渲染耗时为约700ms,加上react native上下文初始化时间约70ms耗时,整个界面的耗时需要约770ms。
那么原生app的实现中,发现tab渲染耗时是多少呢?通过打点发现,安卓端原生app实现中,发现tab从onCreateViewEx开始至onResume结束,耗时约100ms。
2.2来吧,加上缓存
看到上述数据时,我的内心是有点崩溃的,说好的高性能呢?但是我并不慌,根据我不多的前端开发经验,我知道有一个大招还没有用上,那就是使用缓存数据。
react native为我们提供了AsyncStorage模块,AsyncStorage是一个简单的、异步的、持久化的Key-Value存储系统,它对于App来说是全局性的。相对于之前我们使用LocalStorage存储数据,在react native端,我们可以使用AsyncStorage作为数据存储解决方案。
在使用缓存之前,我们的视图渲染流程如下所示
上述流程中,每次渲染界面视图前,都需要等待网络请求。这里我们将请求数据缓存起来,渲染界面视图时,首先使用缓存数据渲染视图,同时发起网络请求,数据返回时再以新的数据渲染一遍。使用缓存之后的渲染流程如下图所示
上述流程中,当有缓存数据时,我们会快速拿到缓存数据渲染出界面视图。然后在网络请求返回数据时,再次触发render方法,以新数据再次渲染视图。
这里为了提升性能,避免多余的触发render方法,可以在shouldComponentUpdate方法中判断cache data和response data差异,仅当两份数据不一致时才再次触发render方法。同时,得益于react 框架的虚拟dom特性,在网络请求的数据返回再次触发render方法后,react native会计算dom diff并以此为依据来判定视图更新范围。
此时通过日志数据可以看到,在有缓存场景下,我们获取缓存时间耗时约在40ms,此时我们的渲染耗时下降至400ms。
2.3接管轮播图
加入缓存优化后,我们将渲染耗时降低至400ms,但是仍与原生实现中耗时有较大差距。此时的优化进入了深水区,经过梳理界面视图,可以将视图划分为上部轮播图、中间部落列表和下部热门帖子三个模块,逐一优化。
在最初的版本中,我们是这么实现轮播组件的:在请求的返回结果中,取出轮播图集合,依次生成全部图片视图,并添加至轮播图容器视图中。最后监听容器视图的对应事件,设置当前显示的图片视图。
如下图所示,当结果数据中共有五张轮播图时,会有五个图片视图被添加至轮播图容器中。初始时仅首张图片视图可见,容器内滑动事件发生时,图片视图可见状态随之改变。
从上述过程可以看出一个明显的问题:对首屏来讲,轮播图容器内,非可见状态的其余图片视图的渲染是没有意义的。
既然已经找到了问题,优化是思路也就很自然:在从请求结果中获取轮播图集合后,不是生成全部图片视图,而是仅生成一份,将这一个图片视图添加至容器内,并为他设置当前图片的url地址。当容器内滑动事件发生时,不再是切换不同视图的可见状态,而是复用这一个图片视图,切换视图图片的url地址,其流程如下图所示。
采用这一方案,完全去除了非可见状态的图片视图的渲染耗时,同时,通过复用视图,也降低了对内存的占用。这一方案在理论上将渲染耗时和内存占用做到了最优,然而在实际运用中,会有一个体验的问题:当滑动发生时,才加载下一张图片并刷新图片视图,会导致容器内滑动事件的卡顿,使得滑动有阻滞感。因此我们最终采用了一种折衷方案:当轮播图集合超过三张时,在容器内加入当前图片视图,同时提前加入当前图片的上一张和下一张图片视图。容器内滑动事件发生时,复用这三个视图,来设置这次事件对应的当前图片、上一张和下一站图片视图。
通过日志输出优化前后的耗时数据,采用这一方案时,在渲染耗时上降低了约30ms,此时我们的首屏渲染耗时下降到了约370ms。
2.4为列表加特效
在发现tab界面中部,是子类别的部落列表。子类别数量通常是两个,每个子类别下一般有七至八个列表项,每个列表项由部落图标和部落名称组成。
最初的版本中,我们从请求结果数据中获取子类别下部落集合,以ScrollView为容器,依次创建列表项视图并添加至容器中。这样,当所有列表项渲染完成,该子类的部落列表才渲染完成,最初的实现示意如下所示。
通过上述过程可以发现,在部落列表渲染过程中,等待非可视区域的列表项的图片下载再渲染该视图,对于首屏是没有意义的。
结合对轮播图组件的优化经验,我们尝试延迟加载列表项视图。理想的状态是在首屏仅渲染可见的几个列表项,非可见区域的列表项,延迟渲染,流程如下所示
很快我们遇到了问题,在轮播图组件中,首屏仅显示第一张图片。但是在横向部落列表中,首屏应该渲染几个列表项呢?答案是我们无法提前确定,首屏可见的列表项,只有在渲染时,根据当前列表项在横向列表中的相对位置,才能计算出该项是否可见。
继续思考,虽然我们不能预知在首屏应该几个列表项,但是利用react native 提供的API方法,我们可以获取当前视图相对于容器的坐标位置。
static measureLayout(relativeToNativeNode: number,
onSuccess: MeasureLayoutOnSuccessCallback, onFail: () => void)
利用measureLayout方法,在视图完成布局,触发其onLayout事件回调后,可以通过视图的measureLayout方法,传入容器节点,从而获得当前视图在容器中的相对位置。利用这一特性,结合我们的视图特点——列表项视图的尺寸是确定的,可以在初始时,将所有列表项视图渲染为这一特定尺寸的空视图,待所有空视图渲染完成后,获取视图在容器中相对位置,仅将可视区域内的列表项用真实视图重新渲染。同时监听列表容器的滚动事件,在滚动事件回调中,计算视图在容器中的相对位置,当视图进入了可视区域时,再用真实视图替换空视图,上述流程如下所示
通过上述方案,我们可以实现列表项的延迟渲染特性,通过日志输出耗时时间,可以发现通过这一优化,渲染优化又可降低约30ms。
上述方案中,屏幕在首次渲染空白视图和再次渲染首屏可见列表项视图之间,有短暂闪动的感觉,为解决这一体验问题,可使用占位图方案替代空视图方案,即首先都用静态资源占位图来渲染列表项icon图,并延迟加载非可见列表项icon图,其方案如下所示
实现效果如下,首次渲染时均用占位图,首屏区域内的列表项icon视图依次下载并重新渲染。在采用这样实现后,解决了空视图带来的屏幕闪动问题,快速的完成初次渲染,实现了icon图片的延迟加载,渲染耗时稍有增加,相比空视图方案耗时增加约10ms,此时我们的首屏渲染耗时下降到了约350ms。
2.5热门帖模块的归宿
发现tab视图下部还有一个模块,如下所示,是几条热门帖子的展示区域。对于首屏内容来说,这一模块整个都属于非可视区域,因此我们需要设法将这一块的耗时从首屏耗时中拿掉。
有了上面部落列表的优化经验,我们已经知道,在布局事件完成后,可以获取视图在容器内的相对位置,对于滚动视图如ScrollView,我们可以监听他的滚动事件,来判断视图距离可视区域的位置以决定是否渲染该视图,为了优化体验,我们再给视图的渲染设置一个提前阈值aheadDistance,当视图距离可视区域还有aheadDistance单位时,提前渲染该视图,这一方法如下图所示
采用这一方案后,已经可以将热门帖子模块的渲染耗时从首屏耗时中去掉了,此时通过日志输出耗时分析,首屏渲染耗时下降到了约270ms。
那么这块的优化工作完成了吗?没有,在优化做完,回顾体验时,我们发现了这里仍存在体验问题。这一体验问题与发现tab的视图结构有关。在发现tab中,热门帖子模块,距可视区域底部的距离不远。因为本身距离可视区域底部很接近,所以ahead Distance的设置并没有起到预想的效果,导致发现tab界面向下滑动时,因为实时去渲染热门帖子模块,使得滑动不流畅,有阻滞感。
为了解决这一体验问题,继续对热门帖子模块做了定制的优化,最终对热门帖子模块做到了异步渲染。在首屏渲染时,还是将热门帖子渲染为空视图,然后将ahead Distance调大,使得空视图落在ahead Distance区域内,接着使用setTimeout技巧,异步的去计算该空视图在容器中位置,并将热门帖子真实视图渲染出来。
通过上述方案,将延迟渲染替换为异步渲染,在不增加渲染耗时的基础上,同时解决了滑动不流畅的体验问题,最终将首屏渲染耗时定格在约270ms。
3.总结
react native框架给了我们新的能力,使得我们可以用javascript开发原生app。当我们走入react native应用的深水区,开始对他进行各方面细致的优化时,我们在原来web端积累的最佳实践依然有效,诸如缓存的使用、元素复用、延迟加载、异步加载等方法依然会起到很好的效果。
与此同时,对渲染耗时的优化也要兼顾使用的体验,我们应该追求的,不仅仅是快。而是在快的基础上,也有很好的使用体验。
优化探索到最后,我们将首屏渲染耗时定格在约270ms,加上react native初始化耗时约70ms,我们的首屏总耗时仍需约340ms,相比原生实现仍然存在差距。原生实现的渲染速度也是经过了层层的优化,而我们对react native最佳实践的探索也没有结束,欢迎大家指导、探讨。
想了解更多干货,欢迎关注腾讯优测微信号:腾讯优测(wxutest)
腾讯优测:
腾讯优测是备受客户信赖的移动云测试平台,为应用、游戏,H5混合应用的研发团队提供产品质量检测与问题解决服务。不仅在线上平台提供「云手机远程操控与调试」、「自动化全面兼容性测试」、「APK源码级缺陷性分析」等多种质量检测工具,更为VIP客户配备专家团队提供定制化综合测试解决方案。真机实验室配备上千款手机,覆盖市面98%主流机型,7*24小时在线运行,覆盖亿级用户。
探索react native首屏渲染最佳实践的更多相关文章
- 腾讯优测优分享 | 探索react native首屏渲染最佳实践
腾讯优测是专业的移动云测试平台,旗下的优分享不定时提供大量移动研发及测试相关的干货~ 此文主要与以下内容相关,希望对大家有帮助. react native给了我们使用javascript开发原生app ...
- React服务器渲染最佳实践
源码地址:https://github.com/skyFi/dva-starter React服务器渲染最佳实践 dva-starter 完美使用 dva react react-router,最好用 ...
- 基于React Native的58 APP开发实践
React Native在iOS界早就炒的火热了,随着2015年底Android端推出后,一套代码能运行于双平台上,真正拥有了Hybrid框架的所有优势.再加上Native的优秀性能,让越来越多的公司 ...
- 基于React Native的移动平台研发实践分享
转载:http://blog.csdn.net/haozhenming/article/details/72772787 本文目录: 一.React Native 已经成为了移动前端技术的趋势 二.基 ...
- React Native 红屏之Could not connect to development server.
React Native 是目前最火的开发框架,其他不说了,上Bug. 按照 React Native iOS环境搭建 高级版 在mac上 搭建 React Native 环境,运行 项目 若出 ...
- 【react native】rn踩坑实践——从输入框“们”开始
因为团队技术栈变更为react native,所以开始写起了rn的代码,虽然rn与react份数同源,但是由于有很多native有关的交互和变动,实际使用还是碰到蛮多问题的,于是便有了这个系列,本来第 ...
- 首屏渲染时间获取 performance.now()
Performance — 前端性能监控利器 最近在写一个监控脚本,终于有机会接触到了这一块,整理后写下了本文.Performance是一个做前端性能监控离不开的API,最好在页面完全加载完成之后 ...
- 如何使用webpack优化首屏渲染时间
其实说到性能优化,他的范围太广了,今天我们就只聊一聊通过webpack配置减少http请求数量这个点吧. 简单说下工作中遇到的问题吧,我们做的一个项目中首页用了十多张图片,每张图片都是一个静态资源,所 ...
- vue 首屏渲染优化 -- 这个不错
这篇文章分享了从遇到前端业务性能问题,到分析.解决并且梳理出通用的Vue 2.x 组件级懒加载解决方案(Vue Lazy Component )的过程. 初始加载资源过多 问题起源于我们的一个页面,下 ...
随机推荐
- jquery 字数限制
$("#TextArea1").keydown(function(){ 10 var curLength=$("#TextArea1").val().lengt ...
- 河流 tyvj1506
题目大意: 给出一棵n个节点的有根树,一开始 树根 是一个控制点,现在要增加m个控制点,使得总费用最少. 给出每个节点的父节点以及到父节点的距离,还有这个节点的权值, 一个节点的费用 即它的权值 乘以 ...
- [开发笔记]-“在引用COM组件时,出现了无法嵌入互操作类型。。。”的错误
这两天在做一个需要将wps文档转换成word文档的程序,在调用wps的com组件时项目编译是没有问题的,但当运行的时候却弹出了下面的错误提示: 从网上百度一番后,找到了正确的解决方法. 先从Com组件 ...
- Linux-C程序的存储空间布局
正文段 指的是由CPU执行的机器代码,通常,正文段是可以共享的,执行的程序在存储器中只有一个副本.通常也是只读的,防止程序本身被修改. 初始化数据段 数据段,被明确赋值的变量,比如全局变量 非初始化数 ...
- zoj 3228 覆盖及非覆盖串的多次匹配
题目题意: 给定多个小串,在一个长串中寻找这些串的匹配次数,有些统计的是可覆盖的,有些统计的是非覆盖的 先可以简单理解一下,建立ac自动机后,当前节点包含的字符串必然被把它作为fail指针的节点包含, ...
- <转>2015-7-14面试题
由于一些原因,最近打算换一份工作,主要目标是大型的互联网公司.在经历了上周三天小公司试水后,昨天终于开始正式的面试之旅了(其实接到面试通知的就几家公司
- 【python】dict。字典
特点:以空间换取时间,使用HASH算法通过key算出了value的内存地址,建立索引,拿到key后查找速度快,但内存浪费多 因为是用key值算的内存地址,所以key为不可变变量 (set,和dict类 ...
- hdu 1069
//Accepted 264 KB 0 ms //每种block只有三种方法,且每种放法至多放一次 //规定三条边的顺序后 //把所有的block按x递增排序,x相同则按y递增排序 //然后dp // ...
- hadoop环境搭建遇到问题集锦
1 在hadoop的bin目录下, 运行hadoop version命令,提示“hadoop:没有此命令” 解决办法: ./hadoop version或者$HADOOP_HOME/bin放在PAT ...
- UIControl的使用
在开发当中,可能很多时候都需要做个点赞的需求,如果用按钮实现,按钮作为一个系统复合控件,外部是一个 View-->UIControl的容器, 内部包含了UILabel和UIImage,以及一些排 ...