# React Native
目前我们常用的移动端应用开发方式主要为原生方式、混合开发这两种,其中原生开发运行效率高,流畅,用户体验好,可以做各种复杂的动画效果。不过我们需要去掌握不同开发平台上特定的开发语言与内建的组件框架,譬如在 Android 开发中开发者需要掌握 Java,而 iOS 开发中开发者需要掌握 Objective-C 或者 Swift;并且由于平台之间的独立性,代码无法在其他平台上运行,无法做到跨平台。而传统混合开发方式则以 Cordova 与 Ionic 为代表,定义好原生功能与 Web 界面之间的协议,拦截特定的 URL Schema 进行原生功能的调用,应用则调用 Web 提供的 JavaScript 方法,将数据回传给 Web 界面。这种方式可以满足一套代码到处运行的目标,不过受限于 UIWebView 等容器本身的限制,其性能体验与原生应用不可同日而语。实际上无论哪一种开发方式都致力于解决如下几个问题:找到一种能达到或者接近原生体验的开发方式、找到一种一套代码能在各个平台上运行,达到代码复用的目的、能够以热更新或者类似的方式进行快速问题修复。
随着 React 在 Web 领域取得的巨大成功,Facebook 继续推出 React Native 以创建接近原生性能的跨平台移动应用,其倡导的 Learn Once,Write Anywhere 的概念同时兼顾了性能与快速迭代的需求。React 的核心设计理念其提供了抽象的、平台无关的组件定义范式,然后通过 react-dom 等库将其渲染到不同的承载体上;这些承载可以是服务端渲染中的字符串,或者客户端渲染中的 DOM 节点。在 React Native 中,我们只需要了解 React 组件定义规范与语法,然后利用 React Native 这个新的渲染库将界面渲染到原生界面组件中。在未来的客户端开发中,负责与用户交互以及存储这一部分建议采用原生的代码,而对于逻辑控制这边,建议是采用 JavaScript 方式实现。
React Native 本质上是用 JSX 的语法风格编写原生的应用,它本质上还是跨平台编译性质的,并没有提供完整的类似于 WebView 那样的上下文,并且大量的 HTML 元素也是不可以直接应用的。React Native 只是借用了 HTML 的语法风格,并且提供了 JavaScript 与原生的桥接。React Native 使用了所谓的 Native Widget APIs 来调用底层的操作系统相关代码,并且处于性能的考虑它会异步批量地调用原生平台接口,其整体架构如下所示:
![](https://www.safaribooksonline.com/library/view/react-and-react/9781786465658/graphics/image_12_001.jpg)
# 快速开始
- Architecture(应用架构)
当使用 react-native 命令创建新的项目时,调用的即https://github.com/facebook/react-native/blob/master/react-native-cli/index.js这个脚本。当使用```react-native init HelloWorld```创建一个新的应用目录时,它会创建一个新的 HelloWorld 的文件夹,包含如下列表:
> HelloWorld.xcodeproj/
>
> Podfile
>
> iOS/
>
> Android/
>
> index.ios.js
>
> index.android.js
>
> node_modules/
>
> package.json
React Native 最大的卖点在于(1)可以使用 JavaScript 编写 iOS 或者 Android 原生程序。(2)应用可以运行在原生环境下并且提供流畅的 UI 与用户体验。众所周知,iOS 或者 Android 并不能直接运行 JavaScript 代码,而是依靠类似于 UIWebView 这样的原生组件去运行 JavaScript 代码,也就是传统的混合式应用。整个应用运行开始还是自原生开始,不过类似于 Objective-C/Java 这样的原生代码只是负责启动一个 WebView 容器,即没有浏览器界面的浏览器引擎。
而对于 React Native 而言,并不需要一个 WebView 容器去执行 Web 方面的代码,而是将所有的 JavaScript 代码运行在一个内嵌的 JavaScriptCore 容器实例中,并最终渲染为高级别的平台相关的组件。这里以 iOS 为例,打开 HelloWorld/AppDelegate.m 文件,可以看到如下的代码:
```objective-c
.....................
RCTRootView *rootView = [[RCTRootView alloc]
initWithBundleURL:jsCodeLocation
moduleName:@"HelloWorld"
launchOptions:launchOptions];
.....................
```
AppDelegate.m 文件本身是 iOS 程序的入口,相信每一个有 iOS 开发经验的同学都不会陌生,这也是本地的 Objective-C 代码与 React Native 的 JavaScript 代码胶合的地方。而这种胶合的关键就是 RCTRootView 这个组件,可以从 React 声明的组件中加载到 Native 的组件。RCTRootView 组件是一个由 React Native 提供的原生的 Objective-C 类,可以读取 React 的 JavaScript 代码并且执行,除此之外,也允许我们从 JavaScript 代码中调用 iOS UI 的组件。
到这里我们可以看出,React Native 并没有将 JavaScript 代码编译转化为原生的 Objective-C 或者 Swift 代码,但是这些在 React 中创建的组件渲染的方式也非常类似于传统的 Objective-C 或者 Swift 创建的基于 UIKit 的组件,并不是类似于 WebView 中网页渲染的结果。
这种架构也就很好地解释了为什么可以动态加载我们的应用,当我们仅仅改变了 JS 代码而没有原生的代码改变的时候,不需要去重新编译。RCTRootView 组件会监听`Command+R`组合键然后重新执行 JavaScript 代码。
- Virtual Dom 的扩展
Virtual Dom 是 React 的核心机制之一,对于 Virtual Dom 的详细说明可以参考笔者 React 系列文章。在 React 组件被用于原生渲染之前,Clipboard 已经将 React 用于渲染到 HTML 的 Canvas 中,可以查看[render React to the HTML element](https://github.com/Flipboard/react-canvas)这篇文章。对于 React Web 而言,就是将 React 组件渲染为 DOM 节点,而对于 React Natively 而言,就是利用原生的接口把 React 组件渲染为原生的接口,其大概示意图可以如下:
![React Native behaves much like React, but can render to many different targets.](https://www.safaribooksonline.com/library/view/learning-react-native/9781491929049/assets/render-targets.png)
虽然 React 最初是以 Web 的形式呈现,但是 React 声明的组件可以通过*bridge*,即不同的桥接器转化器会将同样声明的组件转化为不同的具体的实现。React 在组件的 render 函数中返回具体的平台中应该如何去渲染这些组件。对于 React Native 而言,`<View/>`这个组件会被转化为 iOS 中特定的`UIView`组件。
- 载入 JavaScript 代码
React Native 提供了非常方便的动态调试机制,具体的表现而言即是允许以一种类似于中间件服务器的方式动态的加载 JS 代码,即
```objective-c
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle"];
```
另一种发布环境下,可以将 JavaScript 代码打包编译,即`npm build`:
```objective-c
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
```
如果在 Xcode 中直接运行程序会自动调用`npm start`命令来启动一个动态编译的服务器,如果没有自动启动可以手动的使用`npm start`命令,就如定义在 package.json 文件中的,它会启动 node_modules/react-native/packager/packager.sh 这个脚本。
### React Native 中的现代 JavaScript 代码
从上文中可以看出,React Native 中使用的是所谓的 JSX 以及大量的 ES6 的语法,在打包器打包之前需要将 JavaScript 代码进行一些转换。这是因为 iOS 与 Android 中的 JavaScript 解释器目前主要还是支持到了 ES5 版本,并不能完全识别 React Native 中提供的语法或者关键字。当然,并不是说我们不能使用 ES5 的语法去编写 React Native 程序,只是最新的一些语法细则规范可以辅助我们快速构建高可维护的应用程序。
譬如�