大家好,我是编程小6,很高兴遇见你,有问题可以及时留言哦。
随着 React
的盛行,其移动开发框架 React Native
也收到了广大开发者的青睐,以下简称 RN。通过 RN 我们能够使用 JavaScript 语言来实现跨平台移动应用的开发,打开了前端工程师通往移动平台的大门。用 RN 官方的介绍来概括它的特点就是:Learn once, write anywhere
。
如果你了解 React,那么学习 RN 的话应该会非常轻松。因为 RN 和 React 使用了相同的开发语言 JavaScript 和相同的设计理念 React,在 React 的基础上添加了原生平台的底层支持。这样,不同平台的适配就交由 RN 去处理,而开发者只需要关注 RN 平台应用开发本身。
本文将从 RN 混合开发(与 iOS、Android 平台交互)的原理和实现进行介绍,结合流程图的方式让大家进一步的了解 RN 开发的思想和底层逻辑。
先来看一个使用 RN 实现的简单的 Hello world 展示:
上方我们不难看到一些很熟悉的 React 语法,但除此之外我们还能看到其引入了 react-native
库中的 AppRegistry API 和 Text (文本)组件,这便是 RN 提供给我们用于调用原生平台的 APIs 和 组件,其能够在不同移动设备上实现一致的功能和逻辑。最后展示在 APP 中的便是 Hello world 文本,而至于 AppRegistry API 后面会做相应介绍。
那么看完 Hello world 示例后,我们应该大致知道了 RN 应用的一个结构,我们用图例的方式进行解刨说明,如下图所示:
从图中可以看到,我们整个的 RN 应用可以分为两层展示:
也可以理解为所谓的应用层和底层。应用层通过 JavaScript 桥接层
与底层平台进行交互,获取底层平台的原生 APIs、UI 组件及一些自定义组件等。比如 Hello world 示例中引入的 AppRegistry API 和 Text 组件便是很好的说明。
这样的分层能够使应用层的开发变得简单、高效和跨平台,对于应用的稳定性、运行时的性能来说将和原生平台保持接近。
大致了解完 React Native 应用的结构后,我们不妨再来认识下原生平台是如何调用 React Native 组件的。我们 RN 的代码要跑在原生 APP 中那必然需要原生 APP 加载运行对应的 RN 组件,以实现混合开发和交互的功能。这里就要来介绍下刚刚搁置的 AppRegistry API 了。
一般我们的 RN 项目都会有一个入口文件,比如 index.js(老版本会存在两个:index.ios.js 和 index.android.js)用于注册根组件并提供给原生平台运行。这里的注册根组件就要通过 AppRegistry API 来实现。
我们需要在根组件里调用 AppRegistry 中的 registerComponent
方法进行组件的注册。注册完之后原生平台便可以通过 runApplication
方法来运行注册过的根组件。需要注意的是注册和运行的组件名称两者必须保持一致,这样才能够实现加载对应的组件。比如 Hello world 示例中我们注册的根组件名为 HelloWorldApp,并且注入相应的组件模块。另外同时一个入口文件中,我们也可以注册多个根组件。
刚刚在介绍原生平台调用 RN 组件时提到了加载对应根组件的功能。那么是不是原生平台只有通过不断的调用运行 RN 注册的根组件才能实现不同页面的首次加载呢(这里的加载指原生打开 RN 页面)?答案是否定的。
除了上述通过调用不同的根组件来实现原生打开不同的 RN 界面外(图中第二点),我们还可以调用一个根组件来实现。唯一的区别在于我们需要调用时在 initialProperties 中添加区分不同界面的标识位来渲染不同的组件,就好比在 URL 上携带不同参数跳转到同一路由一样,根据路由上的参数在应用层进行对应组件的渲染。
在 RN 根组件中我们可以通过 this.props
获取原生平台携带过来的参数对象,如示例中的 viewName,再根据 viewName 实现 RN 内部组件的渲染,当然也可以结合 react-navigation
来实现路由模块的切换。至于最终选择哪种方式加载,决定权还是要看业务的划分和功能的定义。相比较而言第一种可能更加灵活和便捷。
在混合开发模式下,我们不可避免的需要和原生平台进行数据的通信,那么在 RN 中,我们如何与原生平台进行通信呢?如何获取原生平台提供的数据或将数据传递给原生平台呢?下面这张图便介绍了这一流程。
在 RN 中,我们可以引用 react-native 模块中的 NativeModules
API 来进行数据通信,调用的方法是 NativeModules.模块名称.接口名称,而原生平台返回数据到 RN 平台是基于回调,代码如下:
import { NativeModules } from 'react-native';
const userInfo = NativeModules.UserInfo; // 获取自定义用户信息模块
console.log(userInfo.userName); // 打印用户名
const router = NativeModules.Router; // 获取自定义路由模块
// 调用原生路由跳转方法
router.openHome('参数', (res) => {
console.log(res); // 打印返回数据
});
通过 NativeModules 我们可以灵活的获取或传递数据给原生平台,同时我们也可以根据业务需要编写不同的 Bridge
方法来实现数据通信模块的封装,比如用户信息模块、路由跳转模块及网络请求模块等。
在 RN 项目中,除了与原生平台通信和交互的功能外,RN 平台自身也需要实现一些数据状态的管理。这里我们还得认识下 Redux 架构。
Redux 是一个用于管理 React 应用状态的容器,在 RN 中也同样适用。其采用单一数据流的方式来实现数据的管理,唯一改变 state 的方法是提交 action 操作。这样的架构使得我们的 RN 项目数据易于维护或扩容,改变数据的流程容易追踪和捕获。需要了解的具体关键字如下:
具体文档可以参考:cn.redux.js.org/
当然你也可以使用其他第三方库实现类似的架构,比如 mobx、dva 等。
除了 Redux 架构,RN 中还加入了 CSS in JS
的概念,将原本关注点分离的理念转移到了关注点混合上,使得我们可以在 JS 中写 CSS 代码,但这并不违背之前关注点分离的理念。
现在随着组件化概念的流行,对从组件层面维护 CSS 样式的需求日益增大,CSS-in-JS 就是在组件内部使用 JavaScript 对 CSS 进行了抽象,可以对其声明和加以维护。这样不仅降低了编写 CSS 样式带来的风险,也让开发变得更加轻松。它和 CSS Modules 的区别是不再需要 CSS 样式文件。
结合 JSX 语法,在 RN 中书写和维护 CSS 变得更加便捷,也是 Web 组件化不断发展的必然产物。
另外,在开发 RN 项目时,官方推荐使用的布局方式是 Flex
布局,因为 Flexbox 可以在不同屏幕尺寸上提供一致的布局结构,这也解决了跨平台布局呈现的问题。
相比我们客户端使用的 Flex 布局,RN 中的 Flex 布局有稍许的不同,比如 flexDirection 的默认值是 column 而不是 row,flex 也只能指定一个数字值等。关于 Flex 布局的介绍可以参考:Flex 布局教程:语法篇、Flex 布局教程:实例篇
最后我们介绍下 RN 中的热部署,这也是选择 RN 开发 APP 的一个重要原因之一。相比传统 APP 更新,大都需要第三方审核的流程,而这个流程可能会很慢或者不及时,遇到需要紧急修复的 bug 无法及时更新而导致直接的经济损失是很常见的问题,而 RN 的热部署可以一定程度上解决或减轻这一问题的影响。那么其实现原理是怎样的呢?
上图左侧部分便展现了用户访问 RN 应用的热部署流程。首先用户访问 APP,APP 会向 RN 服务器请求资源包,如果资源包未更新则读取本地缓存资源,如果开发者为了解决 bug 重新更新了服务器上的资源包,那么 APP 拉去后会缓存起来,待用户下次进入后再进行更新。这便是 RN 热部署的流程。
在本地开发时,我们不难发现当我们在运行起来的 RN 项目中修改代码时,再次从 APP 进入 RN 页面,本地终端会再次加载一次更新后的资源数据,这也是 RN 热部署的体现。
同样线上的热部署则需要将我们打包后的 RN 资源上传到服务器上供 APP 读取来实现。
我们可以手动执行打包、上传发布流程,当然为了减少人为干预,实现前端自动化,我们也可以把这块流程交给构建平台去自动打包部署,这便需要搭建一个后台系统进行管理。
本文介绍了 React Native 混合开发的原理与实现逻辑。只有先了解原理,才能高效的投入项目的开发中,而关于 RN 自身的功能实现大家可以直接阅读官方文档,这里我也额外提供一些关于 RN 的参考资料:
注:本文部分图例参考自《React Native 移动开发实战》一书