什么樣的app才是一個優(yōu)秀的app呢?
- 安裝包的體積小
- 啟動速度快
- 使用流暢擎浴、不卡頓
- 用戶交互友好
- 報錯或者閃退次數(shù)少
一歉嗓、安裝包大小
1赫段、第三方庫部分引用
使用第三方庫會增加安裝包的大小乌询,有一些第三方庫寫得很完美(代碼很多)榜贴,但是我們可能只需使用庫里面很小的一部分代碼。這種情況下妹田,我們可以把相應(yīng)的代碼拷貝到項目里面唬党,而不是直接引用整個庫。比如:助手里面使用了SDWebImage第三方庫鬼佣,里面支持很多圖片格式驶拱,但是項目中只是為了支持gif圖片,因此我們只需要導(dǎo)入與gif相關(guān)的文件沮趣。
2屯烦、資源文件壓縮
項目中一般都會有大量的圖片資源文件坷随,如果沒有進(jìn)行壓縮房铭,會增加app的體積。使用TinyPNG去進(jìn)行壓縮温眉,一般可以減少50%-70%的大小缸匪。手動使用tiny去壓縮,不但會增加開發(fā)時間类溢,而且容易遺忘凌蔬,因此建議寫成腳本露懒,在項目執(zhí)行打包的時候去統(tǒng)一處理。
3砂心、定期清除沒有使用的資源文件懈词、代碼
版本迭代會造成一些資源文件或者代碼被棄用,建議定期檢查項目辩诞,刪掉一些沒用到的資源文件(腳本坎弯?)和代碼。
二译暂、首屏渲染優(yōu)化
1抠忘、拆包
在 react-native 執(zhí)行 JS 代碼之前,必須將代碼加載到內(nèi)存中并進(jìn)行解析外永。如果你加載了一個 50MB 的 bundle崎脉,那么所有的 50mb 都必須被加載和解析才能被執(zhí)行,這會導(dǎo)致首屏渲染時間變得很長伯顶。使用拆包技術(shù)將bundle拆成不同的業(yè)務(wù)包, 啟動時根據(jù)需要加載相應(yīng)的模塊囚灼,之后再逐漸按需加載更多的包。
(1)moles-packer:由攜程框架團(tuán)隊研發(fā)的砾淌,與攜程moles框架配套使用的React Native 打包和拆包工具啦撮,同時支持原生的 React Native 項目。重寫了react native自帶的打包工具汪厨,適合RN0.4.0版本之前的分包赃春。維護(hù)少,現(xiàn)在基本沒有多少人使用劫乱,兼容性差织中。
(2)diff patch:由谷歌團(tuán)隊研發(fā),兼容多種語言衷戈。原理是舊包與新包進(jìn)行差異比對狭吼,生成補丁文件,壓縮上傳到服務(wù)器殖妇。app請求接口把補丁文件下載到本地刁笙,與舊包合成新包。
優(yōu)點:打包生成的補丁文件體積星ぁ疲吸;
缺點:跨版本升級的時候不容易維護(hù)
git: https://github.com/google/diff-match-patch
(3)metro bundle:facebook官方提供的,支持RN 0.50及以上版本前鹅,并隨著RN版本的迭代不斷完善摘悴。文檔鏈接:https://facebook.github.io/metro/docs/en/getting-started
2、內(nèi)聯(lián)引用
內(nèi)聯(lián)引用(require 代替 import)可以延遲模塊或文件的加載舰绘,直到實際需要該文件蹂喻。
import React, { Component } from 'react';
import { TouchableOpacity, View, Text } from 'react-native';
let VeryExpensive = null;
export default class Optimized extends Component {
state = { needsExpensive: false };
didPress = () => {
if (VeryExpensive == null) {
VeryExpensive = require('./VeryExpensive').default;
}
this.setState(() => ({
needsExpensive: true,
}));
};
render() {
return (
<View style={{ marginTop: 20 }}>
<TouchableOpacity onPress={this.didPress}>
<Text>Load</Text>
</TouchableOpacity>
{this.state.needsExpensive ? <VeryExpensive /> : null}
</View>
);
}
}
3葱椭、bundle預(yù)加載
調(diào)用require會造成額外的開銷。因為當(dāng)遇到尚未加載的模塊時口四,require需要通過bridge來發(fā)送消息孵运。這主要會影響到啟動速度,因為在應(yīng)用程序加載初始模塊時可能觸發(fā)相當(dāng)大量的請求調(diào)用蔓彩。幸運的是掐松,我們可以配置一部分模塊進(jìn)行預(yù)加載。具體的步驟可以參考官方文檔:https://reactnative.cn/docs/performance/
三粪小、app的流暢度
使用 React Native 替代基于 WebView 的框架來開發(fā) App 的一個強有力的理由大磺,就是為了使 App 可以達(dá)到每秒 60 幀(足夠流暢),并且能有類似原生 App 的外觀和手感探膊。因此我們也盡可能地優(yōu)化 React Native 去實現(xiàn)這一目標(biāo)杠愧,使開發(fā)者能集中精力處理 App 的業(yè)務(wù)邏輯,而不用費心考慮性能逞壁。但是流济,總還是有一些地方有所欠缺,以及在某些場合 React Native 還不能夠替你決定如何進(jìn)行優(yōu)化(用原生代碼寫也無法避免)腌闯,因此人工的干預(yù)依然是必要的绳瘟。
1、減少不必要的渲染
在一個復(fù)雜應(yīng)用的根組件上調(diào)用了this.setState姿骏,從而導(dǎo)致一次開銷很大的子組件樹的重繪糖声,可想而知,這可能會花費 200ms 也就是整整 12 幀的丟失分瘦。此時蘸泻,任何由 JavaScript 控制的動畫都會卡住。只要卡頓超過 100ms嘲玫,用戶就會明顯的感覺到悦施。
你可以實現(xiàn)shouldComponentUpdate函數(shù)來指明在什么樣的確切條件下,你希望這個組件得到重繪去团。如果你編寫的是純粹的組件(界面完全由 props 和 state 所決定)抡诞,你可以利用PureComponent來為你做這個工作。再強調(diào)一次土陪,不可變的數(shù)據(jù)結(jié)構(gòu)(immutable昼汗,即對于引用類型數(shù)據(jù),不修改原值旺坠,而是復(fù)制后修改并返回新值)在提速方面非常有用 —— 當(dāng)你不得不對一個長列表對象做一個深度的比較乔遮,它會使重繪你的整個組件更加快速扮超,而且代碼量更少取刃。
2蹋肮、使用動畫改變圖片的尺寸時,UI 線程掉幀
在 iOS 上璧疗,每次調(diào)整 Image 組件的寬度或者高度坯辩,都需要重新裁剪和縮放原始圖片。這個操作開銷會非常大崩侠,尤其是大的圖片漆魔。比起直接修改尺寸,更好的方案是使用transform: [{scale}]的樣式屬性來改變尺寸却音。比如當(dāng)你點擊一個圖片改抡,要將它放大到全屏的時候,就可以使用這個屬性系瓢。
3阿纤、Touchable 系列組件的優(yōu)化
有些時候,如果我們有一項操作與點擊事件所帶來的透明度改變或者高亮效果發(fā)生在同一幀中夷陋,那么有可能在onPress函數(shù)結(jié)束之前我們都看不到這些效果欠拾。比如在onPress執(zhí)行了一個setState的操作,這個操作需要大量計算工作并且導(dǎo)致了掉幀骗绕。對此的一個解決方案是將onPress處理函數(shù)中的操作封裝到requestAnimationFrame中:
handleOnPress() {
this.requestAnimationFrame(() => {
this.doExpensiveAction();
});
}
4藐窄、動畫優(yōu)化
React Native 提供了兩個互補的動畫系統(tǒng):用于創(chuàng)建精細(xì)的交互控制的動畫Animated
和用于全局的布局動畫LayoutAnimation
Animated的接口一般會在 JavaScript 線程中計算出所需要的每一個關(guān)鍵幀,而LayoutAnimation則利用了Core Animation酬土,使動畫不會被 JS 線程和主線程的掉幀所影響荆忍。但是,LayoutAnimation
只工作在“一次性”的動畫上("靜態(tài)"動畫) -- 如果動畫可能會被中途取消撤缴,你還是需要使用Animated
东揣。
5、耗時的操作等動畫結(jié)束后再進(jìn)行
Interactionmanager可以將一些耗時較長的工作安排到所有互動或動畫完成之后再進(jìn)行腹泌。這樣可以保證JavaScript動畫的流暢運行嘶卧。
InteractionManager.runAfterInteractions(() => {
// ...耗時較長的同步的任務(wù)...
});
6、ListView 初始化渲染太慢以及列表過長時滾動性能太差
用新的FlatList
或者SectionList
組件替代凉袱。除了簡化了API芥吟,這些新的列表組件在性能方面都有了極大的提升, 其中最主要的一個是無論列表有多少行,它的內(nèi)存使用都是常數(shù)級的专甩。
如果你的FlatList
渲染得很慢, 請確保你使用了getItemLayout
钟鸵,它通過跳過對items的處理來優(yōu)化你的渲染速度。除此之后涤躲,你還可以使用initialNumToRender設(shè)置首屏渲染數(shù)量棺耍。
四、用戶體驗優(yōu)化
1种樱、TextInput
- 自動聚焦第一個字段
- 使用占位符文本作為預(yù)期數(shù)據(jù)格式的示例
- 文本格式化(電話蒙袍、身份證俊卤、銀行卡)
- 選擇鍵盤類型(例如電子郵件,數(shù)字)
- 確保返回按鈕聚焦下一個字段或提交表單
- 點擊空白處收起鍵盤
- 編輯時顯示清除按鈕
2害幅、鍵盤可見時管理布局
軟件鍵盤幾乎占據(jù)了屏幕的一半消恍。如果您有可以被鍵盤覆蓋的交互式元素,請確保使用該KeyboardAvoidingView
組件仍可訪問它們以现。
3狠怨、擴大響應(yīng)區(qū)域
在手機上按按鈕時很難非常精確。確保所有交互式元素都是44x44或更大邑遏。一種方法是為元素留出足夠的空間padding
佣赖,minWidth
并且minHeight
樣式值對此有用〖呛校或者茵汰,您可以使用[hitSlop
]屬性來增加交互區(qū)域而不影響布局。
4孽鸡、使用Android Ripple
Android API 21+使用材質(zhì)設(shè)計紋波蹂午,在用戶觸摸屏幕上的可交互區(qū)域時為其提供反饋。React Native通過TouchableNativeFeedback
組件公開它彬碱。使用這種可觸摸效果而不是不透明度或高光效果通常會讓您的應(yīng)用感覺更適合平臺豆胸。也就是說,使用它時需要小心巷疼,因為它不適用于iOS或Android API <21晚胡,因此您需要回退使用iOS上的其他可觸摸組件之一。您可以使用像react-native-platform-touchable這樣的庫來為您處理平臺差異嚼沿。
五估盘、錯誤上報
1、官方提供了方法:global.ErrorUtils.setGlobalHandler來監(jiān)聽全局的錯誤骡尽,可以參考以下代碼:
global.ErrorUtils.setGlobalHandler((e) => {
if (!e || !(e instanceof Error) || !e.stack) return {}
try {
const stack = e.stack.toString().split(/\r\n|\n/), frameRE = /:(\d+:\d+)[^\d]*$/
while (stack.length) {
const frame = frameRE.exec(stack.shift())
if (frame) {
// 堆棧信息里面的行數(shù)和列數(shù)
const position = frame[1].split(':')
return { line: position[0], column: position[1] }
}
}
} catch ( e ) {
return {}
}
})
2遣妥、使用SourceMap查看
我們可以通過完整的srouceMap和出錯的line、column來準(zhǔn)確還原發(fā)生錯誤時代碼上下文攀细。在RN當(dāng)中我們可以在執(zhí)行打包命令的時候帶上--sourcemap-output youbundle.map來生成箫踩。
const { SourceMapConsumer, SourceMapGenerator, SourceNode } = require('source-map')
const fs = require('fs')
/**
* rawMap
*/
const rawMap = JSON.parse(fs.readFileSync('./index.map').toString())
const smc = new SourceMapConsumer(rawMap)
const position = smc.originalPositionFor({
line: 1,
column: 75422
})
const { source, line, column } = position
// output
console.log(`事故發(fā)生現(xiàn)場:${source},位于第${line}行谭贪,第${column}列境钟!`)
3、第三方庫:Sentry
- 提供了完善的客戶端SDK俭识,Android慨削、iOS、react-native、web全平臺支持缚态。各端只需配置上報地址磁椒,其余SDK全搞定;
- 支持上傳sourcemap進(jìn)行源碼定位猿规。由于RN運行在客戶端中,與普通運行在瀏覽器的js相比錯誤堆棧有自己的特點宙橱。Sentry是目前調(diào)研中唯一一個默認(rèn)支持react-native錯誤分析的平臺姨俩。
- Sentry從SDK到后端服務(wù)都開源,可以私有化部署
文檔:https://docs.sentry.io/clients/react-native/
github:https://github.com/getsentry/sentry-react-native