前言
本文主要介绍 react-native(下称 RN) 的入门, 和前端的异同点
选择优势
我们先说说, 为什么很多人会选择使用 RN 、他对应的特性和普通 Web 的区别
- 前端资源, 生态的互通
因为使用的语言是 JS 和 react, 对于前端来说可以无缝切换, 并且他还能使用前端的各类包
在 JS 端, 安卓和 iOS 是同一套代码
- 热更新
很多选择使用 RN 的原因就是有热更新
这样可以保证用户使用的 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 上,默认情况下
路由管理
在 RN 中常用的路由管理有两个: 一是 React Navigation
, 另一个是 react-native-navigation
这两的区别在于, 前者是通过 JS 代码, 通过 monorepo
的组合, 并且通过 react-native-screens
和 react-native-reanimated v2
等库的优化, 最终形成最终接近原生的体验
而后者使用原生容器来作为路由界面, 如 <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
APP 更新
在上面我们讲到了, 原生组件更新之后, 就需要重新下载 APP 了, 那么怎么方便地更新呢?
这里就用到了我之前写的一篇文章, 原理如下图:
其他不同点
在 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-community.github.io/upgrade-helper/
他能比较对应的版本, 把其中的 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。
原理
举个简单的例子就是
这就类似于 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
是最好的一个选择