深入了解 React Native(初学者教程)
已发表: 2022-03-11当 React Native 发布时,第一反应是非常积极的。 传统上,当我们考虑移动领域的 Web 技术时,会想到 Apache Cordova 之类的东西,它允许我们将网站或 Web 应用程序打包为移动平台的应用程序。 在本初学者教程中,我们将了解 React Native 的架构、React Native 背后的理念,以及它与同一领域的其他解决方案有何不同。 在本文的最后,我们将把一个 React “Hello World” 应用程序转换成一个 React Native 应用程序。
让我们首先说 React Native 是一项相对较新的技术。 它自 2015 年 3 月起正式上市,自当年年初以来一直处于内测阶段,并在此之前在 Facebook 内部使用了一段时间。 “罗马不是一天建成的”这句话通常也适用于技术。 像grunt
这样的工具和像 Node.js 这样的平台需要数年时间才能成熟。 在网络世界中,事情发展得很快,每天都有大量的框架、包和工具出现,开发人员往往会变得更加怀疑,不想赶上每一个炒作的潮流,只是意识到这一点他们最终陷入了供应商锁定的境地。 我们将深入探讨 React Native 的特殊之处,为什么它是一项值得研究的技术,并涵盖一些并非都是独角兽和彩虹的例子。
引擎盖下
在谈论移动网络技术时,可用的解决方案通常属于以下类别之一。
在移动 Web 浏览器中捆绑 Web 应用程序
Web 应用程序存在于移动浏览器中,通常称为 WebView。 无需任何重大重构,网站或 Web 应用程序就可以在移动设备上运行。 我们可能需要考虑移动浏览器事件,例如点击或收听设备方向变化和较小的屏幕以获得完整的用户体验,但我们有一个工作量很小的移动版本。 Cordova/PhoneGap 是该类别中最受欢迎的选项。 不幸的是,这个选项有一个很大的缺点:在某些情况下,使用 Cordova 开发的应用程序比本机应用程序慢得多,尤其是对于图形密集型应用程序。 在其他情况下,移动操作系统实际上并未提供移动浏览器中可用的所有 WebView 功能。 用户体验也可能不同于原生应用程序; 这可能是由于应用程序或平台本身造成的。 这个问题的范围可能从滚动条感觉不一样到点击元素时有明显的延迟。
编译为原生技术
一个完全不同的解决方案是最终创建一个本机代码库。 这是通过将原始源代码转换为另一种编程语言来实现的。 我们用原生性能换取具有一些不确定性的抽象层。 在闭源解决方案的情况下,我们甚至不确定引擎盖下发生了什么以及我们正在处理什么样的黑匣子。 在其他情况下,我们不确定下一次移动操作系统更新将破坏我们的代码的程度,以及修复或更新何时可用。 此类的一个流行示例是 Haxe。
使用 JavaScript 层
在这里,我们使用移动环境的 JavaScript 引擎并在那里执行我们的 JavaScript。 原生控件映射到 JavaScript 对象和函数,所以当我们调用一个名为fancyButtonRightHere()
的函数时,屏幕上会出现一个按钮。 NativeScript 或 Appcelerator Titanium 是此类的知名示例。
React Native 可以归为第三类。 对于 iOS 和 Android 版本,React Native 在底层使用 JavaScriptCore,这是 iOS 上的默认 JavaScript 引擎。 JavaScriptCore 也是 Apple Safari 浏览器中的 JavaScript 引擎。 如果愿意,OS X 和 iOS 开发人员实际上可以直接与它交互。
一个很大的区别是 React Native 在单独的线程中运行 JavaScript 代码,因此用户界面不会阻塞,动画应该是丝滑的。
React 是关键特性
值得注意的是,React Native 中的“React”并不是偶然出现的。 对于 React Native,我们需要了解 React 到底提供了什么。 以下概念在 React 和 React Native 中的工作方式相同,尽管这些代码示例是为在浏览器中运行而定制的。
单一渲染入口点
当我们看一个简单的 React 组件时,我们可能会注意到的第一件事是该组件具有render
功能。 事实上,如果组件内部没有定义渲染函数,React 会抛出错误。
var MyComponent = function() { this.render = function() { // Render something here }; };
特殊之处在于,我们不会在这里处理 DOM 元素,而是返回一个基于 XML 的构造,该构造表示将在 DOM 中呈现的内容。 这种基于 XML 的结构称为 JSX。
var MyComponent = function() { this.render = function() { return <div className="my-component">Hello there</div>; }; };
一个特殊的 JSX 转换器获取所有看起来像 XML 的代码并将其转换为函数。 这是转换后的组件的样子:
var MyComponent = function() { this.render = function() { return React.createElement("div", { className: "my-component" }, "Hello there"); }; };
最大的优势是通过快速查看组件,我们总是知道它应该做什么。 例如,一个<FriendList />
组件可能会渲染多个<Friend />
组件。 除了在render
函数内部之外,我们无法在其他任何地方渲染我们的组件,因此我们永远不会担心我们不知道渲染组件的确切来源。
单向数据流
为了构建组件的内容,React 提供了属性或 props 的简称。 与 XML 属性类似,我们将 props 直接传递给组件,然后可以在构造的组件内使用 props。
var Hello = function(props) { this.render = function() { return <div className="my-component">Hello {props.name}</div>; }; }; var Greeter = function() { this.render = function() { return <Hello name="there" /> } };
这导致我们的组件处于树状结构中,并且我们只允许在构造子元素时传递数据。
重新渲染更改
除了 props,组件还可以有一个内部状态。 这种行为最突出的例子是点击计数器,它会在按下按钮时更新其值。 点击次数本身将保存在状态中。
每个 prop 和 state 更改都会触发组件的完整重新渲染。
虚拟 DOM
现在,当 props 或 state 发生变化时,一切都被重新渲染,React 本身为什么表现得那么好? 神奇的成分是“虚拟 DOM”。 每当需要重新渲染某些东西时,都会生成更新后的 DOM 的虚拟表示。 虚拟 DOM 由在组件树之后建模的元素的轻量表示组成,使得生成它们的过程比生成真实 DOM 元素的过程更加高效。 在将更改应用到真实 DOM 之前,会进行检查以确定更改发生在组件树中的确切位置,创建差异,并且仅应用那些特定的更改。
本 React Native 教程入门
为了开发 React Native,初学者需要设置一些先决条件。 由于 iOS 是第一个受支持的平台,也是我们在本教程中介绍的平台,因此我们需要 macOS 和 Xcode,至少版本 6.3。 还需要 Node.js。 使用brew install watchman
watchman 通过 Brew 包管理器安装 Watchman 有帮助。 虽然这不是必需的,但在处理我们的 React Native 项目中的大量文件时它会有所帮助。
要安装 React Native,我们只需要使用npm install -g react-native-cli
安装 React Native 命令行应用程序。 然后调用react-native
命令帮助我们创建一个新的 React Native 应用程序。 运行react-native init HelloWorld
会创建一个名为HelloWorld
的文件夹,可以在其中找到样板代码。
转换 React 应用程序
鉴于 React 是来自 React 库的关键特性和核心原则,让我们来看看将最小的 React “Hello World” 应用程序转换为 React Native 应用程序需要什么。
我们在这个代码示例中使用了一些 ES2015 特性,特别是类。 坚持使用React.createClass
或者使用类似于流行的模块模式的函数形式是完全可行的。
var React = require('react'); class HelloThere extends React.Component { clickMe() { alert('Hi!'); } render() { return ( <div className="box" onClick={this.clickMe.bind(this)}>Hello {this.props.name}. Please click me.</div> ); } } React.render(<HelloThere name="Component" />, document.getElementById('content'));
第 1 步:拥抱 CommonJS 模块
在第一步中,我们需要更改要求 React 模块使用react-native
。
var React = require('react-native'); class HelloThere extends React.Component { clickMe() { alert('Hi!'); } render() { return ( <div className="box" onClick={this.clickMe.bind(this)}>Hello {this.props.name}. Please click me.</div> ); } } React.render(<HelloThere name="Component" />, document.getElementById('content'));
在开发 React Web 应用程序时,工具管道的一部分通常是 React Native 的一个组成部分。
第 2 步:没有 DOM
毫不奇怪,移动设备上没有 DOM。 在我们之前使用<div />
的地方,我们需要使用<View />
并且在我们使用<span />
的地方,我们这里需要的组件是<Text />
。
import React from 'react'; import {View, Text, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert('hi!'); } render() { return ( <View className="box" onClick={this.clickMe.bind(this)}>Hello {this.props.name}. Please click me.</View> ); } } React.render(<HelloThere name="Component" />, document.getElementById('content'));
虽然将文本直接放在<div />
元素中非常方便,但在原生世界中,文本不能直接放在<View />
中。 为此,我们需要插入一个<Text />
组件。
import React from 'react'; import {View, Text, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert('hi!'); } render() { return ( <View className="box" onClick={this.clickMe.bind(this)}> <Text>Hello {this.props.name}. Please click me.</Text> </View> ); } } React.render(<HelloThere name="Component" />, document.getElementById('content'));
第 3 步:内联样式是必经之路
React Native 允许我们使用 Flexbox 建模,而不是使用我们在网络世界中非常熟悉的float
和inline-block
。 有趣的是 React Native 不使用 CSS。
import React from 'react'; import {View, Text, StyleSheet, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert('hi!'); } render() { return ( <View style={styles.box} onClick={this.clickMe.bind(this)}> <Text>Hello {this.props.name}. Please click me.</Text> </View> ); } } var styles = StyleSheet.create({ box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 } }); React.render(<HelloThere name="Component" />, document.getElementById('content'));
使用内联样式对初学者来说似乎很困惑。 这类似于 React 开发人员在面对 JSX 和之前使用 Handlebars 或 Jade 等模板引擎时必须经历的过渡。
这个想法是我们在使用 CSS 的方式中没有全局样式表。 我们直接在组件级别声明样式表,因此我们拥有查看组件功能、它创建的布局以及它应用的样式所需的所有信息。
import React from 'react'; import {Text} from 'react-native'; var Headline = function(props) { this.render = () => <Text style={headlineStyle.text}>{props.caption}</Text>; }; var headlineStyles = StyleSheet.create({ text: { fontSize: 32, fontWeight: 'bold' } }); module.exports = Headline;
第 4 步:处理事件
相当于点击网页是点击移动设备上的一个元素。 让我们更改我们的代码,以便在我们点击元素时弹出“警报”。
import React from 'react'; import {View, Text, StyleSheet, TouchableOpacity, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert("Hi!") } render() { return ( <TouchableOpacity onPress={this.clickMe()}> <View style={styles.box}> <Text>Hello {this.props.name}. Please click me.</Text> </View> </TouchableOpacity> ); } } var styles = StyleSheet.create({ box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 } }); React.render(<HelloThere name="Component" />, document.getElementById('content'));
我们需要显式地使用触发事件的元素,而不是直接在<View />
组件上可用的事件,在我们的例子中是按下视图时的触摸事件。 有不同类型的可触摸组件可用,每一种都提供不同的视觉反馈。

第 5 步:跨平台自定义行为
通过访问Platform.OS
的值,可以检测 React Native 应用程序在哪个平台上运行。 假设在上面的示例中,我们希望根据我们运行的平台显示不同的警报消息。 我们可以这样做:
... clickMe() { var message = ''; if(Platform.OS == 'ios') { message = 'Welcome to iOS!'; } else if(Platform.OS == 'android') { message = 'Welcome to Android!'; } Alert.alert(message); } ...
或者,也可以使用select
方法,它提供类似 switch 的语法:
… clickMe() { Alert.alert(Platform.select({ ios: 'Welcome to iOS!', android: 'Welcome to Android!' }) ); } ...
第 6 步:自定义字体和react-native link
为了添加自定义字体,我们需要跳过一些环节。 首先,确保字体全名和字体文件名相同:iOS 将使用字体全名来选择字体,而 Android 使用文件名。
因此,如果您的字体的全名是myCustomFont
,请确保字体的文件名是myCustomFont.ttf
。
之后,我们需要创建一个 assets 文件夹并将 npm 指向它。 我们可以通过首先在应用程序根目录的assets/fonts
下创建文件夹来做到这一点。 任何其他目录都可以,但这是用于字体目录的常规名称。
我们可以通过在 React 的 npm 集成部分 rnpm 下添加Assets
属性来告诉 npm 我们在哪里拥有我们的资产:
"rnpm": { "Assets": [ "./assets/fonts/" ] }
完成所有这些之后,我们终于可以运行react-native link
。 这会将字体复制到正确的目录,并将必要的 xml 添加到 iOS 上的 info.plist。
完成后,我们可以通过在任何样式表中通过其全名引用它来使用我们的字体。 让我们在Text
元素上使用它:
import React from 'react'; import {View, Text, StyleSheet, TouchableOpacity, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert("Hi!") } render() { return ( <TouchableOpacity onPress={this.clickMe()}> <View style={styles.box}> <Text style={styles.message}>Hello {this.props.name}. Please click me.</Text> </View> </TouchableOpacity> ); } } var styles = StyleSheet.create({ box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 }, message: { fontFamily: 'myCustomFont' } }); React.render(<HelloThere name="Component" />, document.getElementById('content'));
第 7 步:四处移动
React Native 使用与 Flexbox 相同的规则来布局组件。 假设我们想将按钮放置在屏幕底部:让我们用容器View
包装TouchableOpacity
:
<View style={styles.container}> <TouchableOpacity onPress={this.clickMe.bind(this)}> <View style={styles.box}> <Text style={styles.message}>Hello {this.props.name}. Please click me.</Text> </View> </TouchableOpacity> </View>
现在让我们定义container
样式以及其他已定义的样式:
container: { flex: 1, justifyContent: 'center', alignItems: 'center' }
让我们关注justifyContent
和alignItems
。 这两个属性分别控制组件如何沿其主轴和辅助轴对齐。 默认情况下,主轴是垂直轴,次轴是水平轴(您可以通过将flexDirection
属性设置为row
来更改它)。
justifyContent
有六个可能的值,可以设置为:
-
flex-start
会将所有元素放在一起,在组件边界框的开头。 -
flex-end
会将所有元素定位在末尾。 -
center
会将所有元素定位在边界框的中心。 -
space-around
将均匀分布组件,并将组件在其创建的边界框中居中。 -
space-evenly
也会均匀分布组件,但它会尝试在组件和其他边界之间留出等量的空间。 -
space-between
将通过保持相邻组件之间的间距相等来分散组件。
alignItems
可以设置为四个可能的值: flex-start
、 flex-end
、 center
和stretch
。 前三个的行为与justifyContent
,而stretch
将设置组件沿轴占据所有可用空间,以便完全填充轴。
因此,由于我们希望TouchableOpacity
显示在底部并沿水平轴居中,我们可以像这样更改样式:
container: { flex: 1, justifyContent: 'flex-end', alignItems: 'center' }
有关justifyContent
和alignItems
值的更多信息,请参见此处和此处。
第 8 步:注册应用程序
在浏览器上使用 React 进行开发时,我们只需要定义一个挂载点,调用React.render
,然后让 React 发挥它的魔力。 在 React Native 中,这有点不同。
import React from 'react'; import {View, Text, StyleSheet, TouchableOpacity, Alert, Platform} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert(Platform.select({ ios: 'Welcome to iOS!', android: 'Welcome to Android!' })); } render() { return ( <View style={styles.container}> <TouchableOpacity onPress={this.clickMe.bind(this)}> <View style={styles.box}> <Text style={styles.message}>Hello {this.props.name}. Please click me.</Text> </View> </TouchableOpacity> </View> ); } } var styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'flex-start', alignItems: 'center' }, box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 }, message: { fontFamily: 'myCustomFont' } }); var MainComponent = function() { this.render = function() { return <HelloThere name="Component" />; } }; AppRegistry.registerComponent('MainComponent', function() { return MainComponent; });
我们必须为 Objective-C 方面的事物注册组件,这是使用AppRegistry
对象完成的。 我们提供的名称必须与 Xcode 项目中的名称相匹配。
我们的 Hello World React Native 应用程序的代码行数明显多于其 Web 应用程序,但另一方面,React Native 将关注点分离更进一步,特别是因为样式是使用组件定义的。
附带说明一下,我们不应该在render
方法中将clickMe
方法重新绑定到this
上下文,尤其是当我们的 React (Native) 应用程序变得更加复杂时。 它会在每次渲染调用时重新绑定方法,这可能会变得很多。 另一种方法是在构造函数中绑定方法。
运行应用程序
要运行应用程序,我们需要将index.ios.js
文件的内容替换为上一步转换后的应用程序的代码。 然后我们只需要打开 Xcode 项目并按下大运行按钮。 首先,会打开一个带有 React Native 服务器的终端,然后会出现模拟器窗口。 React Native 服务器创建一个包,然后本机应用程序将获取该包。 这允许类似 Web 开发的快速开发周期,其中更改将几乎立即反映在模拟器中。
对于 Android,将以下内容添加到您的package.json
文件中的scripts
下就足够了:
"android-linux": "react-native bundle --platform android --dev false --entry-file index.ios.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/ main/res && react-native run-android"
然后运行npm run android-linux
。 确保事先存在android/app/src/main/assets
目录。
终端弹出后,我们的应用程序将显示在模拟器中。 按 CMD+D 将显示开发菜单。 单击该框将显示警报。 iOS版本:
Android 呈现如下内容:
对于分发,拥有一个指向本地开发服务器的应用程序对我们来说是行不通的。 出于这个原因,我们可以在 React Native 服务器没有运行时使用命令react-native bundle
创建包。 在这种情况下,我们需要更新AppDelegate
的didFinishLaunchingWithOptions
方法以使用离线包。
这个示例应用程序也可以在 Github 上找到。
使用 React Native
另一件值得一提的事情是,我们不仅将 React 概念和 JavaScript 用于我们的移动应用程序,而且一些 Web 开发人员习惯使用的工作流也可用于 React Native。 当来自 Web 开发时,我们习惯于开发工具、检查元素和实时重新加载。
React Native 的工作方式是将我们所有的 JavaScript 文件放在一个包中。 此捆绑包由服务器提供或与应用程序捆绑在一起。 第一个对于模拟器中的开发非常有用,因为我们可以启用实时重新加载。 React 提供的开发者菜单绝不像 Chrome 开发者工具那样强大,但它提供了一种非常类似于 web 的开发者体验,可以使用 Chrome(或 Safari)开发者/调试器工具进行实时重新加载和调试。
Web 开发人员熟悉 JSFiddle 或 JSBin,这是一个用于快速 Web 测试的在线游乐场。 有一个类似的环境允许我们在 Web 浏览器中试用 React Native。
React Native:可靠的现代选择
我最初建议对 React Native 采取更谨慎的方法。 今天,它是一个成熟而可靠的选择。
React 的一大优势是它不会强加于您的工作流程,因为它只代表视图层。 您想定义自己的 Grunt 管道吗? 还是您更愿意使用 Webpack? 你会使用 Backbone.js 来满足你的模型需求吗? 还是您想使用纯 JavaScript 对象? 所有这些问题的答案完全取决于你,因为 React 对这些选择没有任何限制。 正如官方网站所说:“由于 React 对您的技术堆栈的其余部分不做任何假设,因此很容易在现有项目中的一个小功能上进行尝试。”
在某种程度上,React Native 也是如此。 移动开发人员可以将 React Native 集成为其应用程序的一部分,利用受 Web 启发的开发工作流程,并在需要时选择更大规模地集成该库。
无论如何,有一件事是肯定的:React Native 不会消失。 Facebook 在其应用商店中拥有多个基于 React Native 的应用程序中占有很大的份额。 围绕 React Native 的社区非常庞大,并且还在继续增长。