前言
本文次要介绍 react-native(下称 RN) 的入门, 和前端的异同点
文章不波及性能的具体实现
抉择劣势
咱们先说说, 为什么很多人会抉择应用 RN、他对应的个性和一般 Web 的区别
- 前端资源, 生态的互通
因为应用的语言是 JS 和 react, 对于前端来说能够无缝切换, 并且他还能应用前端的各类包
在 JS 端, 安卓和 iOS 是同一套代码
- 热更新
很多抉择应用 RN 的起因就是有热更新
简略解释下热更新, 在运行 APP 时, js 层咱们能够通过承受到的告诉, 来进行实时替换, 替换结束之后个别是要重启 APP 的, 这个时候能够询问用户, 也能够在下次重启时从新载入新的 JS 代码
这样能够保障用户应用的 js 环境, 能够是较新的, 如果是原生 APP 的更新则须要让用户去利用商店从新下载
- 反对原生
RN 通过桥接与原生进行交互, 页面级别的融入原生 APP
他的许多组件, 办法都是调用了原生办法 / 组件, 绝对 webview 来说性能更好
跨端框架横向比照
RN 和 Flutter 的简略比照
环境
无论是 RN 还是 Flutter
,都须要 Android 和 IOS 的开发环境,也就是 JDK
、Android SDK
、Xcode
等环境配置,而不同点在于:
- RN 须要 npm、node、
react-native-cli
等配置。 Flutter
须要flutter sdk
和Android Studio
/VSCode
上的Dart
与Flutter
插件。
针对前端来说 RN 环境绝对敌对一点
实现原理
在 Android 和 IOS 上,默认状况下 Flutter 和 React Native 都 ** 须要一个原生平台的Activity
/ ViewController
反对,且在原生层面属于一个“单页面利用”,** 而它们之间最大的不同点其实在于 UI 构建:
- RN:
React Native 是一套 UI 框架,默认状况下 React Native 会在 Activity
下加载 JS 文件,而后运行在 JavaScriptCore
中解析 Bundle 文件布局,最终重叠出一系列的原生控件进行渲染。
简略来说就是 通过写 JS 代码配置页面布局,而后 React Native 最终会解析渲染成原生控件,如 <View>
标签对应 ViewGroup/UIView
,<ScrollView>
标签对应 ScrollView/UIScrollView
,<Image>
标签对应 ImageView/UIImageView
等。
- Flutter:
Flutter 中绝大部分的 Widget
都与平台无关,开发者基于 Framework
开发 App,而 Framework
运行在 Engine
之上,由 Engine
进行适配和跨平台反对。这个跨平台的反对过程,其实就是将 Flutter UI 中的 Widget
“数据化”,而后通过 Engine
上的 Skia
间接绘制到屏幕上。
相似于前端的 canvas 绘图
此节来自于文章: https://www.jianshu.com/p/da8…
毛病
-
RN:
- 不能齐全兼容 W3C 的标准,比方 W3C 外面,能够轻易设置圆角的大小,粗细,边框是实现和虚线,然而在客户端,这个实现起来都比拟难。所以这类技术都只能无限的反对 W3C 的规范。
- js 运行性能瓶颈。
- 数据通信的性能瓶颈。
-
Flutter:
- 无奈动静更新。
- 内存和包大小占用。
- 学习老本高,生态有余。
js 运行环境
在应用 RN 时,JS 代码将会运行在两个不同的环境上:
- 大多数状况下,RN 应用的是 JavaScriptCore,也就是 Safari 所应用的 JavaScript 引擎。然而在 iOS 上 JavaScriptCore 并没有应用即时编译技术(JIT),因为在 iOS 中利用无权领有可写可执行的内存页(因而无奈动静生成代码)。
- 在应用 Chrome 调试时,所有的 JavaScript 代码都运行在 Chrome 中,并且通过 WebSocket 与原生代码通信。此时的运行环境是 V8 引擎。
所以在咱们开启调试的时候和正式的运行环境会有一些不一样
RN 内置了 Babel
转换器。所以很多语法咱们是不须要再配置 babel
的, 语法环境间接上手即用
在 这里 能够看到具体的配置
定时器
在 RN 中有针对动画的定时器: InteractionManager
原生利用感觉晦涩的一个重要起因就是在互动和动画的过程中防止沉重的操作。
在 RN 里,则受到了限度,因为咱们只有一个 JavaScript 执行线程。于是就有了 InteractionManager
来确保在执行沉重工作之前所有的交互和动画都曾经处理完毕。
InteractionManager.runAfterInteractions(() => {// ... 须要长时间同步执行的工作...});
相比拟另外的几个定时器:
requestAnimationFrame()
: 用来执行在一段时间内管制视图动画的代码setImmediate/setTimeout/setInterval()
: 在稍后执行代码。留神这有可能会提早以后正在进行的动画。runAfterInteractions()
: 在稍后执行代码,不会提早以后进行的动画。
Hermes 引擎
Hermes 是专门针对 RN 利用而优化的全新开源 JavaScript 引擎。对于很多利用来说,启用 Hermes 引擎能够优化启动工夫,缩小内存占用以及空间占用。
Hermes 的特色
- 预编译字节码(引擎加载二进制代码效率高于运行 JS 脚本)
- 无 JIT 编译器(减小了引擎大小,优化内存占用,但间接运行 JS 脚本的性能差于 V8 和 JSC)
- 针对挪动端的垃圾回收策略
优化原理
传统 JavaScript 引擎通常是以上图的模式实现代码执行的,编译阶段只实现 babel 本义和 minify 压缩,产物还是 JavaScript 脚本,解释与执行的工作都须要在运行时实现(如 V8 引擎,还会在运行时将 JavaScript 编译为本地机器码)很显著毛病就是在运行时须要边解释边执行,甚至须要占用系统资源执行编译工作。
Hermes 引擎应用了 aot 编译的形式,将解释和编译过程前置到编译阶段,运行时只实现机器码的执行,大大提高了运行效率。
原生 ui 组件
在 RN 中的一个劣势就是能够插入原生组件, 进步 APP 的性能
如果咱们在 js
中要应用 ImageView
, 那就须要这几步:
- 创立一个
ViewManager
的子类。 - 实现
createViewInstance
办法。 - 导出视图的属性设置器:应用
@ReactProp
(或@ReactPropGroup
)注解。 - 把这个视图治理类注册到利用程序包的
createViewManagers
里。 - 实现 JavaScript 模块。
上述是安卓的增加, 相对来说 iOS 会简略一点:
- 首先创立一个
RCTViewManager
的子类。 - 增加
RCT_EXPORT_MODULE()
宏标记。 - 实现
-(UIView *)view
办法。
// RNTMapManager.m
#import <MapKit/MapKit.h>
#import <React/RCTViewManager.h>
@interface RNTMapManager : RCTViewManager
@end
@implementation RNTMapManager
RCT_EXPORT_MODULE(RNTMap)
- (UIView *)view
{return [[MKMapView alloc] init];
}
@end
在 JS 中应用:
// MapView.js
import {requireNativeComponent} from 'react-native';
// requireNativeComponent 主动把 'RNTMap' 解析为 'RNTMapManager'
export default requireNativeComponent('RNTMap');
// MyApp.js
import MapView from './MapView.js';
...
render() {return <MapView style={{ flex: 1}} />;
}
这是简略的展现, 对于传值的话就是多一些属性的判断
除了原生组件之外, js 还能传值给挪动端, 增加监听事件(包含 promise 回调), 对应的挪动端也都能够
这样就组成了两端残缺的通信体系
链接原生库
上图就是 react-native-splash-screen
库的 install
和 link
在咱们应用三方原生库的时候, 就须要做一个 link
的性能
咱们随着 RN 公布的所有库都在仓库中的 Libraries 文件夹下。其中有一些是纯 Javascript 代码,你只须要去 import 它们就能够应用了。另外有一些库基于一些原生代码实现,你必须把这些文件增加到你的利用,否则利用会在你应用这些库的时候产生报错。
而 link 它就是手动链接我的项目中的依赖项的代替办法。
而手动链接是一个很麻烦的事件, 安卓和 iOS 的计划还是不雷同的, 具体可查看
现状
很侥幸的是, 然而如果咱们应用的 RN 库是在 0.60
以上的, 就能够不须要应用 link
指令了
在安卓中他会主动链接, 而在 iOS 中, 则能够应用 cocoapods
来下载原生包
cocoapods
简略介绍一下,
CocoaPods
是一个用于Swift
和Objective-C
Cocoa 我的项目的依赖管理器.
类比的话, 看成npm
即可.
应用 cocoapods 时, 会须要文件 Podfile
, 能够类比成 package.json
之后通过指令 pod install
(类比 npm install
) 下载, 下载结束之后会存于 Pods
文件下, 同时也存在 lock
文件: Podfile.lock
, Manifest.lock
两份
轻轻说一句, 如果没有翻墙工具 pod 的下载会变得很麻烦, 常常会卡住
路由治理
在 RN 中罕用的路由治理有两个: 一是 React Navigation
, 另一个是 react-native-navigation
这两的区别在于, 前者是通过 JS 代码, 通过 monorepo
的组合, 并且通过 react-native-screens
和 react-native-reanimated v2
等库的优化, 最终造成最终靠近原生的体验
至于为什么大部分放在 js 端, 他有什么益处, 我会放在上面热更新局部解说
而后者应用原生容器来作为路由界面, 如 <ScreenContainer>
或者 <Screen>
, 他带来了原生的性能、个性和体验, 但在咱们应用此库或者要集成另外的库时会带来一些麻烦
和前端有什么异同
在 APP 中的路由会呈现一个概念 堆栈(stack), 这就和 web 中最大的一点不同了
这里用一张图来介绍下:
当咱们到一个新页面时, 上一个页面是不会销毁的(大多数状况), 他是将新页面增加到栈中, 所以在 APP 中, 要常常小心内存的透露问题
热更新
这是一个在 RN 中最罕用到的以及最大的一个劣势性能 – 热更新
热更新计划
一般来说有三种计划:
- react-native-pushy
ReactNative 中文网推出的代码热更新服务,收费阶段实用于小型利用,轻度更新需要, 超出就须要免费了 - react-native-code-push + AppCenter
完全免费,国内速度可能慢,适宜集体开发者 - react-native-code-push + code-push-server
适宜公司自建热更新服务器
对于热更新的留神点:
- 苹果 App 容许应用热更新 Apple’s developer agreement, 为了不影响用户体验,规定必须应用静默更新。Google Play 不能应用静默更新,必须弹框告知用户 App 有更新。中国的 android 市场必须采纳静默更新(如果弹框提醒,App 会被“请上传最新版本的二进制利用包”起因驳回)。
- react-native-code-push 只更新资源文件, 不会更新 java 和 Objective C,所以 npm 降级依赖包版本的时候,如果依赖包应用的本地化实现, 这时候必须更改利用版本号, 而后从新编译 app 公布到利用商店。
一般来说手机热更新的流程:
其中检测、下载、重启等等, 都是 npm 包 react-native-code-push 提供的 API
对于热更新还有进一步优化的空间, 如: 一次打包进去的 bundle 过大, 对其进行分包, 本文就不在深刻解析了
APP 更新
在下面咱们讲到了, 原生组件更新之后, 就须要从新下载 APP 了, 那么怎么不便地更新呢?
这里就用到了我之前写的一篇文章, 原理如下图:
原文点击这里
上述的热更新和 APP 更新, 在
electron
上也有对应的实现, 对于 web 端的同学来说, 这是一个值得参考的信息
其余不同点
在 APP 中还有很多细节与 Web 端不同, 这里列出几点
debug 计划
开发过 H5 的人应该对于 vconsole
很相熟, 在 RN 中也有一个 vconsole
的组件, 用来 debug、打印 console、查看申请、显示各类信息等等
之前我也封装过一个 RN 的 vconsole
插件: react-native-vconsole, 联合了多个插件的长处
针对物理键的操作
在安卓机上特有的一种性能, 他就是物理键
用户能够间接点击物理键来进行后退, 当退到最首页的时候, 就须要显示提醒 再按一次退出
这就须要对其进行非凡适配:
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress)
handleBackPress = () => {if (// 如果是第一个页面) {const timestamp = new Date().valueOf()
if (timestamp - firstClick > 2000) {
firstClick = timestamp
ToastAndroid.show('再按一次退出', ToastAndroid.SHORT)
return true // 返回 true,意思是阻止默认操作
}
}
return false
}
沉迷式状态栏
在手机上会有状态栏这么一个场景, 这是一个很影响视觉的性能
能够看到上图中, 在显示信号和电池那一块的变动, 这一部分就是状态栏
在 RN 中时候咱们通过此 API 来管制:
<StatusBar barStyle="dark-content" backgroundColor="#ecf0f1" />
当然, 为了适配多种状况(在 APP 中要在页面的进入, 来到, 其余小性能的变动时批改状态栏), 有时候一些页面是须要通明状态栏, 也须要一些非凡设置
很多时候这个组件并不是间接就用的, 须要包装来适配大多数的页面
版本变动
在 RN 中有几个版本是有很大的 breaking change
- 0.59-0.60 的降级
在这两个版本间接有很多的 breaking change
其中 iOS 端最大的改变就是, 包变成了 CocoaPods(下面曾经讲过)
这让咱们的 package 依赖也须要对应的降级(预计会有 50% 以上的包降级), 所以影响范畴根本就是整个我的项目
而安卓方面, 则是 link 的形式变动了, 另外就是 build.gradle
、settings.gradle
、AndroidX
的配置的批改
这里要介绍一下官网的降级工具: https://react-native-communit…
他能比拟对应的版本, 把其中的 changes 显示进去
- 0.68 的降级
另一个就是 0.67 到 0.68 的降级, 在这个版本变更中, RN 进行了四点调整:
- JavaScript Interface(JSI) – 通信的更新
- Fabric – 新的渲染零碎
- Turbo Modules – Native 模块的加强
- CodeGen – 动态类型查看器
因为这是一个很底层的批改, 可能会导致现有的所有组件发生变化, 影响范畴简直笼罩全局
新的架构
这里咱们就来讲一下 0.68 中的更新具体是什么
JavaScript Interface(JSI)
原有的架构我在上文中曾经讲过了, 他存在的一些问题:
目前 RN 应用 Bridge Module
通信(其中还须要数据转换和解码), 发送的音讯实质上是异步的, 也就是说如果是即时性比拟高的操作, 比方拖拽, 就会呈现失帧的状况
而在全新架构中,Bridge 将被一个名为 JavaScript Interface 的模块所代替,它是一个轻量级的通用层,用 C++ 编写,JavaScript Engine 能够应用它间接执行或者调用 native。
原理
在 JSI 里 Native 办法会通过 C++ Host Objects 裸露给 JS,而 JS 能够持有对这些对象的援用,并且应用这些援用间接调用对应的办法。
举个简略的例子就是
这就相似于 Web 里 JS 代码能够保留对任何 DOM 元素的援用,并在它下面调用办法:
const container = document.createElement(‘div’);
如果你有 electron
的教训, 原有的通信模式就和 electron
中的主过程和渲染过程的通信一样
Fabric
在老架构中,RN 布局是异步的,这导致在宿主视图中渲染嵌套的 RN 视图,会有布局“抖动”的问题。
而新的架构中和 JSI 一样, 采纳的是跨平台的解决方案,共享了外围的 C++ 实现。
简略的解释就是 JSI 的 UI 版本.
当然还有一些其余的长处:
- 借助多优先级和同步事件的能力,渲染器能够进步用户交互的优先级,来确保他们的操作失去及时的解决。
- React Suspense 的集成,容许你在 React 中更合乎直觉地写申请数据代码。
- 容许你在 RN 应用 React Concurrent 可中断渲染性能。
- 更容易实现 RN 的服务端渲染。
Turbo Modules
在之前的架构中 JS 应用的所有 Native Modules(例如蓝牙、地理位置、文件存储等)都必须在应用程序关上之前进行初始化,这意味着即便用户不须要某些模块,然而它依然必须在启动时进行初始化。
Turbo Modules 基本上是对这些旧的 Native 模块的加强,正如在后面介绍的那样,当初 JS 将可能持有这些模块的援用,所以 JS 代码能够仅在须要时才加载对应模块,这样能够将显着缩短 RN 利用的启动工夫。
CodeGen
Codegen 次要是用于保障 JS 代码和 C++ 的 JSI 能够失常通信的动态类型查看器,通过应用类型化的 JS 作为参考起源,CodeGen 将定义能够被 Turbo 模块和 Fabric 应用的接口,另外 Codegen 会在构建时生成 Native 代码,缩小运行时的开销。
skia
当初 RN 也学习了 Flutter
的 skia
渲染, 然而目前它还处于 alpha release
的阶段
这是一个值得期待的方向, 目前该库反对 Image
、Text
、Shader
、Effects
、Shapes
、Animations
等操作
毛病
目前来说 RN 存在的毛病:
- 旧库的问题
目前 RN 的生态环境的确还算能够, 然而也有很多旧仓库, 不止是对于 RN 版本的兼容, 对于各类的业务需要也须要定制
并且短少保护, 有了 issue 也不能及时修理 - 性能方面
RN 的性能, 的确比 webview 好很多, 但也比原生差, 在很简单的场景中就须要应用原生页面 / 组件了 - 控件
RN 目前应用最多的还是 antd 版本的组件库, 他能反对很多场景, 但也是短少人员保护 - 兼容
下面讲了很多 RN 的降级问题, 其实到当初 RN 还没到正式版本1.0.0
, 所以他的很多 API 都会有 break change
总结
文章还有其余的一些问题没有在文章里具体阐明, 比方安卓的打包、签名, iOS 的上架, 罕用的代码、图片优化伎俩, 字体解决方案,
启动屏, 长列表的问题等等, 不过这些也都是细枝末节, 主体的比拟基本上都讲到了
总体来说, RN 目前的状况还是很不错的, 将来和 flutter
的竞争也不虚, 作为一个前端来拓展挪动端方向时 RN
是最好的一个抉择
援用
- https://www.jianshu.com/p/da8…
- https://blog.csdn.net/tyuiof/…
- https://www.infoq.cn/article/…
- https://reactnavigation.org/d…
- https://juejin.cn/post/706373…