第 2 章 React Native 工作原理

第 2 章 React Native 工作原理

在本章中,我们将介绍“桥接”的知识,了解 React Native 在后台是如何工作的,再看看 React Native 组件与 Web 平台上的组件有何区别,以及开发移动应用所需的组件创建与样式的知识。

 如果想学习 React Native 的实战部分,你可以直接跳到下一章。

2.1 React Native是如何工作的

使用 JavaScript 开发移动应用的想法可能有些奇怪。在移动环境中使用 React 是怎样实现的呢?为了更好地理解 React Native 的工作原理,我们首先需要回顾一下 React 的一个特点:Virtual DOM(虚拟 DOM)。

在 React 中,Virtual DOM 就像是一个中间层,介于开发者描述的视图与实际在页面上渲染的视图之间。为了在浏览器上渲染出可交互的用户界面,开发者必须操作浏览器的 DOM(Document Object Model,文档对象模型)。这个操作代价昂贵,对 DOM 的过度操作将会给性能带来严重的影响。React 维护了一个内存版本的 DOM,通过计算得出必要的最小操作并重新渲染。图 2-1 展示了这个工作过程。

图 2-1:执行 Virtual DOM 的计算,减少浏览器 DOM 的重复渲染

对于 Web 环境的 React 而言,大多数的开发者认为 Virtual DOM 的出现主要是为了优化性能。Virtual DOM 确实能提升性能,但它主要的潜力在于提供了强大的抽象能力。在开发者的代码与实际的渲染之间加入一个抽象层,这带来了很多可能性。想象一下,如果 React 能够渲染到浏览器以外的其他平台呢?毕竟,React 已经“理解”了你的应用应该如何展现。

确实,这就是 React Native 的工作原理,如图 2-2 所示。React Native 调用 Objective-C 的 API 去渲染 iOS 组件,调用 Java 接口去渲染 Android 组件,而不是渲染到浏览器 DOM 上。这使得 React Native 不同于那些基于 Web 视图的跨平台应用开发方案。

图 2-2:React 可以渲染到多平台

“桥接”令这一切成为可能,它使得 React 可调用宿主平台开放的 UI 组件。React 组件通过 render 方法返回了描述界面的标记代码。如果是在 Web 平台上,React 最终将把标记代码解析成浏览器的 DOM;而在 React Native 中,标记代码会被解析成特定平台的组件,例如 <View> 将会表现为 iOS 平台上的 UIView

React Native 目前同时支持了 iOS 和 Android 两种平台。由于 Virtual DOM 提供了抽象层,React Native 也可以支持其他平台,只需为其提供“桥接”即可。

2.2 渲染周期

如果你习惯使用 React,那你应该熟悉 React 的生命周期。当 React 在 Web 环境中运行时,渲染周期始于 React 组件挂载之后(图 2-3)。

图 2-3:React 组件挂载过程

接着,React 进入渲染周期并根据需要渲染组件(图 2-4)。

图 2-4:React 组件重新渲染过程

在渲染阶段,React 将开发者在 render 方法中返回的 HTML 标记直接按需渲染到页面上。

至于 React Native,生命周期与 React 基本相同,但渲染过程有一些区别,因为 React Native 依赖于“桥接”,正如先前图 2-2 所示。JavaScript 通过“桥接”的解析,间接调用宿主平台的基础接口和 UI 元素(也就是 Objective-C 或 Java)。由于 React Native 不是在 UI 主线程运行,它可以在不影响用户体验的前提下执行这些异步调用。

2.3 在React Native中创建组件

所有的 React 代码都存在于 React 组件中。React Native 组件与 React 组件大体上一致,但在渲染和样式方面有一些重要的区别。

2.3.1 编写视图

当编写 Web 环境的 React 时,视图最终需要渲染成普通的 HTML 元素(<div><p><span><a> 等)。而在 React Native 中,所有的元素都将被平台特定的 React 组件所替换(见表 2-1)。最基础的组件是能跨平台的 <View>,这是一个简单且灵活的 UI 元素,类似于 <div> 标签。例如,在 iOS 中,<View> 组件被渲染成 UIView,而在 Android 平台则被渲染成 View

表2-1:Web与React Native基础元素的比较

React

React Native

<div>

<View>

<span>

<Text>

<li><ul>

<ListView>

<img>

<Image>

其他组件则是平台特定的。例如,<DatePickerIOS> 组件显然将被渲染成 iOS 标准的日期选择器。下面是从 UIExplorer 示例应用中摘录出来的代码,用来展示 iOS 日期选择器。正如你期待的那样,用法相当直观:

<DatePickerIOS
  date={this.state.date}
  mode="date"
  timeZoneOffsetInMinutes={this.state.timeZoneOffsetInHours * 60}
/>

以上代码将被渲染成一个标准的 iOS 日期选择器(如图 2-5 所示)。

图 2-5:DatePickerIOS,顾名思义,是 iOS 特有的组件

由于我们所有的 UI 元素均为 React 组件,而不是像 <div> 这样基础的 HTML 元素,因此我们在使用每一个组件之前,都需要明确地进行导入。例如,我们可以这样导入 <DatePickerIOS> 组件:

var React = require('react-native');
var {
  DatePickerIOS
} = React;

UIExplorer 应用是一个打包的标准 React Native 示例(https://github.com/facebook/react-native#examples),可以让你查看它所支持的所有 UI 元素,建议你体验一下其中包含的各种元素。除此之外,它还讲解了许多关于样式和交互的知识。

 平台特定的元素和接口在官方文档中有特殊的标签,通常使用平台名称作为后缀,例如:<SwitchAndroid><SwitchIOS>

这些组件因平台而不同,因此在使用 React Native 时,如何组织你的组件变得尤为重要。在 Web 环境的 React 中,我们通常混合各种 React 组件,有的组件控制逻辑及其子组件,而有的则渲染原生标记。在使用 React Native 时,如果你想复用代码,那么这些组件的抽象分离就至关重要。当然,如果一个组件渲染 <DatePickerIOS> 元素,那它显然不能在 Android 平台复用了。不过,如果一个组件封装的是关联逻辑,那就可以被复用。因此,视图组件可以根据平台进行替换选择。如果你乐意的话,还可以为组件设计平台特定的版本,例如 picker.ios.js 和 picker.android.js。我们将在 4.4.2 节具体讲解。

2.3.2 使用JSX

与 React 中相一致,React Native 也是通过编写 JSX 来设计视图,并将视图标记和控制逻辑组合在一起成为一个文件。React 刚问世的时候,JSX 在业界引起了强烈的反响。对于许多 Web 开发者来说,根据技术进行文件分离是理所当然的:保持 CSS、HTML 和 JavaScript 文件的独立。然而将标记、控制逻辑,甚至样式合并成一门语言难免会让人觉得混乱。

JSX 认为减少心智负担比文件分离更有用。在 React Native 中,这一点表现得更为明显。在一个没有浏览器的世界里,每个组件的样式、标记和行为被统一成单个文件的形式将会更有意义。因此,React Native 中的 .js 文件实际上就是 JSX 文件。如果你正在使用原生 JavaScript 编写 Web 环境的 React,你应该想转换到 JSX 语法来编写 React Native 项目。

假如你之前从未使用过 JSX,也不用太担心,它非常简单。举个例子,用纯 JavaScript 编写 React 组件的代码看起来如下:

var HelloMessage = React.createClass({
  displayName: "HelloMessage",

  render: function render() {
    return React.createElement(
      "div",
      null,
      "Hello ",
      this.props.name
    );
  }
});

React.render(React.createElement(HelloMessage, { name: "Bonnie" }), mountNode);

我们可以通过使用 JSX 使其更为简洁,使用类 XML 标记来代替调用 React.createElement 方法并传入一组 HTML 属性的做法。

var HelloMessage = React.createClass({
  render: function() {
    // 返回标记,而不是调用createElement方法。
    return <div>Hello {this.props.name}</div>;
  }
});

// 我们不再需要调用createElement方法。
React.render(<HelloMessage name="Bonnie" />, mountNode);

以上两段代码最终都会在页面上被渲染为下面的 HTML:

<div>Hello Bonnie</div>

2.3.3 原生组件的样式

在 Web 中,正如使用 HTML 标签一样,我们仍然使用 CSS 来为 React 组件添加样式。不论你是否喜欢 CSS,它都已经成为 Web 开发不可或缺的一部分。React 通常不影响我们编写 CSS 的方式,它确实让内联样式(清晰且实用)的使用和样式的动态创建(通过 propsstate)更加容易。除此之外,React 基本上不关心我们是如何处理样式的。

非 Web 平台上有大量的方法来处理布局和样式。但庆幸的是,我们在使用 React Native 时,只需要用一种标准的方法来处理样式。React 和宿主平台之间的“桥接”包含了一个缩减版 CSS 子集的实现。这个 CSS 子集主要通过 flexbox 进行布局,做到了尽量简单化,而不是去实现所有的 CSS 规则。有别于 Web 平台,CSS 的支持程度因浏览器而不同,React Native 则做到了样式规则的一致。在 React Native 配套的 UIExplorer 应用(https://github.com/facebook/react-native/tree/master/Examples/UIExplorer) 中不仅可以查看许多 UI 元素,也能看到许多支持的样式例子。

React Native 也坚持使用内联样式,通过 JavaScript 对象进行样式组织。React 团队先前也提倡在 Web 环境的 React 中使用内联样式。如果你曾经在 React 中使用过内联样式,那么下面的语法你一定非常熟悉了:

// 定义一个样式。
var style = {
  backgroundColor: 'white',
  fontSize: '16px'
};

// 然后使用它。
var tv = (
  <Text style={style}>
    A styled Text
  </Text>);

为了让样式更容易管理,React Native 为我们提供了创建和扩展样式的工具。我们将在第 5 章探索这部分内容。

内联样式的写法让你觉得难受?它基于 Web 背景而产生,被公认为标准实践的一个突破。相对于样式表来说,使用样式对象可能需要一些思维上的调整,从而改变你编写样式的方法。然而,在 React Native 中,这是一个实用的转变。我们将在第 5 章讨论编写样式的最佳实践和工作流程,你很难不对它的实际使用效果感到惊讶!

2.4 宿主平台接口

使用 Web 环境的 React 与 React Native 最大的不同应该就在于宿主平台的接口了。在 Web 中,我们遇到的问题通常是由于采纳标准的不一致和碎片化引起的,并且大多数浏览器只支持部分核心的特性。然而在 React Native 中,平台特定的接口在提供优秀原生的用户体验方面发挥了巨大的作用。当然,要考虑的方面还有很多。接口囊括了许多功能,从数据存储到地理服务,以及操控硬件设备(如摄像头)等。由于 React Native 可以扩展到其他平台,我们有机会看到各种不同的平台接口,例如,React Native 和虚拟现实头盔之间的接口会是什么样的呢?

默认情况下,iOS 和 Android 版本的 React Native 支持许多常用的特性,甚至可以支持任何异步的本地接口,本书中将会涉及这些内容。React Native 让宿主平台接口的使用变得更加简单和直观,你可以在其中自由地试验。同时,务必思考一下怎样做才符合目标平台的体验,并在心里设计好交互过程。

毋庸置疑,React Native 的“桥接”不可能暴露宿主平台全部的接口。如果你需要使用一个未支持的特性,完全可以自己动手添加到 React Native 中。另外,如果其他人已经集成,那就更好了,所以应该及时查看社区是否已经或即将支持某些特性。我们将在第 7 章讨论这部分知识。

值得注意的是,使用平台接口也会对代码复用有帮助。同时,实现平台特定功能的 React 组件也是平台特定的。隔离和封装这些组件将会给你的应用带来更大的灵活性。当然,这对你开发 Web 应用同样奏效,如果你想共享 React 和 React Native 的代码,请记住像 DOM 这样的接口在 React Native 中并不存在。

2.5 小结

使用 React Native 为移动应用编写组件与 Web 环境的 React 相比有一些不同。JSX 是强制使用的,并且我们通过创建组件的方式来开发基本模块,比如 <View> 代替了 <div> 这个 HTML 元素。样式方面也不太一样,我们通过使用 CSS 的子集编写内联语句的方式来编写样式。当然,这些调整都能得到很好的把控。在下一章中,我们将动手编写第一个移动应用!

目录