第 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 API 去渲染 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 也可以支持其他平台,只需为其提供桥接即可。例如,开发者社区实现了 React Native 的 Windows(https://github.com/Microsoft/react-native-windows)和 Ubuntu(https://github.com/CanonicalLtd/react-native)版本,因此你还可以使用 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 通过桥接的解析,间接调用宿主平台的基础 API 和 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:React与React Native基础元素的比较

React

React Native

<div>

<View>

<span>

<Text>

<li><ul>

<FlastList> 中的子条目

<img>

<Image>

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

<DatePickerIOS
  date={this.state.date}
  mode="time"
/>

{%}

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

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

import { DatePickerIOS } from "react-native";

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

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

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

class HelloMessage extends React.Component {
  render() {
    return React.createElement(
      "div",
      null,
      "Hello ",
      this.props.name
    );
  }
}

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

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

class HelloMessage extends Component {
  render() {
      // 返回标记,而不是调用createElement方法
    return <div>Hello {this.props.name}</div>;
  }
}

// 我们不再需要调用createElement方法
ReactDOM.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 配套的 RNTester 应用中不仅可以查看许多 UI 元素,还能看到许多支持的样式例子。

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

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

// 然后使用它
const txt = (
  <Text style={style}>
    A styled Text
  </Text>);

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

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

2.4 宿主平台API

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

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

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

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

2.5 小结

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

目录