reactNative性能优化
本文将简单介绍一下我所收集到的React Native应用优化方法,希望对你有所启发。很多方法也是适用React web应用的。
包体积优化
无论是热更新方案走网络下载js,还是直接将js打进apk,减小js bundle体积都很必要。
走网络的js体积大影响首次加载速度,打进apk的增加包体积。
- 压缩
为了测试,直接使用react-native init
命令生成了一个rn工程,将其中的App.js改为下面这样简单的代码,验证这样简单的代码打包生成的js bundle体积情况。
import React from 'react';
import {
Text,
} from 'react-native';
const App: () => React$Node = () => {
return (
<Text>1</Text>
)
};
export default App;
使用 下边的命令可以打包js bundle
# 非压缩
react-native bundle --entry-file ./index.js --bundle-output ./testBundle.js --dev true
# 压缩
react-native bundle --entry-file ./index.js --bundle-output ./testBundle_min.js --dev false
# 查看体积
ls -h testBundle.js testBundle_min.js
-rw-r--r-- 1 bingxiongyi staff 3.3M Dec 24 11:02 testBundle.js
-rw-r--r-- 1 bingxiongyi staff 629K Dec 24 11:06 testBundle_min.js
可见不压缩的体积远远大于压缩后的体积,因此压缩十分必要。将js打包进apk的,默认就会压缩。
关键要注意的是走网络下载js的这种方式需要压缩js.
- 包拆分
上边可以看到一个空工程打包出来的js就压缩后有629k, 他们主要是react-native,react这些框架的代码。
如果是纯的react-native应用,我们可能会直接用react-navigation做路由,这个应用就是生成一个js bundle,打到apk, 框架部分的代码不会重复生成,自然没有什么问题。
但是大多情况为了实现热更新,react-native是嵌入到原生app中的,可能一个页面就会是一个js bundle, 走网络下载,如果每个js bundle都包含框架的代码,必然造成不必要的重复下载。
因此,需要将框架和不常变动的代码单独抽取,最好是将这部分代码打进apk, 这样业务代码体积会大大减小。实际发现我们一个包含三个复杂页面的js bundle业务代码压缩体积仅仅180k多。
关于如何拆包可参考React native 拆包
render次数优化
为什么要减少render次数
走到了render并不一定会有真实dom的操作,但是一定会走一次dom diff。react大名鼎鼎的diff算法确实高效,看了一眼vitual-dom原理与简单实现,确实像大佬们说的,是个O(n)的算法。
需要更新dom时去算diff无可厚非,但是diff了半天发现无差别,不需要更新就白瞎了,O(n)虽好,做不必要的O(n)也是一件浪费资源的事,因此需要尽可能减少diff次数,也就是减少render次数。
如何减少render的次数
- 减少setState的次数
需要说明的是setState可以是批量的,可能多次setState只会有一次render; 也可以是setState一次就render一次。
- 批量:在合成事件, 生命周期函数中的setState是异步批量更新的, 多次setState只会走一次render
- 非批量:在setTimeOut, setInterval, 原生事件, Promise中的setState是同步逐个更新的, 而且每次setState都会走一次render
因此减少setState次数需要减少的是非批量的情形,在合成事件,比如onClick里边去多次setState是没有关系的。
举个例子:
通常写一个上拉加载的列表会像下边这样,每次请求开始时将状态置为加载中。但是可能进页面其实就是加载中的状态了。
getList(pageNum) {
this.setState({
status: 1, // 1 loading, 2 success, 3 error
});
fetchList(pageNum)
.then(res => {
this.setState({
data: [...this.statedata, ...res],
});
})
.catch(e => {
console.log(e);
});
}
这样存在的一个问题是第一页会多一次不必要的setState, 因为初始状态一般就是加载中,当你使用Component而且没有重写shouldComponentUpdate时,这会导致一次不必要的render的。改成下边这样就ok了。
getList(pageNum) {
// 加上这样一个判断
if (this.state.status !== 1) {
this.setState({
status: 1,
});
}
fetchList(pageNum)
.then(res => {
this.setState({
data: [...this.state.data, ...res],
});
})
.catch(e => {
console.log(e);
});
}
第一段代码首屏会有3次render, 第二段代码只有两次。
- 使用PureComponent
Component是每次setState都会去render, 即使你setState的值和之前相同,也会render。
PureComponent重写了shouldComponentUpdate,在里边做了一次浅比较,如果setState后新state和旧state相同是不会走render的。
潜在的问题是当你把state里一个对象的某个属性值改了,由于浅比较是相等的,所以不会走render, 造成显示异常,这个注意下就行。
举个例子
还是上边这个,使用会render 3次的方案,但是使用PureComponent。
class App extends React.PureComponent{}
可以发现也只render了两次。这是因为我们的那次多余的setState set的是和原来相同的值,浅比较相等,所以没有render。
- 重写shouldComponentUpdate
这里有一个react生命周期的图, 从图中可以看出更新的时候每次render之前都会走shouldComponentUpdate, 当shouldComponentUpdate返回false的时候就不会render了,因此我们可以在shouldComponentUpdate中合理控制是否render.
同上,使用会render 3次的方案。
重写一下shouldComponentUpdate, 也来做一个浅比较。
shouldComponentUpdate(nextProps, nextState) {
for (let key in nextState) {
if (nextState[key] !== this.state[key]) {
return true;
}
}
for (let key in nextProps) {
if (nextProps[key] !== this.props[key]) {
return true;
}
}
return false;
}
发现这样做后也只render了2次。
- 减少
diff代价(这个不是减少次数的)
将state的尽量放在更小的组件中,这样render时计算diff的成本更小一些.
举个例子
import React from 'react';
import { Text, Button, View } from 'react-native';
class SubComponent extends React.Component {
constructor(props, context) {
super(props, context);
}
state = {
text: 'A',
};
onPress = () => {
this.setState({
text: 'B',
});
};
render() {
console.log('SubComponent render');
return <Button title={this.state.text} onPress={this.onPress} />;
}
}
class SubComponent2 extends React.Component {
render() {
console.log('SubComponent2 render');
return (
<Text>F</Text>
)
}
}
function FunctionComponent() {
console.log('FunctionComponent excuted')
return <Text>G</Text>
}
class App extends React.Component {
constructor(props, context) {
super(props, context);
}
state = {
text: 'D',
}
onPress = () => {
this.setState({
text: 'E'
})
}
render() {
console.log('App render');
return (
<View>
<Button title={this.state.text} onPress={this.onPress} color="red"/>
<SubComponent />
<SubComponent2 />
<FunctionComponent />
</View>
)
}
}
export default App;
点红色按钮执行结果如下
App render
App.js:20 SubComponent render
App.js:27 SubComponent2 render
App.js:36 FunctionComponent excuted
点蓝色按钮执行结果如下
SubComponent render
若父组件内状态变更,则他和他所有子组件都会render, 而子组件的变更则不会影响到父组件和兄弟组件。因此在实际开发中应该尽量减小state的作用范围,如果一个状态能收敛到组件内部,就不应该放在外边。这样可以降低render操作的代价。
动画优化
- 启用原生动画渲染
Animated的 API 是可序列化的(即可转化为字符串表达以便通信或存储)。通过启用原生驱动,我们在启动动画前就把其所有配置信息都发送到原生端,利用原生代码在 UI 线程执行动画,而不用每一帧都在两端间来回沟通。如此一来,动画一开始就完全脱离了 JS 线程,因此此时即便 JS 线程被卡住,也不会影响到动画了。(来自reactnative中文文档)
Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true // <-- 加上这一行
}).start();
- setNativeProps
setNativeProps方法可以使我们直接修改基于原生视图的组件的属性,而不需要使用setState来重新渲染整个组件树。
如果我们要更新的组件有一个非常深的内嵌结构,并且没有使用shouldComponentUpdate来优化,那么使用setNativeProps就将大有裨益。
(来自reactnative中文文档)
- InteractionManager
Interactionmanager 可以将一些耗时较长的工作安排到所有互动或动画完成之后再进行。这样可以保证 JavaScript 动画的流畅运行。
如果动画执行期间可能有比较耗时的代码可以如下操作
InteractionManager.runAfterInteractions(() => {
// 把耗时比较多的代码放到这里,防止他们影响动画效果
});
过渡绘制优化
过度绘制(Overdraw)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的 UI 结构里面,如果不可见的 UI 也在做绘制的操作,会导致某些像素区域被绘制了多次,同时也会浪费大量的 CPU 以及 GPU 资源。
在rn应用开发中解决过度绘制问题方法如下:
子组件能将父组件占满的情况下不要父组件背景色,而是指定子组件背景色
这里可以看个例子来了解为什么会这样
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
bg: {
backgroundColor: '#aaa',
},
text: {
padding: 30,
}
})
export default class App extends React.PureComponent {
render() {
return (
<View>
<Text style={styles.text}>0次</Text>
<View style={styles.bg}>
<Text style={styles.text}>1次</Text>
<View style={styles.bg}>
<Text style={styles.text}>2次</Text>
<View style={styles.bg}>
<Text style={styles.text}>3次</Text>
<View style={styles.bg}>
<Text style={styles.text}>4次</Text>
</View>
</View>
</View>
</View>
</View>
);
}
}
这个例子中我们不断的叠加颜色,这样就会产生过度绘制问题,我们可以在开发者选项开启"显示过度绘制"查看效果,如下
从这个例子可以看出我们应该尽量减少不必要的背景叠加来减少过度绘制。
小结
本文的优化方法基本都是从前辈大佬文章借鉴来的,部分方法做了一些小测试,大多没有。有一些方法也是适用react web应用的。
写的比较粗,后边可能会对这些优化点逐一测试,做一些数据上的支撑,实实在在看看这些方法对性能有何种提升。使用的工具应该会是腾讯的性能狗, 非常容易用,大家感兴趣也可以先测测。
参考文章
reactNative性能优化的更多相关文章
- React-Native性能优化点
shouldComponentUpdate 确保组件在渲染之后不需要再更新的,即静态组件,尽量在其中增加shouldComponentUpdate方法,防止二次消耗所产生的性能消耗 shouldCom ...
- ReactNative---卡顿问题及性能优化
ReactNative性能优化 在reactnative 中如果要更改DOM中的数据显示,只有通过setState方法来实现:但是当setState时,要刷新整个DOM:在一般情况先还能保证体验,但是 ...
- 01.SQLServer性能优化之----强大的文件组----分盘存储
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 文章内容皆自己的理解,如有不足之处欢迎指正~谢谢 前天有学弟问逆天:“逆天,有没有一种方 ...
- 03.SQLServer性能优化之---存储优化系列
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 概 述:http://www.cnblogs.com/dunitian/p/60413 ...
- Web性能优化:What? Why? How?
为什么要提升web性能? Web性能黄金准则:只有10%~20%的最终用户响应时间花在了下载html文档上,其余的80%~90%时间花在了下载页面组件上. web性能对于用户体验有及其重要的影响,根据 ...
- Web性能优化:图片优化
程序员都是懒孩子,想直接看自动优化的点:传送门 我自己的Blog:http://cabbit.me/web-image-optimization/ HTTP Archieve有个统计,图片内容已经占到 ...
- C#中那些[举手之劳]的性能优化
隔了很久没写东西了,主要是最近比较忙,更主要的是最近比较懒...... 其实这篇很早就想写了 工作和生活中经常可以看到一些程序猿,写代码的时候只关注代码的逻辑性,而不考虑运行效率 其实这对大多数程序猿 ...
- JavaScript性能优化
如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍J ...
- 02.SQLServer性能优化之---牛逼的OSQL----大数据导入
汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 上一篇:01.SQLServer性能优化之----强大的文件组----分盘存储 http ...
随机推荐
- EF ObjectStateManager无法跟踪具有相同键的多个对象 标签: EasyUIc# 2015-09-05 11:01 1181人阅读
最近做一个项目,因为是重构,好多代码是搬过来的,但是因为框架不同,所以搬过来也出现了很多问题,前几天在调试的时候,就碰到一个EF框架经常出现的问题:ObjectStateManager中已存在具有同一 ...
- c++中单引号和双引号的区别
在C++中单引号表示字符,双引号表示字符串. 例如 :在定义一个数组的时候string a [5]={"nihao","henhao","good&q ...
- Knative Eventing 中如何实现 Registry 事件注册机制
摘要: 在最新的 Knative Eventing 0.6 版本中新增了 Registry 特性, 为什么要增加这个特性, 该特性是如何实现的.针对这些问题,希望通过本篇文章给出答案. 背景 作为事件 ...
- @topcoder - SRM577D1L3@ XorAndSum
目录 @description@ @solution@ @accepted code@ @details@ @description@ 给出 N 个数,每次操作可以任意选择两个数,将其中一个替换为两个 ...
- Tensorflow中tf.ConfigProto()详解
参考Tensorflow Machine Leanrning Cookbook tf.ConfigProto()主要的作用是配置tf.Session的运算方式,比如gpu运算或者cpu运算 具体代码如 ...
- 神经网络入门——6and感知机
AND 感知器练习 AND 感知器的权重和偏置项是什么? 把权重(weight1, weight2)和偏置项 bias 设置成正确的值,使得 AND 可以实现上图中的运算. 在这个例子 ...
- 安装win下的Anaconda ----针对python3.6.4版本
我的python版本是3.6.4, Anaconda下载地址: Anaconda官网:https://repo.anaconda.com/archive/ 清华大学镜像站:https://mirror ...
- Python--day19--random模块
random模块 >>> import random #随机小数 >>> random.random() # 大于0且小于1之间的小数 0.766433866365 ...
- TensorFlow指定使用GPU 多块gpu
持续监控GPU使用情况命令: $ watch -n 10 nvidia-smi1一.指定使用某个显卡如果机器中有多块GPU,tensorflow会默认吃掉所有能用的显存, 如果实验室多人公用一台服务器 ...
- python的for循环、下标和切片
for循环的格式 for 临时变量 in 列表或者字符串: 循环满足条件时执行的代码 else: 循环不满足条件时执行的代码 例: name = "abcdef&qu ...