95分前端React Native应用秒开实践

前言

React Native(以下简称RN)是一种基于React框架的移动端应用开发技术,由Facebook在2015年开源发布。它的出现解决了开发人员需要编写多个平台的原生代码的繁琐问题,同时提供了更快的开发速度和更好的用户体验。

然而,RN应用的包体积大、加载慢等问题一度困扰着开发者。在面临这些挑战的同时,开发者们也通过各种优化措施和工具不断尝试解决这些问题。

背景提要

为何业务功能增长=加载时间增长?

因为首屏时间的长短与访问转化率有直接的关联性,用户没有成功访问更别提最后的交易转化,所以降低加载时长提高访问转化率,是业务增长重要的一环。

RN业内加载时长与访问转化的关联数据:

将首屏时间降低了 1s,访问转化率提高了 6.9%(数据来源,58 同城 4 个 RN 业务的统计

(上图的含义:首屏耗时超过 1s 后,每增加 1s,用户到达率会降低 6.9%)

当然,不同的业务都有自己的特点,首屏时间与到达率的收益关系不一定就是 6.9% 这个值。这里的趋势图可以作为一个优化收益的参考。

有了以上的收益参考作为基础,便有了以下对降低加载时长上的技术思考。

技术思考

谁说不能又好又快?!

熟悉前端的同学都知道在开发比较大型的web项目时,会遇到很多低频的页面是不会被打开的,为了提高打开速度我们往往会使用webpack 的 import() 方法,这种按需加载的方式可以轻松实现模块的按需加载。

同样的,在RN业务中我们是否也可以使用同样的思维?,RN官方提供了一个很好的按需加载解决方案:RAM Bundles & react-native-bundle-splitter。

解决方案

RN启用 RAM 格式打包

在 iOS 上使用 RAM 格式将创建一个简单的索引文件,React Native 将根据此文件一次加载一个模块。在 Android 上,默认情况下它会为每个模块创建一组文件。你可以像 iOS 一样,强制 Android 只创建一个文件。

在 Xcode 中启用 RAM 格式,需要编辑 build phase 里的"Bundle React Native code and images"。在../node_modules/react-native/scripts/react-native-xcode.sh中添加 export BUNDLE_COMMAND="ram-bundle":

export BUNDLE_COMMAND="ram-bundle"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh 

在 Android 上启用 RAM 格式,需要编辑 android/app/build.gradle 文件。在apply from: "../../node_modules/react-native/react.gradle"之前修改或添加project.ext.react

project.ext.react = [bundleCommand: "ram-bundle",
] 

以上具体的启用可在官网找到例子。

按需加载的优势

将bundle包分成多个副包可以减小主包的大小,主包在应用程序启动时加载。那么优先加载比如首页、登录页、商品详情页等不可避免会看到的页面,一些低频的页面与功能可以异步进行加载,这样的话就可以很大程度的提升用户的体验。

右图可以看出所有的模块/组件,被划分为不同的组,这允许你可以加载自己需要的部分,而不是一股脑的加载整个bundle包(左图)

什么是内联引用

内联引用(require 代替 import)可以延迟模块或文件的加载,直到实际需要该文件。这里推荐使用 react-native-bundle-splitter库来处理应用的内联引用。

react-native-bundle-splitter使用介绍

react-native-bundle-splitter的特性

1.让你无侵入于你代码去实现哪些组件需要按需加载
2.拥有组件缓存的能力可以避免重复加载已加载过的组件
3.预加载组件的能力,可以预加载页面,使得未来进入页面体检更加流畅
4.完美适配react-navigation, react-native-navigation导航库

方案实践与优化对比

yarn add react-native-bundle-splitter
# or with npm# npm install react-native-bundle-splitter --save 
├── ...├── Login# 页面文件夹 
│ ├── View.jsx # 组件文件
│ └── index.ts # 页面入口文件
│ 
└── ... 

项目里除了首屏要加载的页面都可以使用以下面方式去改造

1.入口文件index.ts

import { register, getComponent, isCached } from 'react-native-bundle-splitter';
import React from 'react';
const name = 'login'; // 模块名称,必须唯一,最好和路由名一致 const staticProps = { // 路由的header的配置navigationOptions: ({ navigation }) => {return {title: '登录',};}
};

const BundleComponent = isCached(name)? getComponent(name): register({require: () => require('./View'), // 注册异步加载的页面name,static: { ...staticProps },});
export default BundleComponent; // 暴露给路由库的组件 

登录页面View.jsx

import React from 'react';
import {StyleSheet, View, Switch, Text} from 'react-native';
import {screen} from '../../navDecorator';
import {DUText, DUPage, DUShare, DUPickerModal, DUPageView, DuShowBrowser, DUOpenView} from 'react-native-du';

@screen('Login')
class Login extends DUPage {
 static navigationOptions = option => {return { title: '登录页面',};
 };

 constructor(props) {super(props);this.state = {};

 }

 componentDidMount() {
 }

 render() {return ( <DUPageView style={styles.container}><Text>Login Page</Text></DUPageView>);
 }
}

export default View; 

与导航库集成

import { createStackNavigator } from 'react-navigation';import LoginScreen from './page/login';import HomeScreen from './page/Home'export const AppNavigator = createStackNavigator({'Login': LoginScreen,'Home': HomeScreen,},{initialRouteName: 'Home'}); 

调试介绍

在处理完上述的改造之后,想看看自己的页面是否真的进行缓存,可以将下段代码放入根文件index.ts

const modules = require.getModules() ;
const moduleIds = Object.keys(modules) ;
const loadedModuleNames = moduleIds.filter(moduleId => modules[moduleId].isInitialized).map(moduleId => modules[moduleId].verboseName) ;
const waitingModuleNames = moduleIds.filter(moduleId => !modules[moduleId].isInitialized).map(moduleId => modules[moduleId].verboseName) ;// make sure that the modules you expect to be waiting are actually waitingconsole.log('loaded:',loadedModuleNames.length, // 首次进入RN加载的文件 
'waiting:',waitingModuleNames.length // 首次进入RN未加载的文件 
) ; 

waiting:首次进入RN未加载的文件

loaded:首次进入RN加载的文件

waiting loaded
优化前 23% 77%
优化后 45% 55%

可以明显看到首页进入RN加载的文件下降的百分之22%,首次主要加载的文件是公共的基础库的文件与运行所需的文件,其余的业务页面(耗时)都进行了异步加载。

捕捉启动时间

为了更好的观察优化前后的加载时间,可以使用react-native-startup-time包

yarn add react-native-startup-time 

在根目录index.js 中引入,可在屏幕中观察到加载时间

import { StartupTime } from 'react-native-startup-time';

<StartupTimestyle={styles.startupTime /* optional */}/> 

TIP:

每次记录时间之后,需杀死APP之后再次打开

优化前后的打开速度前后对比

优化前 优化后
平均加载时长 1200ms 150ms
gif对比 add99e9f-36a9-48eb-865b-cad83ad33b65.gif 2ed142ba-50a6-4799-a8a7-215b75fe7925.gif

在飞书中 gif放不进table里展示,在掘金中显示正常

通过RAM Bundles & react-native-bundle-splitter的处理之后,我们真正地做到了React Native应用的秒开,将打开时长从原来的1200ms降低到了150ms,打开速度提升了87.5%!

用户应用打开率数据

优化当月的用户应用打开率走势,在优化上线后,提升了百分之三的应用打开率。

FAQ

RN版本限制

需要0.59或者更高的版本,因为具有内联引用的特性只能从0.59版本开始。

页面生命周期中的监听问题

在做一个监听页面停留时长的需求时,在注册了监听路由即将进入的事件willFocus, 发现没有生效,排查下来是使用了按需加载之后会出现这个问题,目前暂时降级使用didFocus事件

this.props.navigation.addListener('willFocus', () => {
 // 在使用react-native-bundle-splitter 该事件监听不到
 xxxxx
});

// 目前降级使用this.props.navigation.addListener('didFocus', () => {
 xxxx

}); 

未来规划

RN 升级为0.6X版本,新版本底层引擎升级,从v8/js-core升级为Hermes,Hermes是个轻量级的JS引擎,专门针对ReactNative进行了优化,启动更快,资源消耗更低,并提供了更多实用的特性,可以为用户带来更好的体验。

最后

最近找到一个VUE的文档,它将VUE的各个知识点进行了总结,整理成了《Vue 开发必须知道的36个技巧》。内容比较详实,对各个知识点的讲解也十分到位。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

猜你喜欢

转载自blog.csdn.net/web22050702/article/details/129441207