前言

本文主要介绍 react-native(下称 RN) 的入门, 和前端的异同点

选择优势

我们先说说, 为什么很多人会选择使用 RN 、他对应的特性和普通 Web 的区别

  1. 前端资源, 生态的互通

因为使用的语言是 JS 和 react, 对于前端来说可以无缝切换, 并且他还能使用前端的各类包

在 JS 端, 安卓和 iOS 是同一套代码

  1. 热更新

很多选择使用 RN 的原因就是有热更新

这样可以保证用户使用的 js 环境, 可以是较新的, 如果是原生 APP 的更新则需要让用户去应用商店重新下载

  1. 支持原生

RN 通过桥接与原生进行交互, 页面级别的融入原生 APP

他的许多组件, 方法都是调用了原生方法/组件, 相对 webview 来说性能更好

跨端框架横向对比

RN 和 Flutter 的简单对比

环境

无论是 RN 还是 Flutter ,都需要 Android 和 IOS 的开发环境,也就是 JDKAndroid SDKXcode 等环境配置,而不同点在于:

  • RN 需要 npm 、node 、react-native-cli 等配置 。
  • Flutter 需要 flutter sdkAndroid Studio / VSCode 上的 DartFlutter 插件。

针对前端来说 RN 环境相对友好一点

实现原理

AndroidIOS 上,默认情况下

路由管理

在 RN 中常用的路由管理有两个: 一是 React Navigation, 另一个是 react-native-navigation

这两的区别在于, 前者是通过 JS 代码, 通过 monorepo 的组合, 并且通过 react-native-screensreact-native-reanimated v2 等库的优化, 最终形成最终接近原生的体验

而后者使用原生容器来作为路由界面, 如 <ScreenContainer> 或者 <Screen>, 他带来了原生的性能、特性和体验, 但在我们使用此库或者要集成另外的库时会带来一些麻烦

和前端有什么异同

在 APP 中的路由会出现一个概念 堆栈(stack), 这就和 web 中最大的一点不同了

这里用一张图来介绍下:

写给前端的 react-native 入门指南-LMLPHP

当我们到一个新页面时, 上一个页面是不会销毁的(大多数情况), 他是将新页面添加到栈中, 所以在 APP 中, 要经常小心内存的泄漏问题

热更新

这是一个在 RN 中最常用到的以及最大的一个优势功能--热更新

热更新方案

一般来说有三种方案:

关于热更新的注意点:

  • 苹果App允许使用热更新Apple's developer agreement, 为了不影响用户体验,规定必须使用静默更新。 Google Play不能使用静默更新,必须弹框告知用户App有更新。中国的android市场必须采用静默更新(如果弹框提示,App会被“请上传最新版本的二进制应用包”原因驳回)。
  • react-native-code-push只更新资源文件,不会更新java和Objective C,所以npm升级依赖包版本的时候,如果依赖包使用的本地化实现, 这时候必须更改应用版本号, 然后重新编译app发布到应用商店。

一般来说手机热更新的流程:

写给前端的 react-native 入门指南-LMLPHP

其中检测、下载、重启等等, 都是 npm 包 react-native-code-push 提供的 API

APP 更新

在上面我们讲到了, 原生组件更新之后, 就需要重新下载 APP 了, 那么怎么方便地更新呢?

这里就用到了我之前写的一篇文章, 原理如下图:

写给前端的 react-native 入门指南-LMLPHP

原文点击这里

其他不同点

在 APP 中还有很多细节与 Web 端不同, 这里列出几点

debug 方案

开发过 H5 的人应该对于 vconsole 很熟悉, 在 RN 中也有一个 vconsole 的组件, 用来 debug、打印console、查看请求、显示各类信息等等

之前我也封装过一个 RN 的 vconsole 插件: react-native-vconsole, 结合了多个插件的优点

写给前端的 react-native 入门指南-LMLPHP

针对物理键的操作

在安卓机上特有的一种功能, 他就是物理键

用户可以直接点击物理键来进行后退, 当退到最首页的时候, 就需要显示提示 再按一次退出

这就需要对其进行特殊适配:

  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
  }

沉浸式状态栏

在手机上会有状态栏这么一个场景, 这是一个很影响视觉的功能

写给前端的 react-native 入门指南-LMLPHP

可以看到上图中, 在显示信号和电池那一块的变化, 这一部分就是状态栏
在 RN 中时候我们通过此 API 来控制:

 <StatusBar barStyle="dark-content" backgroundColor="#ecf0f1" />

当然, 为了适配多种情况(在 APP 中要在页面的进入, 离开, 其他小功能的变化时修改状态栏), 有时候一些页面是需要透明状态栏, 也需要一些特殊设置

很多时候这个组件并不是直接就用的, 需要包装来适配大多数的页面

版本变化

在 RN 中有几个版本是有很大的 breaking change

  • 0.59-0.60 的升级

在这两个版本直接有很多的 breaking change

其中 iOS 端最大的改动就是, 包变成了 CocoaPods(上面已经讲过)
这让我们的 package 依赖也需要对应的升级(预计会有 50%以上的包升级), 所以影响范围基本就是整个项目

而安卓方面, 则是 link 的方式变化了, 另外就是 build.gradlesettings.gradleAndroidX 的配置的修改

这里要介绍一下官方的升级工具: https://react-native-community.github.io/upgrade-helper/
他能比较对应的版本, 把其中的 changes 显示出来

  • 0.68 的升级

另一个就是 0.67 到 0.68 的升级, 在这个版本变更中, RN 进行了四点调整:

  1. JavaScript Interface(JSI) - 通信的更新
  2. Fabric - 新的渲染系统
  3. Turbo Modules - Native 模块的增强
  4. CodeGen - 静态类型检查器

因为这是一个很底层的修改, 可能会导致现有的所有组件发生变化, 影响范围几乎覆盖全局

新的架构

这里我们就来讲一下 0.68 中的更新具体是什么

JavaScript Interface(JSI)

原有的架构我在上文中已经讲过了, 他存在的一些问题:
目前 RN 使用 Bridge Module 通信(其中还需要数据转换和解码), 发送的消息本质上是异步的, 也就是说如果是即时性比较高的操作, 比如拖拽, 就会出现失帧的情况

而在全新架构中,Bridge 将被一个名为 JavaScript Interface 的模块所代替,它是一个轻量级的通用层,用 C++ 编写,JavaScript Engine 可以使用它直接执行或者调用 native。

原理

写给前端的 react-native 入门指南-LMLPHP

举个简单的例子就是

这就类似于 Web 里 JS 代码可以保存对任何 DOM 元素的引用,并在它上面调用方法:

const container = document.createElement(‘div’);

如果你有 electron 的经验, 原有的通信模式就和 electron 中的主进程和渲染进程的通信一样

Fabric

在老架构中,RN 布局是异步的,这导致在宿主视图中渲染嵌套的 RN 视图,会有布局“抖动”的问题。

而新的架构中和 JSI 一样, 采用的是跨平台的解决方案,共享了核心的 C++ 实现。

简单的解释就是 JSI 的 UI 版本.

写给前端的 react-native 入门指南-LMLPHP

当然还有一些其他的优点:

  • 借助多优先级和同步事件的能力,渲染器可以提高用户交互的优先级,来确保他们的操作得到及时的处理。
  • 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 代码,减少运行时的开支。

写给前端的 react-native 入门指南-LMLPHP

skia

现在 RN 也学习了 Flutterskia 渲染, 但是目前它还处于 alpha release 的阶段

这是一个值得期待的方向, 目前该库支持 ImageTextShaderEffectsShapesAnimations 等操作

缺点

目前来说 RN 存在的缺点:

  1. 旧库的问题
    目前 RN 的生态环境确实还算可以, 但是也有很多旧仓库, 不止是对于 RN 版本的兼容, 对于各类的业务需求也需要定制
    并且缺少维护, 有了 issue 也不能及时修理
  2. 性能方面
    RN 的性能, 确实比 webview 好很多, 但也比原生差, 在很复杂的场景中就需要使用原生页面/组件了
  3. 控件
    RN 目前使用最多的还是 antd 版本的组件库, 他能支持很多场景, 但也是缺少人员维护
  4. 兼容
    上面讲了很多 RN 的升级问题, 其实到现在 RN 还没到正式版本 1.0.0, 所以他的很多 API 都会有 break change

总结

文章还有其他的一些问题没有在文章里详细说明, 比如安卓的打包、签名, iOS 的上架, 常用的代码、图片优化手段, 字体解决方案,
启动屏, 长列表的问题等等, 不过这些也都是细枝末节, 主体的比较基本上都讲到了

总体来说, RN 目前的情况还是很不错的, 未来和 flutter 的竞争也不虚, 作为一个前端来拓展移动端方向时 RN 是最好的一个选择

引用

09-01 10:56