ReactNative-IOS-开发教程-全-

ReactNative IOS 开发教程(全)

原文:React native for iOS development

协议:CC BY-NC-SA 4.0

一、学习基础:React 的短暂停留之旅

“千里之行始于足下。”—老子

在开始 React Native 之旅之前,您必须对 React(也称为 ReactJS 或 React.js)有所了解。在本章中,您将快速了解 React 的核心概念,这将有助于您使用 React Native。React 不同于大多数流行的 web 技术,随着本章的深入,您将会了解其中的原因。如果你已经花了相当多的时间在传统框架上,它的核心概念将会真正打开你的思维方式;这种新的思维方式有时被称为 React 式思维方式。您可能听说过“一次编写,随处运行”这句话,但由于各种形式(web、移动设备、平板电脑)的激增,您认为这几乎是不可能的。React 有一个不同的指导原则:“学习一次,在任何地方写作。”哇,那看起来很不一样,很自由。因此,您将从快速浏览 React 开始第一章,这将帮助您为 React Native 做好准备。如果你对 React 有初步的了解,你可以跳过这一章,进入第二章。

React 是一个用于创建用户界面的 JavaScript 库。它是由脸书和 Instagram 团队共同努力打造的。React 于 2013 年首次向世界推出,并在社区范围内获得了广泛认可,并受益于作为脸书核心技术的优势。根据官方文档,有些人认为 React Native 是 MVC 中的 V,因为 React Native 对所使用的其他技术栈没有任何假设。你可以使用任何你想要的技术,你可以用 React Native 创建你的应用的一个单独的部分;您也可以方便地在已经创建的应用中进行更改。

为什么要 React?

但是在一个充满 js 库和框架的世界里,我们还需要另一个 JavaScript 库吗?几乎每个月都会引入新的 js 框架。

React 的出现是因为它的创造者面临着一个重大问题:如何构建数据频繁变化的大型应用。这个问题几乎出现在任何现实世界的应用中,React 就是为了解决这个问题而诞生的。众所周知,许多流行的框架都是 MVC 或 MV框架,但这里有一点需要注意和重申:React 不是 MV框架。这只是一个为数据随时间变化的 UI 组件构建可组合用户界面的库。与流行的 js 框架不同,React 不使用模板或 HTML 指令。React 通过将 UI 分成许多组件来构建用户界面。就这样,没别的了。这意味着 React 使用编程语言的全部功能来构建和呈现视图。

以下是为您的下一个项目选择 React 的一些优势:

  • React 广泛使用 JavaScript:传统上,HTML 中的视图与 JavaScript 中的功能是分开的。有了 React,组件就创建好了,而且有一个完整的部分,JavaScript 对 HTML 了如指掌。
  • 可扩展和可维护:组件由一个统一的标记及其视图逻辑组成,这实际上使 UI 易于扩展和维护。
  • 虚拟 DOM: React 应用正在飞速发展。这归功于虚拟 DOM 及其差分算法。
  • 单向数据流:双向数据绑定是一个很好的想法,但是在现实世界的应用中,它产生的痛苦多于好处。双向数据绑定的一个常见缺点是,您不知道数据是如何更新的。有了单向数据流,事情就简单了:你知道数据在哪里发生了变化,这使得维护和测试你的应用变得更加容易。

为了对一项新技术有一个坚实的基础,有必要了解它的核心概念。在下一节中,您将探索 React 的一些独特概念,这将使您更进一步了解这项令人惊叹的技术。

虚拟 DOM

在所有 web 应用中,应用遭受的最昂贵的操作之一是改变 DOM。为了解决这个问题,React 维护了一个 DOM 的虚拟表示(如图 1-1 所示),它被称为虚拟 DOM 或 VDOM。除了差分算法,React Native 还能够计算实际 DOM 的增量,并只更新 DOM 中发生变化的部分。因此,更改的数量较少,这导致应用非常快。在应用的开始阶段,你可能看不到它,但是随着你的项目膨胀到疯狂的复杂程度(这通常发生在现实世界的应用中),你将开始看到用户快速体验的好处。

A978-1-4842-1395-7_1_Fig1_HTML.jpg

图 1-1。

Virtual DOM and diffing algorithm operations

手动 DOM 操作很麻烦,并且很难跟踪 DOM 以前的状态。如图 1-1 所示,React 通过保留一个虚拟 DOM 的两个副本来解决这个问题。接下来,对这两个虚拟 DOM 应用一个不同的算法,该算法主要检查发生的更改并返回 DOM 操作流。这些 DOM 操作然后被应用到实际的浏览器 DOM。

现在让我们从组件的角度来理解虚拟 DOM 是如何工作的。在 React 中,每个组件都有一个状态;这种状态是可以观察到的。每当状态发生变化时,React 基本上都知道这种变化需要重新渲染。所以当应用状态改变时,它会生成一个新的 VTreediff 算法再次共享了所需更改的 DOM 路径,如图 1-2 所示。这使得手动 DOM 操作最少。

A978-1-4842-1395-7_1_Fig2_HTML.jpg

图 1-2。

Components with virtual DOM

虚拟 DOM 的这个特性不仅重要,而且是 React 的杀手锏。DOM 访问速度非常慢,谦虚地说,在大多数应用中一次又一次地访问 DOM 使得情况变得更糟。为了让你的应用运行得更快,你应该尽可能少的接触 DOM,而虚拟 DOM 的实现很好的处理了这一点。对于一个小而琐碎的应用,您不会注意到这一点,但是一旦您的应用增长到有数千个 DOM 元素都试图更新,React 将不会让您的性能受到影响。

单向数据流

React 主要是 MVC 模式中的 V,但是在深入 React 中单向数据流的概念之前,您必须理解 MVC 框架的挑战。MVC 框架的最大挑战之一是管理视图。如您所知,MVC 框架的视图组件主要是 DOM 表示。当您编写与 DOM 交互的代码时,这很简单,但是对于框架来说,处理各种 DOM 操作是非常复杂的。

传统的 MVC 视图通常包含许多繁重的 UI,当数据发生变化时,即使是很小的元素,最终也会重新呈现应用,循环继续。这是因为通常大多数 MVC 框架都遵循双向数据绑定(见图 1-3 )。

A978-1-4842-1395-7_1_Fig3_HTML.jpg

图 1-3。

Two-way data binding

在 JavaScript 中,数据在内存中更改,并且绑定到 UI 中的一个视图,这意味着当在内存中的 JavaScript 中修改数据时,UI 中的数据也会更改。反过来,当 UI(即 DOM)中的数据通过单击按钮或任何其他事件发生变化时,它也会在内存中得到更新,从而使二者保持同步。理论上,这是完美的,这个想法是浪漫的完美。然而,在现实世界的应用中,当您有一个相当复杂和大的应用,用多个视图表示您的一个模型中的数据时,问题就出现了。随着您添加更多的模型和更多的视图,这种双向数据绑定最终会像意大利面条一样,将数据的每次更改添加到锅里,有时甚至会导致无限事件循环,其中一个视图更新一个模型,而模型又更新一个视图,以此类推,如图 1-4 所示。

A978-1-4842-1395-7_1_Fig4_HTML.jpg

图 1-4。

Unwanted spaghetti relationship

这个系统的另一个问题是做出改变的代价非常高。当你向一个新开发人员介绍一个如此复杂的应用时,很难理解一个变化可能会在这种错综复杂的关系中造成的影响。

React 遵循单向数据流以保持简单,如图 1-5 所示。它基于关注点分离(SoC)的概念。这是计算机科学中的一个设计原则,其中一个应用或程序被分成不同的部分,每个部分解决一个单独的或特定的问题。这种设计原则的价值在于,它简化了开发,创建了可维护和可伸缩的应用。这导致了模块化的代码,其中一个单独的部分可以被独立地重用、开发和修改。这很有意义,确实是聪明思维的一个例子。

A978-1-4842-1395-7_1_Fig5_HTML.jpg

图 1-5。

React Native’s one-way data flow

安装和设置

为了理解实际的例子,您必须首先设置您的环境来运行您的 React 例子。开始使用 React 最简单的方法是使用 JSFiddle 示例。

另一种方法是下载完整的初学者工具包甚至离线工作: http://facebook.github.io/react/downloads/react-0.13.3.zip

您也可以使用 npm: npm install -g react-tools安装 react-tools。

使用 React Native 提高工作效率的另一个有用工具是 React Developer Tools,这是一个非常有用的 Chrome 扩展,允许您在 Chrome 浏览器中检查 React 组件层次结构:

https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi

为了方便起见,您将在本章的示例中使用以下设置:React Native 和 JSX transformer 的脸书 cdn。

react . js:fb.me/react-0.13.3.js

JSX 变压器: http://fb.me/JSXTransformer-0.13.3.js

只需在代码中引用它们。接下来,您将安装节点包 browserify、watchify 和 babelify,这将帮助您转换 JSX 并将其保存在适当的 js 文件中:

s$ npm install react browserify watchify babelify --save-dev

$ watchify –t babelify ./your-jsx-file.jsx –o ./final-js-file.js –v

太好了!要为应用提供服务,您将通过以下命令在项目文件夹的根目录中使用简单的 python 服务器(SimpleHTTPServer ):

$ python -m SimpleHTTPServer

默认情况下,简单 python 服务器将在 8000 端口上为您的示例提供服务。

现在您已经完成了设置,您可以很快开始理解 React 的概念。

组件介绍

React 是关于组件的。您在 React 中做的任何事情都是基于可重用组件的。换句话说,使用 React 你只做一件事,那就是构建组件。在前面的章节中,我们讨论了关注点的分离。这些组件的整个概念是它们被完全封装,使它们易于测试和重用。

创建可重用组件是一项艺术工作,React Native 为您提供了许多特性。您将很快深入研究它们,但是首先让我们创建一个简单的“Hello World”组件。

创建一个hello_world.html文件,并将以下代码粘贴到其中:

<!DOCTYPE html>

<html>

<head>

<script src="http://fb.me/react-0.13.3.js

<script src="http://fb.me/JSXTransformer-0.13.3.js

</head>

<body>

<div id="intro"></div>

<script type="text/jsx">

React.render(

<h1>Hello, world </h1>, document.getElementById('intro')

);

</script>

</body>

</html>

这很简单。您的代码驻留在 HTML 标记中。JavaScript 驻留在脚本标签中。我们确实在类型'text/jsx'中看到了一些不同的东西;这就是 JSX,我们将在接下来的章节中详细解释。我们还看到一个关键字“React ”,这是 React 库的入口点。其中有包含所需文本的 H1 标签,以及文本出现的目标(在本例中为document.getElementById(intro))。此外,这个 React 代码可以存在于一个单独的文件中,并且可以在 HTML 中被引用。让我们创建一个名为src/helloworld.js.的单独文件

React.render(

<h1>Hello, world </h1>,

document.getElementById(“intro”)

);

现在让我们在您的hello_world.html file:中引用它

<script type="text/jsx" src="src/hello-world.js"></script>

这很简单。但是什么是 JSX 呢?在我们全面了解组件的不同方面之前,让我们先来看看 JSX。

小艾

React 不要求您使用 JSX。然而,JSX 确实让生活变得更简单。其类似 XML 的语法有助于非常容易地定义带有属性的树结构。XML 有很多好处,比如平衡开放标签和封闭标签。这使得大树比函数调用或对象文字更容易阅读。

让我们看一个简单的例子,先不看 JSX,看看随着我们逐渐包括 JSX,事情是如何变得简单的。

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta charset="UTF-8">

<title>Example without JSX</title>

<script src="http://fb.me/react-0.13.3.js

</head>

<body>

<script >

var Appo = React.createClass({

render:function(){

return React.createElement("h1",null, "Hello Dear")

}

});

React.render(React.createElement(Appo), document.body);

</script>

</body>

</html>

在本例中,您使用了React.createElement。您首先传递一个表示 HTML 标记的字符串,第二个是您的属性,第三个是子元素,在本例中是内部 HTML。

接下来,您希望在React.render中呈现您的组件,因此您传递应用组件,并将其作为document.body加载到目标位置。输出类似于图 1-6 。

A978-1-4842-1395-7_1_Fig6_HTML.jpg

图 1-6。

Output in React.render

接下来,我们再来看一个方法,这个方法已经被弃用,不再使用了。(React 生态系统发展如此之快,以至于有些东西已经被弃用了;然而,回顾一下过去,就会发现在如此短的时间内,他们在使事情变得更容易方面取得了多大的进展。)

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta charset="UTF-8">

<title>Old world example</title>

<script src="http://fb.me/react-0.13.3.js

</head>

<body>

<script>

var Appo = React.createClass({

render:function(){

return React.DOM.h1(null, "Hello Past")

}

});

React.render(Appo(), document.body);

</script>

</body>

</html>

这个例子使用了React.DOM模式,其中指定了要使用的标签(在这个例子中是 h1);第一个参数是属性,第二个是字符串,用作内部 HTML。当使用React.render呈现时,您将组件函数和目标传递到它要被加载的地方(在本例中是文档体)。如果你有 React 的最新版本,你会在你的控制台得到如图 1-7 所示的警告和错误。

A978-1-4842-1395-7_1_Fig7_HTML.jpg

图 1-7。

DevTools will show this warning

访问 http://fb.me/react-legacyfactory 找到除了 JSX 调用工厂之外的另一种方法,在调用组件之前包装组件。这也提醒你是时候使用 JSX 了。

现在让我们看看使用 JSX 时,同一个示例是什么样子的(另见图 1-8 ):

A978-1-4842-1395-7_1_Fig8_HTML.jpg

图 1-8。

Output using JSX

<!DOCTYPE HTML>

<html lang='en'>

<head>

<meta charset="UTF-8">

<title>JSX me like !</title>

<script src="http://fb.me/react-0.13.3.js

<script src="http://fb.me/JSXTransformer-0.13.3.js

</head>

<body>

<!-- with JSX -->

<script type="text/jsx">

var App = React.createClass({

render:function(){

return <h1>Hi from JSX</h1>

}

});

React.render(<App />, document.body);

</script>

</body>

</html>

在这里,我们已经包括 JSX 变压器,浏览器内的变压器。你的组件应用有 h1 标签。接下来,您使用React.render呈现它,以 JSX 语法传递您的组件,并将第二个参数作为目标,即document.body。就这样。

现在将您的 JSX 代码分离到一个单独的JSX文件中,并将其转换成纯 JavaScript,看看会发生什么。为此,让我们创建一个名为src的文件夹,并将您的 JSX 代码放入hello.jsx文件中:

var Appo = React.createClass({

render:function(){

return React.DOM.h1(null, "Hello Past")

}

});

React.render(Appo(), document.body);

接下来,让我们添加一个dist文件夹,您可以在其中保存转换后的 js 文件示例bundle.js。之后,在 HTML 文件中引用您的bundle.js文件(在本例中是jsx_example.html)。

如前所述,您应该安装节点包 browserify、babelify 和 watchify,以便使用它们将您的 JSX 转换成 js。这可以使用以下命令来完成:

$ npm install react browserify watchify babelify --save-dev

现在,用下面的命令将您的hello.jsx转换成bundle.js:

$ watchify –t babelify ./src/hello.jsx –o ./dist/bundle.js –v

结果是下面的bundle.js文件:

var Appo = React.createClass({

displayName: "Appo",

render: function render() {

return React.DOM.h1(null, "Hello Past");

}

});

React.render(Appo(), document.body);

尽管 React 并不明确要求 JSX,但它是首选。它允许您使用 HTML 语法创建 JavaScript 对象。组件有两个用途:模板和显示逻辑。因此,标记和代码紧密地联系在一起。显示逻辑通常非常复杂,使用模板语言来表达它确实变得很困难。解决这个问题的最好方法是从 JavaScript 本身生成 HTML 和组件。JSX 通过创建 React 树节点,用其 HTML 类型的语法解决了这些问题。

为了更好的可维护性,您可以保持 JSX 源代码的完整性;把它放在 JSX 档案的独立文件夹里。要将 JSX 代码转换成普通的 JavaScript,可以手动或通过构建脚本使用 JSX 编译器( http://facebook.github.io/react/jsx-compiler.html )。在您的生产环境中提供 JSX 文件是可能的,但是 React 将向您提供有关性能降低的控制台通知。

深入了解组件

在下一节中,您将探索组件的重要概念,这将帮助您轻松地使用它们。

性能

React 有些类似于 HTML 中的属性:properties。您可以使用它们来初始化您的组件,就像这样:

React.render(<App myProp="Hi from prop" />, document.getElementById('container'))

这里你用一个名为myProp的属性初始化 App 组件,这个属性的目标是'container'的 id。您可以通过以下方式在 JSX 通过插值访问此属性。让我们创建一个组件应用:

var App = React.createClass({

render: function(){

var txt = this.props.txt

return (

<div>

<h1> {myProp} </h1>

</div>

);

}

});

React.render(<App myProp="Hi from prop" />, document.getElementById('container'))

如果刷新浏览器,您将看到来自内部 HTML 的属性的消息。请为您的 HTML 文件使用以下代码(结果如图 1-9 所示):

A978-1-4842-1395-7_1_Fig9_HTML.jpg

图 1-9。

Refreshing your browser produces this message

<!DOCTYPE html>

<html>

<head>

</head>

<body>

<div id="container"></div>

</body>

<script src="http://fb.me/react-0.13.3.js

<script src="http://fb.me/JSXTransformer-0.13.3.js

<script src="dist/bundle.js"></script>

</html>

随着应用的增长,您首先需要确保组件是正确创建的。对于属性,您可以用一系列验证器来指定一种属性。这确保并验证了接收到的数据类型。让我们用一个例子来看看其中的一些:

var App = React.createClass({

propTypes: {

message: React.PropTypes.string,

age: React.PropTypes.number

},

render: function(){

// keeping props in a variable to use to often

var message = this.props.message,

age = this.props.age

return (

<div>

<h1> {message} </h1>

<p> My age is {age}</p>

</div>

);

}

});

React.render(<App age={5} message="Hi from prop" />, document.getElementById('container'))

这里,propType关键字表示属性名及其类型的散列。在您的示例中有两个 prop type:stringnumber,分别对应于messageage属性。

还有许多其他的财产类型。请注意,您可以将isRequired添加到任何 propType 的末尾,使其成为必需的:

propTypes: {

//some specific JS primitive

arrayType: React.PropTypes.array,

boolType: React.PropTypes.bool,

funcType: React.PropTypes.func,

objectType: React.PropTypes.object,

//if a value of a prop is necessary

numberType: React.PropTypes.number.isRequired

}

通过关键字getDefaultProps在属性中也有一个默认类型。例如,在同一个组件中,您可以为您的messageage属性指定默认类型:

getDefaultProps: function(){

return {

message: 'Default value of message',

age: 0

}

},

状态

在上一节中,您了解了属性,这些属性是传递到组件中的静态值。另一方面,状态由组件维护和更新。让我们用一个例子来理解这个概念:

var App = React.createClass({

getInitialState: function(){

return {

message: 'this is a default message from state',

}

},

render: function(){

return (

<div>

<h1> {this.state.message} </h1>

</div>

);

}

});

React.render(<App />, document.getElementById('container'))

如果您运行这个代码片段,您将在浏览器中看到如图 1-10 所示的结果。

A978-1-4842-1395-7_1_Fig10_HTML.jpg

图 1-10。

Resulting message using state

让我们看看代码。在同一个组件中,您使用关键字getInitialState初始化状态,在这个关键字中,您设置了消息的初始状态:

getInitialState: function(){

return {

message: 'this is a default message from state',

}

},

接下来,与上一个例子不同,您使用this.state.message来访问这个状态,它打印消息状态的初始文本:

<div>

<h1> {this.state.message} </h1>

</div>

现在让我们为您的组件添加一些功能。您在消息语句上方添加一个文本框。当您在文本框中键入内容时,消息会使用状态的概念实时更新:

var App = React.createClass({

getInitialState: function(){

return {

message: 'this is a default message from state',

}

},

updateState: function(e){

this.setState({message: e.target.value})

},

render: function(){

return (

<div>

<input type="text" onChange={this.updateState} />

<h1> {this.state.message} </h1>

</div>

);

}

});

React.render(<App />, document.getElementById('container'))

如果您在浏览器中执行这段代码,您将会看到如图 1-11 所示的结果。

A978-1-4842-1395-7_1_Fig11_HTML.jpg

图 1-11。

Adding a text box above your message statement

让我们看看您向组件中添加了什么。首先,您引入了一个名为updateState的函数:

updateState: function(e){

this.setState({message: e.target.value})

}

这个新函数updateState接受一个名为(e)的事件,并更新消息状态的值。此外,您还添加了一个输入字段:

<div>

<input type="text" onChange={this.updateState} />

<h1> {this.state.message} </h1>

</div>

输入框有一个onChange事件,每当状态更新时,它调用您的定制方法updateState。当您在文本框中键入内容时,打印的信息会立即更新。

摘要

本章提供了 React 的快速浏览。在你开始下一章之前,让我们回顾一下到目前为止你所学的内容。向您介绍了 React 库及其发明背后的原因。然后,您学习了如何安装和设置 React。您学习了这项技术的基础,例如虚拟 DOM、单向数据流和 JSX。您还了解了组件,以及在组件中使用状态和属性。

现在,您已经准备好在 React 生态系统中编码和工作,您旅程的有趣路径将在下一章开始。您将开始使用 React Native。

二、最简单的程序:React Native 的 Hello World

“大事始于小事。”—普罗米修斯

在上一章中,您对 React 生态系统有了一个很好的概述。现在是时候用 React Native 弄脏你的手了。在本章中,您将通过安装先决条件来设置您的环境,然后您将创建您的第一个 React 本机应用。

最好的学习方法是通过实例。贯穿整本书的主题是,你将通过编程来跟随例子学习 React Native 以理解概念。

在本章中,您将探索以下主题:

  • React Native 简介
  • React Native 的要点
  • React Native 的安装
  • 你的第一份申请
  • React 本机应用的剖析
  • 如何调试您的应用

Note

您可能会面临不同项目在不同节点版本上工作的情况。因此,建议您安装 NVM(节点版本管理器)来帮助保持可以在项目之间切换的多个节点版本。

什么是 React Native?

React Native 是一个开发原生移动应用的开源平台;它主要是由脸书的一个团队开发的。使用 React Native 很酷的一点是,您的程序使用标准的 web 技术,如 JavaScript (JSX)、CSS 和 HTML,而您的应用是完全原生的。换句话说,您的应用非常快速和流畅,它等同于任何使用传统 iOS 技术(如 Objective-C 和 Swift)构建的原生应用。然而,React Native 在性能和整体体验方面并没有妥协,就像流行的使用 web 技术构建 iOS 应用的混合框架一样。

React Native 旨在将 React 的强大功能引入移动开发,这一点在第一章的中有所解释。用 React 团队的话说,“学习一次,在任何地方写。”使用 React 和 React Native,您将看到许多使用 React 为 Web 构建的组件可以很容易地移植到 React Native iOS 应用,只需很少或不需要修改。React Native 引入了一种高度功能化的方法来构造用户界面,这与传统的 iOS 开发方法有很大不同。

尽管 React Native 是由脸书开发者开发的,但它是一个开源项目。该代码可在 https://github.com/facebook/reactreact-native 获得。

React 本地先决条件

在开始安装之前,让我们先回顾一下 React Native 的先决条件:

  • iOS 应用只能在安装了 OSX 的苹果 Mac 电脑上开发。您需要 OSX 版本 10.10 或以上。
  • 您需要 Xcode 6 或更高版本,其中包括 iOS SDK 和模拟器。React Native 仅支持 iOS7 或以上版本。Xcode 可以从苹果 App Store 下载。
  • 如果你注册了苹果 iOS 开发者计划,这是很有帮助的。如果您不在 iOS 开发者计划中,您将无法
    • 在实际设备上测试应用。
    • 访问测试版操作系统。
    • beta 测试的试飞。
    • 将您的应用提交到 App Store。

装置

让我们对 React Native 做一个快速的一次性设置。React Native 是 JavaScript 和 Objective-C 代码的混合体,因此您需要一些工具来创建、运行和调试用 JavaScript 编写的本机应用。我们一个一个来。

安装节点和 npm

Node.js 是建立在 Chrome 的 JavaScript 运行时之上的开源平台;它提供了一种轻松构建快速、可伸缩程序的方法。Node.js 允许您在终端中运行 JavaScript,并帮助创建模块。通过在终端中运行以下命令来安装 node . js:$brew install node

自制是安装 Node 的推荐方式。也可以从 https://nodejs.org 下载安装程序,手动安装。

npm 是 Node.js 的包管理器,如果你来自 iOS 世界,它类似于 CocoaPods。

通过在终端中运行以下命令来检查节点安装:

>> node –v

v4.2.1

>> npm –v

2.13.1

安装 Watchman

当您的文件和记录发生变化时,Watchman 会观察它们。当匹配文件更改时,它还可以触发操作(如重建资源)。更多详情,请访问 https://facebook.github.io/watchman/

您可以通过运行以下命令来安装 Watchman:

$brew install watchman

安装 React 本地包

React Native 是一个 npm 包,所以使用下面的代码来安装React Native- cli模块:

npm install -g react-native-cli

更新 React 本机

React Native 和 iOS 都是快速移动的框架。建议每次有新版本时更新它们。升级 React Native 很简单。在终端中运行以下命令:

npm update -g react-native-cli

你的第一个应用

既然您已经对 React Native 有了足够的了解,并且已经设置好了系统,那么是时候创建您的第一个应用了。为了让事情变得简单,开始的时候跟着做就行了。有时,单调地输入代码可能会让你感到与现实脱节,但现在跟着感觉走就足够了。记住模仿是一种强有力的学习形式;这是我们学习大部分技能的方式,比如说说话、阅读、写作,也是你学习 React Native 编程的方式。随着您的继续,这种方法将帮助您深入理解为什么您创作了某些代码。

在整本书中,你将创建一个应用,并把它从 Hello World 变成一个成熟的、发行级的应用,除了在某些地方我们需要脱离主题去独立探索一个概念。所以在你设置之前,先说一下你打算解决的问题。你将在本书过程中创建的应用计划解决一些住房问题;这将是任何流行的房地产搜索应用的一个非常原始的版本。

我们称之为合租吧。它将有一些基本的功能,如列表,创建一个条目,地理定位一个属性,等等。随着操作的进行,您将会看到各种 React 本机特性如何适合您的应用。

那是相当多的,但是在这一章中,你将仅仅使用 React Native 和一些 Hello World 代码为你的项目创建基本的结构。

创建一个基本的骨架

启动终端并键入以下命令:

react-native init HouseShare

这段代码使用 CLI 工具构建一个 React 本机项目,该项目可以按原样构建和运行。该命令为 React 本地 iOS 项目创建基本的文件夹结构。接下来,让我们进入这个目录:

> cd HouseShare/ios/

现在,点击HouseShare.xcodeproj。这是一个 Xcode 项目文件,将在 Xcode 中打开您的项目。接下来,让我们在 iOS 模拟器中加载您的应用。要构建您的应用并将其加载到模拟器中,只需单击顶部的运行按钮(图 2-1 )或执行 Command + R。这将在 iOS 模拟器中编译、构建并启动您的项目(图 2-2 )。

A978-1-4842-1395-7_2_Fig2_HTML.jpg

图 2-2。

Using the iOS simulator

A978-1-4842-1395-7_2_Fig1_HTML.jpg

图 2-1。

Building by clicking the Run button

真的很快。由于一个简单的命令,您的项目的基本结构已经就绪,您的应用已经加载到模拟器中。另请注意,当您从 Xcode 运行应用时,“终端”会自动打开。这是 React Native 的节点包管理器。如果您取消此操作,应用将停止工作。

终端打开,启动 React 原生打包器和一个服务器处理上述请求(图 2-3 )。React 本机打包程序负责读取和构建 JSX(您将在后面看到)和 JavaScript 代码。

A978-1-4842-1395-7_2_Fig3_HTML.jpg

图 2-3。

The packager is ready

http://localhost:8081/index.ios.bundle

Note

如果终端没有启动,iPhone 显示红屏,运行npm start并保持终端打开。

在您喜欢的任何编辑器中设置您的项目。React Native 并不强迫您,也没有偏好任何特定的编辑器,所以您可以继续默认使用 Xcode。但是,我们建议您在编辑器中打开您的项目,就像我们个人最喜欢的 Sublime Text 一样。

index.io.js做一些改动。事实上,删除文件中的所有代码。现在,添加以下代码:

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

} = React;

var HelloWorld = React.createClass({

render: function() {

return (

<View style={styles.container}>

<Text style={styles.welcome}>

HelloWorld !!

</Text>

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1,

justifyContent: 'center',

alignItems: 'center'

},

welcome: {

fontSize: 25,

textAlign: 'center'

}

});

AppRegistry.registerComponent('HouseShare', () => HelloWorld);

在 iOS 模拟器中按 Command + R,将刷新屏幕,如图 2-4 所示。(注意,在本书中,我们将使用 Command 键代替 CMD 键,这是等效的。)

A978-1-4842-1395-7_2_Fig4_HTML.jpg

图 2-4。

The screen is refreshed

真快!在几分之一秒的时间里,你可以看到你应用的变化。您不需要编译代码并重启模拟器来对本机更改做出 React。如果你以前做过任何原生 iOS 应用开发,点击刷新来查看变化可能会像一个奇迹。

现在,让我们来理解代码。该文件的顶部是下面一行:

'use strict';

这启用了严格模式,这为 React 本机 JavaScript 代码添加了改进的错误处理。ECMAScript 规范的第五版引入了严格模式。严格模式更容易编写安全的 JavaScript,并将糟糕的语法转换成真正的错误。这对于调试任何示例和使用未声明的变量都非常重要。

接下来是下面一行:

var React = require('react-native');

这将加载 React 本机模块,并将其分配给可在您的代码中使用的 React 本机变量。React Native 使用与 Node.js 相同的模块加载技术,带有require函数;这大致相当于在 Swift 中链接和导入库。

之后,添加以下代码片段:

var {

AppRegistry,

StyleSheet,

Text,

View,

} = React;

您正在将多个对象属性分配给一个变量;这被称为赋值过程中的析构。这个很酷的特性是在 JavaScript ECMAScript 6 中提出的。虽然它是可选的,但它非常有益;否则,每次在代码中使用组件时,都必须使用完全限定名,例如 React。模仿,React。样式表等等。这节省了不少时间。

接下来,您将创建一个视图:

var HelloWorld = React.createClass({

render: function() {

return (

<View style={styles.container}>

<Text style={styles.welcome}>

HelloWorld !!

</Text>

</View>

);

}

});

React Native 的基本构建块称为组件。您可以使用createClass方法来创建定制的组件类。这个类只有一个函数,render()render功能负责屏幕上显示的内容。您使用 JavaScript 语法扩展(JSX)来呈现 UI。JSX 是一种 JavaScript 语法扩展,看起来类似于 XML。

现在,您可以定义应用的样式。这里你会用到 Flexbox 它类似于 CSS 之于 HTML。现在,您可以键入以下代码。我们将在下一章解释样式。

var styles = StyleSheet.create({

container: {

flex: 1,

justifyContent: 'center',

alignItems: 'center'

},

welcome: {

fontSize: 25,

textAlign: 'center'

}

});

你可以看到这种风格与 CSS 非常相似;您可以定义字体大小、对齐方式等。

最后一步是定义应用的入口点和根组件:

AppRegistry.registerComponent('HouseShare', () => HelloWorld);

这不是 UIWebView

您正在使用 web 技术,但您的应用没有 web 组件;它有一个本地组件。打开调试➤视图调试➤捕获视图层级(见图 2-5 )。

A978-1-4842-1395-7_2_Fig5_HTML.jpg

图 2-5。

Using the native component

当您遍历 UIWindow 的树时,您会看到代码中没有 UIWebView,以及“Hello World!!"是 RCTText 的调用,如图 2-6 。

A978-1-4842-1395-7_2_Fig6_HTML.jpg

图 2-6。

“Hello World !!” is the call of RCTText

启用实时重新加载

React Native 的另一个很酷的功能是 live reload。一旦有变化,它会在 iOS 模拟器中重新加载您的应用视图。要激活此选项,您需要通过按 Ctrl + Command + Z 从 iOS 模拟器中打开的应用访问开发者菜单,并选择启用实时重新加载选项。现在,对 JavaScript 代码所做的任何更改都会导致应用自动重新加载。

React Native 为什么不一样?

在深入 React 本地世界之前,您必须理解为什么需要另一个框架来构建移动应用。我们已经生活在一个充满能够构建移动应用的框架和工具链的世界里。在 React Native 出现之前,使用 web 技术构建移动应用可以通过两种策略实现:

  • 基于 WebView:这些框架使用常见的 web 技术,如 HTML 和 JavaScript,并使用 WebView 加载应用。一个例子是流行的框架 Phonegap。
  • 使用 web 技术的本地应用:这些框架再次使用常见的 web 技术,如 HTML 和 JavaScript(准确地说,它们模仿使用 JavaScript 和 HTML)来创建本地应用。一个例子是流行的框架 Titanium Appcelerator。

使用这些策略创建的应用存在性能问题。基于 WebView 的应用很慢,因为它们使用 DOM,并且 DOM 操作非常昂贵,这导致了性能问题。正如 Flipboard ( http://engineering.flipboard.com/2015/02/mobile-web/ ))的一篇博文中所说,“你不能用 DOM 构建 60fps 的滚动列表视图。”这是通过这种技术开发的应用的一个基本问题:尽管开发时间可能很快,但您最终会体验迟缓。

另一种策略是框架模仿 JavaScript 和 HTML,并将它们转换成本地代码,这种策略有其他挑战。尽管最终的应用本质上是原生的,但在从 JavaScript 到原生的转换过程中有一个基本问题:它运行在主线程上。在这些应用中,你总是直接与本地对象交互,这又一次导致了缓慢而呆滞的体验。

React Native 从根本上不同于这两种方法。它在单独的线程上运行所有布局,你的主线程自由更新 UI,使得动画和 UI 渲染流畅,就像 100%纯原生 app 一样。

React Native 使用 JavaScriptCore 框架运行 JavaScript。在 iOS 7 中,苹果为 JavaScriptCore 引入了原生的 Objective-C API。这个框架允许 JavaScript 和 Objective-C 相互交流。这意味着您可以从 Objective-C 创建并调用 JavaScript 函数,或者从 JavaScript 回调 Objective-C。这一切都像魔咒一样管用。

React Native 还有一个不同之处。正如您在 Hello World 示例中看到的,您用 JavaScript 编写一个组件,就像您用 React 一样,只是您没有使用 HTML div,而是使用了像 View 和 Text 这样的标签。在 iOS 应用的情况下,视图基本上是 UIView。

React 本机应用的剖析

现在让我们理解 React Native init命令生成的应用结构。如果您打开名为 HouseShare 的项目,它看起来就像一个普通的 Xcode 项目。它具有以下文件夹结构:

|ios

|- HouseShare

|- HouseShare.xcodeproj

|- HouseShareTests

|android

node_modules

index.ios.js

index.android.js

package.json

Note

随着框架的发展,这里定义的文件夹结构可能会改变或修改,但是大部分功能保持不变。

如果您在 Xcode 中打开项目,它将具有不同的文件夹结构。Xcode 中的“文件夹”实际上是群组,不一定链接到 Finder 中的文件夹。

  • iOS:iOS文件夹有两个文件夹和一个文件。如上图所示,有一个HouseShare文件夹,里面有所有的 Objective-C 代码,比如AppDelegateImages.xcassetsInfo.plistLaunchScreen.xib等文件。另一个文件夹是HouseShareTests,这是你所有测试用例所在的地方。最后是您的 Xcode 项目文件HouseShare.xcodeproj,它用于加载到 Xcode 中以构建您的应用。
  • package.json:此文件夹包含关于您的应用的元数据,当您运行 npm 安装时,它将安装所有依赖项。如果你熟悉 Ruby,它类似于一个 Gemfile。
  • node_modules:所有package.json中提到的节点模块都将被下载到这个文件夹中。该文件夹还包含 React 本机框架的代码。
  • 这是您开始编写 iOS 应用的文件。
  • AppDelegate.m:这是任何 iOS app 的起点。
  • Android : React Native 也支持 Android 的开发。你所有的原生 Android 代码都在这个文件夹里。
  • 这个文件是你开始为 Android 应用编程的地方。

让我们从HouseShare/ios/HouseShare/AppDelegate.m打开AppDelegate.m文件:

#import "AppDelegate.h"

#import "RCTRootView.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

NSURL *jsCodeLocation;

/**

* Loading JavaScript code - uncomment the one you want.

*

* OPTION 1

* Load from development server. Start the server from the repository root:

*

* $ npm start

*

* To run on a device, change localhost to the IP address of your computer

* (you can get this by typing ifconfig into Terminal and selecting the

* inetvalue underen0:) and make sure your computer and iOS device are

* on the same Wi-Fi network.

*/

jsCodeLocation = [NSURL URLWithString:@"``http://localhost:8081/index.ios.bundle

/**

* OPTION 2

* Load from pre-bundled file on disk. To re-generate the static bundle

* from the root of your project directory, run

*

* $ react-native bundle --minify

*

* seehttp://facebook.github.io/react-native/docs/runningondevice.html

*/

//   jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];

RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation

moduleName:@"HouseShare"

launchOptions:launchOptions];

self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

UIViewController *rootViewController = [[UIViewController alloc] init];

rootViewController.view = rootView;

self.window.rootViewController = rootViewController;

[self.window makeKeyAndVisible];

return YES;

}

@end

RCTRootView是 React Native 提供的 Objective-C 类,继承自 iOS UIView类。它获取并执行您的 JavaScript 代码。

它还加载了http://localhost:8081/index.ios.bundle URL,其中有您用index.ios.js编写的代码,还有一个由 React 本机框架添加的程序。

排除故障

用 React Native 调试符合我们调试 web 应用的方式;总之,真的很简单。要访问调试选项,请在 iOS 模拟器中加载的应用内按 Command + D。这将打开一个提供几个调试选项的菜单,如图 2-7 所示。

A978-1-4842-1395-7_2_Fig7_HTML.jpg

图 2-7。

Debugging options

您必须为最终版本禁用此菜单,因为您的最终用户不应看到这些选项。要禁用它,请在 Xcode 中打开项目,然后选择:产品➤方案➤编辑方案(或按 Command +)

让我们回顾一下图 2-7 中显示的每个选项。

重新加载

reload 选项使用最新的 React 本机代码刷新模拟器中的屏幕,而无需再次编译项目。这可以通过两种方式实现:一、点击菜单中的重新加载选项,如图 2-7 所示,或者按 Command + R,这样会重新加载 JavaScript 代码中所做的所有更改。

在 Swift 或 Objective-C 文件中所做的任何更改都不会反映出来,因为这些更改需要重新编译。此外,如果您添加任何资产,如图像,应用需要重新启动。

在 Chrome 中调试

这是调试用 React Native 编写的 JavaScript 代码的最好和最常用的选项之一。与 web 应用一样,你可以在 Chrome 中调试 React 原生应用。当你点击“在 Chrome 中调试”时,它会在 Chrome 中打开http://localhost:8081/debugger-ui(图 2-8 )。

A978-1-4842-1395-7_2_Fig8_HTML.jpg

图 2-8。

Debugging in Chrome

安装 React 开发工具,这是一个 Chrome 扩展,用于调试 React 应用和 React 本机代码。它允许您在 Chrome 开发者工具中检查 React 本地组件层次结构。要安装它,请访问 Chrome 网上商店或访问以下网址: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en

一旦安装了扩展,按 Command + Option + J 或者从 Chrome 浏览器中选择查看➤开发者➤开发者工具来访问开发者工具控制台。

你将会在你的 Chrome 开发工具中获得一个名为 React 的新标签。这向您显示了已经呈现在页面上的根 React 组件,以及它们最终呈现的子组件。还可以看到属性、状态、组件、事件监听器,如图 2-9 所示。

A978-1-4842-1395-7_2_Fig9_HTML.jpg

图 2-9。

Debugging in Chrome DevTools

查看图 2-10 ,你可以看到一个类似于 Xcode 的层次结构:Hello World 被包装在 RCTText 中,然后又被包装在 RCTview 中。

A978-1-4842-1395-7_2_Fig10_HTML.jpg

图 2-10。

Debugging the app with the React tab in Chrome DevTools

在 Safari 中调试

如果你没有 Chrome,你也可以使用 Safari 进行调试,但 Chrome 是调试 React 原生应用的首选。

显示 FPS 监视器

许多应用使用大量的动画和图形。FPS(每秒帧数)为您的应用定义这些动画的平滑度;这在游戏应用中被广泛使用。当您在菜单中选择“显示 FPS 监视器”时,它会在模拟器中显示您的应用的一些属性(图 2-11 )。虽然你可能在你的 Hello World 应用中找不到这些属性的太多用途,但它们对于动画密集型应用来说非常有用,可以防止它们进入昏睡模式,从而创建不稳定的用户体验。见图 2-11 。

A978-1-4842-1395-7_2_Fig11_HTML.jpg

图 2-11。

Additional properties in the simulator

该检查元件

您还可以从模拟器中检查 React 本地元素,有点类似于您在浏览器中检查元素的方式,尽管您目前不能像在浏览器中那样更改属性的实时值。现在,您可以看到任何对象的样式表属性。点击 HelloReact!!文本(图 2-12 ),它将打开该元素的详细信息。

A978-1-4842-1395-7_2_Fig12_HTML.jpg

图 2-12。

Click the text to see element details

该元素的详细信息显示在左下方的图 2-13 中。

A978-1-4842-1395-7_2_Fig13_HTML.jpg

图 2-13。

Font details

你可以看到 Hello World 的字体大小是 25,并且居中对齐。

开始分析

剖析用于衡量绩效。概要分析会话提供了对代码的哪些部分最常用、需要多少时间以及应该改进代码的哪些部分的深入了解。您还可以从分析器在一个方法上停止的次数中找到每个方法花费的时间。

下面的 URL 链接到一篇非常好的博客文章,这篇文章将很好地概述如何使用 Xcode 进行分析: www.raywenderlich.com/23037/how-to-use-instruments-in-xcode .

摘要

在这一章中,你被介绍到 React 原生。您设置了 React 本机开发环境,并编写了第一个应用。您还了解了 React 本机应用的文件夹结构以及如何调试。现在,您已经做好了准备,可以开始使用 React Native 为您的 iOS 应用创建用户界面了。

在下一章中,您将学习如何通过掌握 Flexbox 来创建令人惊叹的用户界面。您将看到如何从一个组件导航到另一个组件,如何将图像添加到未封装的应用中,以及如何使用 React Native 创建 ListView 和 ScrollView。

三、画布、画笔和颜料:使用用户界面

用户界面是从混乱的复杂到优雅的简单的转变过程—阿克沙特·保罗

在前一章中,我们介绍了 React Native 并创建了我们的第一个 React Native 应用。现在我们的项目有了一个空白的框架,我们将用一个令人惊叹的用户界面来填充它。在本章中,我们将讨论以下主题:

  • 导航仪
  • flex box(flex box)的缩写形式
  • 图像
  • TouchableHighlight
  • 路由到新组件
  • ListView
  • ScrollView

任何有经验的软件专业人士都会同意——一个应用的成功取决于它不仅运行完美,而且看起来很棒。因此,一个优秀的用户界面对你的应用的成功有着巨大的影响。

布局系统是一个需要掌握的基本概念,以便创建优秀的应用。让我们首先了解如何使用 React Native 在 iOS 应用中导航。

导航仪

NavigatorIOS 是一个用于创建导航的 React 本地组件。它包装了 UIKit 导航,并允许你在应用中添加向后滑动功能。NavigatorIOS 管理一堆视图控制器,以便为分层内容提供一个向下钻取的界面。现在我们知道 NavigatorIOS 是做什么的了,让我们在项目中实现它。

首先,在index.ios.js文件的组件列表中添加 NavigatorIOS:

var {

AppRegistry,

StyleSheet,

Text,

View,

NavigatorIOS,

} = React;

现在让我们创建一个具有 NavigatorIOS 的组件,并将其命名为mainView,然后让我们在mainView中加载另一个组件,我们将其命名为Home:

var mainView = React.createClass ({

render: function() {

return (

<NavigatorIOS

style={styles.navigator} initialRoute={{

title: 'House Share',

component: Home

}}

/>

);

},

});

Home component will be like home page of our application.

NavigatorIOS 是一个 React 本机组件;我们还使用initialRoute为页面提供了标题(House Share)和组件名称(Home)。还有其他选项,记录在 https://facebook.github.io/react-native/docs/navigatorios.html 中。

NavigatorIOS 有助于最基本的 IOS 路由。路线是描述导航器中每个视图的对象。首先,将路线提供给导航员。在前面的代码中,我们从我们的mainView调用组件 Home。让我们添加一个空的 Home 组件:

var Home = React.createClass({

render: function() {

return (

<View>

</View>

);

}

});

让我们也给我们的mainView组件添加一些样式:

var styles = StyleSheet.create({

container: {

flex: 1,

justifyContent: 'center',

alignItems: 'center'

},

welcome: {

fontSize: 25,

textAlign: 'center'

},

navigator: {

flex: 1

}

});

现在让我们将应用的入口点更新为mainView:

AppRegistry.registerComponent('HouseShare', () => mainView);

现在让我们用 Xcode 构建我们的应用,看看结果,如图 3-1 所示。

A978-1-4842-1395-7_3_Fig1_HTML.jpg

图 3-1。

Our application, with updated navigation bar at the top

非常好——我们可以在顶部看到一个更新的导航栏。让我们给这个导航栏添加一些颜色,使我们的更改更加明显:

var mainView = React.createClass ({

render: function() {

return (

<NavigatorIOS barTintColor='#48BBEC' titleTextColor= "#FFFFFF" style={styles.navigator} initialRoute={{

title: 'House Share',

component: Home

}}

/>

);

},

});

我们已经使用barTintColortitleTextColor给我们的导航栏添加了颜色(见图 3-2 )。

A978-1-4842-1395-7_3_Fig2_HTML.jpg

图 3-2。

Our toolbar now has color

我们在这一部分做了一些样式设计,如果你来自网格布局背景,这可能对你来说是新的东西。React Native 使用 Flexbox 进行样式化,我们将在下一节详细讨论。

flex box(flex box)的缩写形式

在前面的例子中创建我们的布局时,您一定已经看到了样式中提到的flex属性。这是因为 React 原生应用使用 Flexbox 布局模型。

React 原生 Flexbox 布局模型的灵感来自于 CSS3 中的 CSS Flex Box 布局。React 原生团队专门为 iOS 重新编写了这一功能。Flexbox 背后的主要思想是能够创建布局,而不必担心不同的屏幕尺寸或设备方向。flex 容器扩展项目以填充可用空间,或者收缩项目以防止溢出。让我们了解一些 Flexbox 的基本知识,以加快我们的布局开发。首先,让我们更新视图:

var Home = React.createClass({

render: function() {

return (

<View style={styles.container}>

<View style={styles.topBox} />

<View style={styles.bottomBox} />

</View>

);

}

});

我们已经创建了一个带有样式容器的主视图和两个带有样式topBoxbottomBox的子视图。现在,让我们创建样式:

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'column'

},

welcome: {

fontSize: 25,

textAlign: 'center'

},

navigator: {

flex: 1

},

topBox: {

flex: 2,

backgroundColor: '#CCE5FF'

},

bottomBox: {

flex: 1,

backgroundColor: '#FFFFCC'

}

});

返回模拟器,使用 Command+R 刷新视图。

A978-1-4842-1395-7_3_Fig3_HTML.jpg

图 3-3。

Screen in portrait mode

现在,旋转模拟器,你会看到它自动调整这些彩色方框的大小。

让我们将模拟器改为风景模式(见图 3-4 )。这可以使用 Command +右/左箭头键(⌘+Left 箭头)轻松完成。您可以看到框是如何调整其大小的,标题是如何调整其宽度的,以便利用所有可用的空间。多亏了 Flexbox,一个相当费力的任务被简化了。

A978-1-4842-1395-7_3_Fig4_HTML.jpg

图 3-4。

Screen in landscape mode

现在,让我们回顾一下 flex 属性Flex-directionFlex

弯曲方向

Flexbox 是一个单向布局概念。Flex-direction允许你定义子元素的流向。它可以有两个值,rowcolumn。在前面的例子中,我们使用了column

我们改成row:

container: {

flex: 1,

flexDirection: 'row'

}

返回模拟器,用命令+R 刷新视图(见图 3-5 )。

A978-1-4842-1395-7_3_Fig5_HTML.jpg

图 3-5。

Changing the orientation of the box

我们可以看到盒子的方向是如何变化的。现在再次将属性flexDirection改为列(见图 3-6 ))。

A978-1-4842-1395-7_3_Fig6_HTML.jpg

图 3-6。

Changing the property to column

弯曲

您一定见过样式表中的flex值;它可以是整数或小数,表示盒子的相对大小:

container: {

flex: 1,

flexDirection: 'column'

},

topBox: {

flex: 2,

backgroundColor: '#CCE5FF',

},

bottomBox: {

flex: 1,

backgroundColor: '#FFFFCC'

}

我们的观点是:

<View style={styles.container}>

<View style={styles.topBox} />

<View style={styles.bottomBox} />

</View>

所以flex定义了盒子的大小百分比。我们可以看到容器内部有两个视图,topBoxbottomBox,分别有21flex值(见图 3-7 )。

A978-1-4842-1395-7_3_Fig7_HTML.jpg

图 3-7。

Container in 2:1 ratio

现在,更新视图并在容器视图中添加一个topBox视图:

<View style={styles.container}>

<View style={styles.topBox} />

<View style={styles.bottomBox} />

<View style={styles.topBox} />

</View>

刷新视图。容器现在有三个视图:topBoxbottomBox,然后又是topBox(见图 3-8 )。

A978-1-4842-1395-7_3_Fig8_HTML.jpg

图 3-8。

Container with three views

这将把视图分成 2:1:2 的比例,因为它们的flex值是 2:1:2 的比例。

为了更好地理解这是如何工作的,让我们改变flex值,看看它如何改变我们的屏幕。让我们把topBoxflex值改成1

让我们将 CSS 更新为:

container: {

flex: 1,

flexDirection: 'column'

},

topBox: {

flex: 1,

backgroundColor: '#CCE5FF',

},

bottomBox: {

flex: 1,

backgroundColor: '#FFFFCC'

}

刷新视图查看变化,如图 3-9 所示。

A978-1-4842-1395-7_3_Fig9_HTML.jpg

图 3-9。

View in ratio of 1:1:1

我们可以看到,现在屏幕被分割成 1:1:1 的比例,因为视图的flex值是 1:1:1 的比例。使用 Flexbox,很容易创建可以根据屏幕大小和方向调整大小的布局。这只是对 Flexbox 的介绍;我们将在需要时在整本书中解释更多的属性。您还可以在 https://facebook.github.io/react-native/docs/flexbox.html 找到更多选项。

添加图像

React Native 有一个内置组件Image,它将帮助我们显示图像,包括网络图像、临时本地图像,以及来自本地磁盘的图像,如相机胶卷。首先,我们将显示本地图像。

在 Xcode 中打开Image.xacassets文件(参见图 3-10 )。点击底部的+按钮。

A978-1-4842-1395-7_3_Fig10_HTML.jpg

图 3-10。

Image.xacassets in Xcode

将图像集命名为“主页”,并将主页图像拖动到屏幕上的方框中(参见图 3-11 )。

A978-1-4842-1395-7_3_Fig11_HTML.jpg

图 3-11。

Image set “home”

xcassets 的使用是 iOS 7 之后的新标准。资产目录管理应用的图像;iOS 有不同的图像分辨率,并将它们分组在一起。构建时,Xcode 会将这个图像目录编译成最有效的包,以便最终分发。

既然我们已经将图像添加到了项目中,那么让我们将它添加到组件中。首先,将Image组件添加到我们的组件列表:

var {

AppRegistry,

StyleSheet,

Text,

View,

NavigatorIOS,

Image

} = React;

现在让我们将图像添加到我们的视图中:

var home = React.createClass({

render: function() {

return (

<View style={styles.container}>

<View style={styles.topBox} >

</View>

<View style={styles.bottomBox} >

<Image source={require('image!home')} style={styles.image}/>

</View>

<View style={styles.topBox} >

</View>

</View>

);

}

});

require('image!home)指向名为home的图像的资产目录。

让我们也指定这个图像的宽度和高度:

image: {

width: 70,

height: 70

},

由于我们在 Xcode 中做了改动,所以我们必须重启模拟器。停止应用并重新生成代码。我们可以看到房子的图像显示在屏幕上(见图 3-12 )。

A978-1-4842-1395-7_3_Fig12_HTML.jpg

图 3-12。

Now we have a house image

现在,让我们通过用下面的样式更新bottomBox来添加一些样式:

bottomBox: {

flex: 1,

backgroundColor: '#FFFFCC',

alignItems: 'center',

justifyContent: 'center',

},

alignItemsjustifyContent分别定义沿横轴和主轴排列伸缩项目的默认行为。因为我们想在中间显示这个,所以我们将这些值更新为center

刷新屏幕,你会发现房屋图像在视图中居中(见图 3-13 )。

A978-1-4842-1395-7_3_Fig13_HTML.jpg

图 3-13。

The house is now centered

我们还可以给定任何服务器图像 URL 作为源,并且Image组件将负责从网络加载它。我们将在本章的后半部分做这件事。

可触摸高亮显示

触摸是与 iOS 应用交互的基本方式。TouchableHighlight是一个 React 本地组件,它帮助我们创建按钮,以便在触摸时做出正确的响应。这些实际上不是按钮,但是 React 原生团队认为直接在 JavaScript 中构造按钮比使用 UIButton 更容易。你 app 里的按钮用的是TouchableHighlight,不是按钮,但工作起来像按钮。让我们用一个例子来理解这一点。

让我们将 TouchableHighlight 组件添加到代码中:

var {

AppRegistry,

StyleSheet,

Text,

View,

NavigatorIOS,

TouchableHighlight

} = React;

更新视图并添加两个TouchableHighlight按钮:

var Home = React.createClass({

render: function() {

return (

<View style={styles.container}>

<View style={styles.topBox} />

<View style={styles.bottomBox} />

<View style={styles.topBox} >

<TouchableHighlight style={styles.button}

underlayColor='#99d9f4'>

<Text style={styles.buttonText}>Show Houses</Text>

</TouchableHighlight>

<TouchableHighlight style={styles.button}

underlayColor='#99d9f4'>

<Text style={styles.buttonText}>Add House</Text>

</TouchableHighlight>

</View>

</View>

);

}

});

当然,我们需要按钮的样式表:

button: {

flex: 1,

backgroundColor: '#48BBEC',

borderColor: '#48BBEC',

borderWidth: 1,

borderRadius: 8,

alignSelf: 'stretch',

justifyContent: 'center',

margin: 10

},

buttonText: {

fontSize: 18,

color: 'white',

alignSelf: 'center'

}

在 iOS 模拟器中刷新应用。我们将在第三个块的屏幕上看到两个按钮(如图 3-14 所示)。

A978-1-4842-1395-7_3_Fig14_HTML.jpg

图 3-14。

Screen with two buttons at bottom

让我们继续构建我们的页面,再添加一个视图来列出住房选项。这将通过单击 show house 页面来完成,该页面将重定向到另一个组件。将以下代码替换为Home组件:

var Home = React.createClass({

_handleListProperty: function() {

console.log(‘Button clicked successfully’);

},

render: function() {

return (

<View style={styles.container}>

<View style={styles.topBox} />

<View style={styles.bottomBox} />

<View style={styles.topBox} >

<TouchableHighlight

style={styles.button}

onPress= {this._handleListProperty }

underlayColor='#99d9f4'

>

<Text style={styles.buttonText}>List properties</Text>

</TouchableHighlight>

<TouchableHighlight style={styles.button}

underlayColor='#99d9f4'>

<Text style={styles.buttonText}>Add House</Text>

</TouchableHighlight>

</View>

</View>

);

}

});

让我们回顾一下我们在这里做了什么;我们已经为列表属性部分的TouchableHighlight组件添加了一个onPress属性。每当有人按下列表属性按钮时,就会调用函数_ handleListProperty

如果您构建您的应用并在开发工具中打开控制台,每次您单击列表属性按钮时,您都会在控制台中看到消息“按钮已成功单击”。

接下来,我们将创建我们的ListProperty组件。但是首先,让我们重构我们的代码,并将我们的组件保存在单独的文件中。创建一个包含Components子文件夹的App文件夹,这是我们保存 React 本地组件的地方。在Components文件夹中,创建文件Home.js,并将它各自的组件放在那里。然后我们将在我们的index.ios.js中要求它们,现在看起来像这样:

'use strict';

var React = require('react-native');

var Home = require('./App/Components/Home');

var {

AppRegistry,

StyleSheet,

Text,

View,

NavigatorIOS,

TouchableHighlight

} = React;

var mainView = React.createClass ({

render: function() {

return (

<NavigatorIOS barTintColor='#48BBEC' titleTextColor= "#FFFFFF" style={styles.navigator} initialRoute={{

title: 'House Share',

component: Home

}}/>

);

},

});

var styles = StyleSheet.create({

navigator: {

flex: 1

}

});

AppRegistry.registerComponent('HouseShare', () => mainView);

路由到组件

在 React Native 中,您将构建许多组件,并在它们之间来回路由。我们必须有办法做到这一点。在本节中,我们将学习从一个组件到另一个组件的路由。在创建组件ListProperty之前,我们需要导航到这个组件的路径。这可以通过修改Home组件中的_handleListProperty函数来实现。在./App/Components/Home.js用以下代码替换_handleListProperty功能:

_handleListProperty: function() {

this.props.navigator.push({

title: "List Properties",

component: ListProperty

})

},

在这里,navigator.push向前导航到新的路线;这种情况下是ListProperty

现在让我们通过在./App/Components/文件夹中创建一个文件ListProperty.js来创建一个List Property组件。在您的ListProperty.js文件中添加以下代码:

var React = require('react-native');

var {

View

} = React;

var ListProperty = React.createClass({

render: function() {

return (

<View />

);

}

});

module.exports = ListProperty;

刷新您的应用并点击列表属性按钮,这将把您带到如图 3-15 所示的空视图。

A978-1-4842-1395-7_3_Fig15_HTML.jpg

图 3-15。

Empty view with traversing option

在左上角,您将看到一个选项,可以遍历回上一个组件。NavigatorIOS 维护这个返回栈,通过它可以返回到上一个组件。现在让我们来处理这个空组件。这个想法是创建一个包含属性列表的表格视图,每个属性的左侧都有一个图像缩略图。其余的细节应该出现在它的旁边。

为了简化本章,我们将模拟数据,而不是从外部服务中提取数据(稍后,您将学习如何从外部 API 中提取相同的数据)。有了这些数据,我们将显示酒店的名称、地址和缩略图。这看起来有点像图 3-16 。

A978-1-4842-1395-7_3_Fig16_HTML.jpg

图 3-16。

Property name and address

将以下代码添加到文件./App/Components/ListProperty.js中的ListProperty组件中:

var React = require('react-native');

var {

Image,

StyleSheet,

Text,

View

} = React;

var MOCK_DATA = [

{name: 'Mr. Johns Conch house', address: '12th Street, Neverland', images: {thumbnail: 'http://hmp.me/ol5

];

var ListProperty = React.createClass({

render: function() {

var property = MOCK_DATA[0]

return (

<View style={styles.container}>

<Image

source={{uri: property.images.thumbnail}}

style={styles.thumbnail}/>

<View style={styles.rightContainer}>

<Text style={styles.name}>{property.name}</Text>

<Text style={styles.address}>{property.address}</Text>

</View>

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'row',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#F5FCFF',

},

thumbnail: {

width: 53,

height: 81,

},

rightContainer: {

flex: 1,

},

name: {

fontSize: 20,

marginBottom: 8,

textAlign: 'center',

},

address: {

textAlign: 'center',

},

});

module.exports = ListProperty;

让我们在 iOS 模拟器中刷新我们的应用,看看有什么变化(见图 3-17 )。

A978-1-4842-1395-7_3_Fig17_HTML.jpg

图 3-17。

Thumbnail image with property name and address

让我们回顾一下我们在这里做了什么:

var React = require('react-native');

var {

Image,

StyleSheet,

Text,

View

} = React;

我们首先指定了本节中要使用的所有组件。我们再次添加了Image组件,它将用于加载我们的图像——不是从一个捆绑的图像,而是像前面承诺的那样,从一个图像 URL。

接下来,我们添加了一些模拟数据:

var MOCK_DATA = [

{name: 'Mr. Johns Conch house', address: '12th Street, Neverland', images: {thumbnail: 'http://hmp.me/ol5

];

这里,我们为我们的属性缩略图添加了名称、地址和图像 URL。随着示例的深入,我们将向这个模拟数据添加更多的值。

接下来,我们创建了我们的ListProperty组件:

var ListProperty = React.createClass({

render: function() {

var property = MOCK_DATA[0]

return (

<View style={styles.container}>

<Image

source={{uri: property.images.thumbnail}}

style={styles.thumbnail}/>

<View style={styles.rightContainer}>

<Text style={styles.name}>{property.name}</Text>

<Text style={styles.address}>{property.address}</Text>

</View>

</View>

);

}

});

我们的ListProperty组件有一个变量属性,它包含来自MOCK_DATA数组的数据。然后我们使用一个View组件来创建一个包含图片组件中缩略图的视图。在Image组件中,我们有属性 source,它可以有值 http address、本地文件路径或静态图像资源的名称。

使用一个Text组件,我们从模拟数据中指定了名称和地址:

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'row',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#F5FCFF',

},

thumbnail: {

width: 53,

height: 81,

},

rightContainer: {

flex: 1,

},

name: {

fontSize: 20,

marginBottom: 8,

textAlign: 'center',

},

address: {

textAlign: 'center',

},

});

module.exports = ListProperty;

最后,我们添加了样式并导出了我们的ListProperty组件。

列表视图

在上一节中,我们通过指定元素的索引来填充数组中的一个元素。在本节中,我们将使用ListView填充一个数据列表。在我们开始之前,让我们多了解一点关于ListView组件的知识。

一个ListView是为填充动态数据的垂直滚动列表而设计的组件。最简单的步骤是创建一个ListView数据源,用类似于本地TableView数据源的数据数组填充它,然后用该数据源和一个renderRow回调实例化一个ListView组件。这从数据数组中取出一个 blob,并返回一个可呈现的组件,它将被显示。

ListView看起来与TableView非常相似,但是实现并没有真正使用TableView.,而是在幕后使用了ScrollView。像滑动删除、重新排序等功能不能通过ListView直接使用。

替换您的ListProperty.js中的以下代码:

var React = require('react-native');

var {

Image,

StyleSheet,

Text,

View,

ListView

} = React;

var MOCK_DATA = [

{name: 'Mr. Johns Conch house', address: '12th Street, Neverland', images: {thumbnail: 'http://hmp.me/ol5

{name: 'Mr. Pauls Mansion', address: '625, Sec-5,  Ingsoc', images: {thumbnail: 'http://hmp.me/ol6

{name: 'Mr. Nalwayas Villa', address: '11, Heights, Oceania', images: {thumbnail: 'http://hmp.me/ol7

{name: 'Mr. Johns Conch house', address: '12th Street, Neverland', images: {thumbnail: 'http://hmp.me/ol5

{name: 'Mr. Pauls Mansion', address: '625, Sec-5,  Ingsoc', images: {thumbnail: 'http://hmp.me/ol6

{name: 'Mr. Nalwayas Villa', address: '11, Heights, Oceania', images: {thumbnail: 'http://hmp.me/ol7

{name: 'Mr. Johns Conch house', address: '12th Street, Neverland', images: {thumbnail: 'http://hmp.me/ol5

{name: 'Mr. Pauls Mansion', address: '625, Sec-5,  Ingsoc', images: {thumbnail: 'http://hmp.me/ol6

{name: 'Mr. Nalwayas Villa', address: '11, Heights, Oceania', images: {thumbnail: 'http://hmp.me/ol7

];

var ListProperty = React.createClass({

getInitialState: function() {

var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

return {

dataSource: ds.cloneWithRows(MOCK_DATA),

};

},

render: function() {

return (

<ListView

dataSource={this.state.dataSource}

renderRow={this.renderProperty}

style={styles.listView}/>

);

},

renderProperty: function(property) {

return (

<View style={styles.container}>

<Image

source={{uri: property.images.thumbnail}}

style={styles.thumbnail}/>

<View style={styles.rightContainer}>

<Text style={styles.name}>{property.name}</Text>

<Text style={styles.address}>{property.address}</Text>

</View>

</View>

);

},

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'row',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#F5FCFF',

},

thumbnail: {

width: 63,

height: 91,

},

rightContainer: {

flex: 1,

},

name: {

fontSize: 20,

marginBottom: 8,

textAlign: 'center',

},

address: {

textAlign: 'center',

},

listView: {

paddingTop: 20,

backgroundColor: '#F5FCFF',

},

});

module.exports = ListProperty;

在 iOS 模拟器中刷新您的应用以查看更新后的视图,如图 3-18 所示。

A978-1-4842-1395-7_3_Fig18_HTML.jpg

图 3-18。

Scrollable addresses

太好了。现在我们有了一个可以滚动浏览的属性列表。现在让我们回顾一下实施情况:

var React = require('react-native');

var {

Image,

StyleSheet,

Text,

View,

ListView

} = React;

我们再次指定了本节中所有组件的用途。增加了一个新组件— ListView。如果你看过除了ListView之外的 React 原生文档来实现类似的功能,那么ScrollView. ListView可能比简单地呈现所有这些元素或者将它们放在ScrollView中要好得多。这是因为,尽管 React Native 速度很快,但呈现一个非常大的元素列表可能会很慢。与TableView类似,ListView实现元素的渲染列表,这样你只显示屏幕上显示的元素;那些已经渲染但现在不在屏幕上的将从本机视图层次结构中删除,这使得渲染平滑而快速。

展望未来,我们已经更新了我们的MOCK_DATA:

var MOCK_DATA =[

{name: 'Mr. Johns Conch house', address: '12th Street, Neverland', images: {thumbnail: 'http://hmp.me/ol5

{name: 'Mr. Pauls Mansion', address: '625, Sec-5,  Ingsoc', images: {thumbnail: 'http://hmp.me/ol6

{name: 'Mr. Nalwayas Villa', address: '11, Heights, Oceania', images: {thumbnail: 'http://hmp.me/ol7

{name: 'Mr. Johns Conch house', address: '12th Street, Neverland', images: {thumbnail: 'http://hmp.me/ol5

{name: 'Mr. Pauls Mansion', address: '625, Sec-5,  Ingsoc', images: {thumbnail: 'http://hmp.me/ol6

{name: 'Mr. Nalwayas Villa', address: '11, Heights, Oceania', images: {thumbnail: 'http://hmp.me/ol7

{name: 'Mr. Johns Conch house', address: '12th Street, Neverland', images: {thumbnail: 'http://hmp.me/ol5

{name: 'Mr. Pauls Mansion', address: '625, Sec-5,  Ingsoc', images: {thumbnail: 'http://hmp.me/ol6

{name: 'Mr. Nalwayas Villa', address: '11, Heights, Oceania', images: {thumbnail: 'http://hmp.me/ol7

];

在这段代码中,我们添加了更多的条目来创建一个可滚动的视图。现在,让我们看看我们在ListProperty组件中所做的更改:

var ListProperty = React.createClass({

getInitialState: function() {

var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

return {

dataSource: ds.cloneWithRows(MOCK_DATA),

};

},

render: function() {

return (

<ListView

dataSource={this.state.dataSource}

renderRow={this.renderProperty}

style={styles.listView}/>

);

},

renderProperty: function(property) {

return (

<View style={styles.container}>

<Image

source={{uri: property.images.thumbnail}}

style={styles.thumbnail}/>

<View style={styles.rightContainer}>

<Text style={styles.name}>{property.name}</Text>

<Text style={styles.address}>{property.address}</Text>

</View>

</View>

);

},

});

这里我们设置getInitialState的组件规格:

getInitialState: function() {

var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

return {

dataSource: ds.cloneWithRows(MOCK_DATA),

};

},

在安装组件之前,getInitialState仅被调用一次。返回值将作为this.state的初始值。

接下来,我们修改了render函数,这样,一旦我们有了数据,它就会呈现一个ListView而不是一个条目:

render: function() {

return (

<ListView

dataSource={this.state.dataSource}

renderRow={this.renderProperty}

style={styles.listView}/>

);

},

renderProperty: function(property) {

return (

<View style={styles.container}>

<Image

source={{uri: property.images.thumbnail}}

style={styles.thumbnail}/>

<View style={styles.rightContainer}>

<Text style={styles.name}>{property.name}</Text>

<Text style={styles.address}>{property.address}</Text>

</View>

</View>

);

},

});

你会注意到我们使用了this.state中的dataSource,它已经由getIntialState设置好了。我们还调用了renderProperty函数来设置每一行的图像、名称和地址。最后,我们添加了一些样式:

listView: {

paddingTop: 20,

backgroundColor: '#F5FCFF',

},

卷动检视

虽然我们没有在家里使用ScrollView,但是application, to populate a list we can use ScrollView just like we used ListView. ScrollView是 iOS 中最通用和最有用的控件之一,因为它是一种列出比屏幕尺寸大的内容的好方法。

我们可以通过使用下面的代码添加一个基本的ScrollView:

var scrollview = React.createClass({

getInitialState: function() {

return {

values: values

};

},

_renderRow: function(value, index) {

return (

<View

style={styles.row}

key={index}

>

<Text>{value + "  <----- Slide the row "}</Text>

</View>

)

},

render: function() {

return (

<View style={styles.container}>

<ScrollView style={styles.outerScroll}>

{this.state.values.map(this._renderRow, this)}

</ScrollView>

</View>

);

}

});

我们将为ScrollView设置getInitialState,如下所示:

var values = [1,2,3,4]

我们可以将这些值映射到_renderRow函数,该函数将返回一个基本视图,并带有一些文本。这是基本的ScrollView;如果我们想要水平滚动,并且我们想要锁定那个方向,我们可以这样做:

<ScrollView

horizontal={true}

directionalLockEnabled={true}

>

使用ScrollView还有许多其他选项可用;有关文档和示例,您可以访问以下网址: https://facebook.github.io/react-native/docs/scrollview.html

摘要

在这一章中,我们学习了一些创造令人惊叹的用户体验的基础知识。我们讨论了以下内容:

  • 跨应用的向后滑动功能导航
  • Flexbox 布局模型
  • TouchableHighlight,一个让视图正确响应触摸的包装器
  • 路由到另一个组件
  • 使用ListView高效滚动垂直列表
  • 使用ScrollView列出大于屏幕尺寸的内容。

在下一章,我们将学习如何通过使用 Flux 以不同的方式解决问题。我们不仅会发现如何使用 flux 模式,还会发现它与无处不在的 MVC 模式有什么不同。我们还将使用 React 创建一个简单的 flux 应用,并将其移植到 React Native 中。

四、Flux:以不同的方式解决问题

简单是可靠的先决条件—吉克斯特拉

Flux 是脸书向世界介绍的一种应用架构,用于构建客户端应用。它通过利用单向数据流来补充 React 本机可组合视图组件。与其说它是一个合适的框架,不如说它是一种模式,人们可以立即开始使用 Flux,而不需要过多的代码。在我们深入研究细节之前,了解常用的流行模式 MVC 以及它与 Flux 的区别是很重要的。在本章中,我们将了解以下主题:

  • MVC 模式
  • MVC 问题
  • 流量
  • Flux 深潜
  • 带电抗器的 Flux 示例
  • React 原生的 Flux 示例

MVC 模式

历史上,MVC 模式将代码分成三个不同的部分:模型、视图和控制器。这种模式的主要目的是将信息的表示与用户交互隔离开来。让我们分别了解这些部分:

  • 模型:管理应用的行为和数据
  • 视图:用户界面中模型的表现层
  • 控制器:接受用户输入并对模型进行必要的操作,使视图得到更新

MVC 问题

MVC 是设计应用的一种非常流行的模式,但是它也有自己的问题。你的源代码变得越复杂,事情就变得越复杂。图 4-1 显示了 MVC 最简单的实现,它在小型应用中运行良好。但是随着应用的增长,它需要新的特性,所以应该有空间来容纳更多的模型和视图。

A978-1-4842-1395-7_4_Fig1_HTML.gif

图 4-1。

Basic MVC implementation

图 4-2 显示了当模型和视图增加时会发生什么。

A978-1-4842-1395-7_4_Fig2_HTML.gif

图 4-2。

An explosion of arrows

有如此多的模型和视图相互交互,当我们跟踪一个模型时,它触发一个视图,视图触发另一个模型,这就像意大利面条一样继续下去,很多时候以无限循环结束。最糟糕的是,在这种情况下调试代码真的很难,最终使系统变得脆弱。脸书也面临过类似的挫折,他用一种叫做“流动”的新模式解决了这个问题。

流量

Flux 放弃 MVC,支持单向数据流。Flux 工作得很好,因为随着应用的增长和变得更加复杂,单向数据流使得理解和修改应用变得容易。之前,我们发现双向数据绑定会导致级联更新,其中一个数据模型的更改会导致另一个数据模型的更新,这使得很难预测单个用户交互的结果会是什么。如图 4-3 所示,Flux 应用有三个主要部分:dispatcher、stores 和 views(这里我们使用 React 本地组件)。这些不应该与 MVC 模式的模型视图控制器元素相比较。

A978-1-4842-1395-7_4_Fig3_HTML.jpg

图 4-3。

Three major components of Flux

图片来源: https://github.com/facebook/flux/blob/master/docs/img/flux-diagram-white-background.png

虽然控制器确实存在于 Flux 应用中,但它们是控制器视图,视图位于从存储中检索数据并将其转发给其子节点的层次结构的顶部。查看图 4-4 ,Flux 架构最重要的部分是调度程序,它是一个单独的程序,指导数据流并确保更新不会级联。

A978-1-4842-1395-7_4_Fig4_HTML.gif

图 4-4。

Dispatcher directs the flow of data

随着应用的增长,调度程序变得越来越重要,因为它负责通过以特定的顺序调用注册的回调来管理存储之间的依赖关系。

当用户与 React 本地视图交互时,视图通过 dispatcher 发送一个动作(通常表示为带有一些字段的 JavaScript 对象),该动作通知保存应用数据和业务逻辑的各个存储。当存储改变状态时,它们通知视图某些东西已经被更新。这与 React Native 的声明性模型配合得特别好,该模型允许存储发送更新,而无需指定如何在状态之间转换视图。

简而言之,Flux 具有以下优势:

  • 提高数据一致性
  • 更容易找出错误
  • 提供更有意义的单元测试;因为一个模块的所有状态都在同一个地方,所以我们可以独立地测试一个模块
  • 包含可预测的代码

Flux 的成功

脸书最受欢迎的功能之一是聊天。然而,它是极其错误的,并且有最负面的用户反馈。脸书实现的新聊天系统使用了 Flux,它现在有了无缝的体验;您可以在以下 URL 查看脸书 React 本地示例中的示例聊天代码:

https://github.com/facebook/flux/tree/master/examples/flux-chat

Flux 深潜

在这一节中,我们将进一步了解 Flux 的一些核心概念。

调度员

调度程序是管理 Flux 应用中所有数据流的中心枢纽。它本质上是一个对存储回调的注册表,本身没有真正的智能;这是一种简单的机制,用于将操作分配给商店。每个存储注册自己并提供回调。当操作创建者向调度程序提供新的操作时,应用中的所有存储都通过注册表中的回调接收该操作。调度员也像交通管理员一样工作。如果它在数据层仍在处理时得到一个动作,它可以拒绝这个动作,这是一个很好的约束。这保证了您将知道您的操作何时开始以及它对数据层产生了什么变化,因为在这两者之间会产生级联效应—您确实可以完全控制您的系统。

对调度员[ dispatch()waitFor() ]的需求

随着应用的增长,不同存储之间的依赖性也会增加。假设我们有这样一种情况,商店 A 需要商店 B 首先更新自己,这样它自己就可以知道如何更新。我们需要调度程序调用对存储 B 的回调,并在继续处理存储 A 之前完成该回调。为了断言这种依赖性,存储 A 需要与调度程序通信,以首先完成更新存储 B 的操作。调度程序通过waitFor()方法提供该功能。

dispatch()方法通过回调提供了一个简单的同步迭代,依次调用每个回调。当在一个回调中遇到waitFor()时,该回调的执行停止,waitFor()为我们提供了一个新的依赖项迭代周期。在完成了整个依赖集之后,最初的回调将继续执行。

此外,在同一个存储的回调中,waitFor()方法可以以不同的方式用于不同的动作。在一种情况下,存储 A 可能需要等待存储 b。但在另一种情况下,它可能需要等待存储 c。在特定于某个操作的代码块中使用waitFor()允许我们对这些依赖关系进行细粒度控制。

然而,如果我们有循环依赖,问题就出现了。也就是说,如果商店 A 需要等待商店 B,而商店 B 需要等待商店 A,我们可能会陷入无限循环。Flux repo 中现在可用的 dispatcher 通过抛出一个信息性错误来提醒开发人员这个问题已经发生,从而防止了这种情况。然后,开发人员可以创建第三个存储并解决循环依赖。

商店

存储包含应用状态和逻辑。它们的角色有点类似于传统 MVC 中的模型,但是它们管理许多对象的状态——它们不像 ORM 模型那样表示单一的数据记录。存储不仅仅是管理一组 ORM 样式的对象,它还管理应用中特定域的应用状态。

如前所述,存储向调度程序注册自己,并为其提供回调。这个回调接收动作作为参数。在商店注册的回调中,基于动作类型的switch语句用于解释动作,并为商店的内部方法提供适当的挂钩。这允许一个操作通过 dispatcher 导致存储状态的更新。在存储被更新后,它们广播一个事件,声明它们的状态已经改变,因此视图可以查询新的状态并更新它们自己。

行动

当新数据进入系统时,无论是通过与应用交互的人还是通过 web API 调用,该数据都被打包到一个动作中——一个包含新数据字段和特定动作类型的对象文字。我们经常创建一个叫做动作创建器的辅助方法库,它不仅创建动作对象,还将动作传递给调度程序。

不同的动作由一个type属性标识。当所有商店接收到该动作时,它们通常使用该属性来确定是否以及如何响应该动作。在 Flux 应用中,存储和视图都控制自己;外部物体不作用于它们。动作通过它们定义和注册的回调流入存储,而不是通过 setter 方法。

让存储自行更新消除了 MVC 应用中常见的许多问题,在 MVC 应用中,模型之间的级联更新会导致不稳定的状态,并使准确的测试变得非常困难。Flux 应用中的对象是高度解耦的,并且非常严格地遵守 Demeter 定律,即系统中的每个对象应该尽可能少地了解系统中的其他对象。这使得软件更易维护、适应性更强、可测试,并且更易于新的工程团队成员理解。

带 React 剂的焊剂示例

在本节中,我们将使用 Flux 创建一个简单的 ReactJS 应用。我们尽量不举这个例子,目的是通过应用实现来理解上一节中的理论解释。

您可以参考所提供的 flux-with-react 源代码并遵循它,也可以创建一个全新的 ReactJS 应用。在 flux-with-react 的根目录下,您需要安装所需的软件包。

这个例子是一个 web 应用,我们将在其中使用几个 npm 模块。使用以下内容初始化您的项目:

$ npm init

这将初始化您的项目;填写所需信息。接下来,让我们安装一些节点模块,这对我们的项目很有帮助:

$ npm install –save-dev react reactify object-assign gulp gulp-browserify gulp-concat es6-promise flux

在所有这些包中,最重要的是 eact,它是 ReactJS 的 JavaScript 库;reactify,帮助将 ES6 语法转换成 ES5 语法结构;gulp,它内置了系统帮助,可以在开发工作流程中自动完成痛苦的任务;最后是 flux,脸书的 npm 模块,用于实现 Flux 架构。

接下来,创建gulpfile.js并向其添加以下任务:

var gulp = require('gulp');

var browserify = require('gulp-browserify');

var concat = require('gulp-concat');

gulp.task('browserify', function() {

gulp.src('src/js/main.js')

.pipe(browserify({transform:'reactify'}))

.pipe(concat('main.js'))

.pipe(gulp.dest('dist/js'));

});

gulp.task('copy', function() {

gulp.src('src/index.html')

.pipe(gulp.dest('dist'));

});

gulp.task('default',['browserify', 'copy'], function() {

return gulp.watch('src/**/*.*', ['browserify', 'copy'])

});

这里,我们有两个任务:browserifycopybrowserify所做的是抓取、转换和连接main.js(我们的应用代码所在的地方)并把它放在我们的分发文件夹中,这个文件夹就是dist。下一个任务copyindex.html复制到dist文件夹中。

我们还有另一个任务default,它既运行这两个任务,也持续观察与这些任务相关的文件是否有任何变化。

接下来,让我们创建我们的文件夹结构,它应该支持 Flux 架构。用根文件夹中的空文件创建如图 4-5 所示的文件夹结构:

A978-1-4842-1395-7_4_Fig5_HTML.jpg

图 4-5。

New Flux file structure

现在,让我们从头开始创建我们的应用。在您的文件index.html中添加以下代码:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>Flux example with ReactJS</title>

</head>

<body>

<div id="main" class="container"></div>

<script src="js/main.js"></script>

</body>

</html>

这个文件中发生的事情不多;它是我们加载our main.js的 html 文件,是我们所有 JavaScript 代码的入口点。id main是我们的组件将被加载的目标。接下来,在src/main.js中使用下面的代码:

var React = require('react');

var App = require('./components/app');

React.render(

<App />,

document.getElementById('main')

);

在这里的src/main.js中,我们使用变量React来获取react,,我们使用变量App来获取来自./components/app.js的组件。我们在目标 id main上渲染App组件——所有 Flux 的魔法都在这里发生。接下来,我们通过在src/components/app.js中添加以下代码来创建我们的组件:

var React = require('react');

var AppActions = require('../actions/app-actions');

var AppStore = require('../stores/app-store');

var AppTitle = React.createClass({

onClickHandler:function(){

AppActions.addElement('element added');

},

render:function(){

return (

<div className="wrapper">

<h3 onClick={this.onClickHandler}>Open console and Click Here </h3>

</div>

)

}

});

module.exports = AppTitle;

同样,我们很少有require语句来抓取 React、动作和存储。接下来,使用createClass我们创建一个AppTitle组件,它有一个带有div的渲染方法,该方法有一个带有onClick事件的h3标签。我们还有另一个函数onClickHandler,每当点击h3标签时,就会触发一个onClick事件。

让我们仔细看看onClickHandler函数:

onClickHandler:function(){

AppActions.addElement('element added');

},

这里的AppActions是一个 fire and forget,它有一个要执行的动作、addElement(我们将在进入动作时研究它)和一些要传递的数据。让我们现在打开src/actions/app-actions.js并粘贴以下代码在里面:

var AppDispatcher = require('../dispatcher/app-dispatcher');

var AppConstants = require('../constants/app-constants');

var AppActions = {

addElement: function(param){

AppDispatcher.handleAction({

actionType:AppConstants.ADD_ELEMENT,

description: param

})

}

}

module.exports = AppActions

同样,我们在开始时有一些必需的项目——dispatcher 和一些常量。如果你还记得,在我们的App组件代码中,我们在onClickHandler中调用了addElement函数。该功能描述如下:

addElement: function(param){

AppDispatcher.handleAction({

actionType:AppConstants.ADD_ELEMENT,

description: param

})

}

addElement函数有一个对象描述,它将该描述作为参数接收,然后传递给调度程序。我们还有一个这里描述的actionType,它是我们从AppConstants那里得到的。在src/constants/app-constants.js中添加以下代码:

module.exports = {

ADD_ELEMENT: 'ADD_ELEMENT'

};

这里,我们创建了几个可以在应用中重用的常量。让我们转到 dispatcher,并将以下代码添加到src/dispatcher/app-dispatcher.js:

var Dispatcher = require('flux').Dispatcher;

var assign = require('object-assign');

var AppDispatcher = assign(new Dispatcher(), {

handleAction: function(action) {

this.dispatch({

source: 'TEST_ACTION',

action: action

});

}

});

module.exports = AppDispatcher;

这里,我们使用了Dispatcher变量来从 flux 模块中调用 dispatcher。如果你还记得,在app-actions.js中,我们调用了函数handleAction;这里我们描述了它的功能。从这个动作中,我们必须发送一个对象,它在这里被用作一个参数,它被进一步包装并被赋予上下文作为TEST_ACTION。然后使用this.dispatch进行广播。商店收听该广播对象。让我们将下面的代码添加到src/stores/app-stores.js:

var AppDispatcher = require('../dispatcher/app-dispatcher');

var EventEmitter = require('events').EventEmitter;

var assign = require('object-assign');

var CHANGE_EVENT = 'change';

var AppStore = assign({}, EventEmitter.prototype, {

emitChange: function() {

this.emit(CHANGE_EVENT);

}

});

AppDispatcher.register(function(payload){

console.log(payload);

return true;

});

module.exports = AppStore;

为了开始使用商店,我们将使用 node 的EventEmitter。我们需要一个EventEmitter来向我们的控件视图广播change事件。然而,在我们的例子中,即使我们保持我们的emit函数为空,它也不会工作,因为在我们的控制视图上没有任何变化。现在让我们回顾一下重要的部分:所有的事情都发生在哪里:

AppDispatcher.register(function(payload){

console.log(payload);

return true;

});

存储注册到AppDispatcher并获取有效负载——这是由调度程序广播的对象。在这种情况下,我们在控制台上记录这个有效载荷,这样每次你点击屏幕上的h3标签,你就会看到控制台上记录的有效载荷。让我们创建一个构建:

$ gulp

在浏览器中打开dist/index.html以及您的开发工具。点击“打开控制台并点击此处”,您将看到如图 4-6 所示的结果。

A978-1-4842-1395-7_4_Fig6_HTML.jpg

图 4-6。

Result of $ gulp build

既然我们已经很好地理解了 Flux 以及它如何与 React 一起工作,那么是时候使用所有这些知识来用 React Native 实现相同的任务了。

Flux 与 React 本地示例

在上一节中,我们学习了如何在 ReactJS 中使用 Flux。React Native 的美妙之处在于,我们可以使用在 ReactJS 中学到的概念,通过 React Native 和 Flux 为 iOS 应用实现类似的任务。在本节中,我们将使用 Flux 和 React Native 创建一个经典的 ToDo 应用。

首先,让我们通过创建一个新的 React 本机应用来设置我们的环境:

$ react-native init FluxTodo

尽管我们已经将我们的应用命名为 FluxTodo,但这并不意味着我们即将构建一个 Flux 应用。我们需要添加一些 npm 模块,以便开始构建我们的应用。让我们将以下节点模块添加到应用中:

$ npm install –save-dev events key-mirror object-assign flux

接下来,为了简单起见,我们将把所有代码放在index.ios.js中;然而,将动作、调度程序、存储和组件分开是一个很好的实践。

让我们首先要求一个调度程序,并获取我们的应用所必需的几个重要模块。在index.ios.js中添加以下代码:

'use strict';

var React = require('react-native');

var ReactPropTypes = React.PropTypes;

var assign = require('object-assign');

var keyMirror = require('key-mirror');

var EventEmitter = require('events').EventEmitter;

var Dispatcher = require('flux').Dispatcher;

var AppDispatcher = new Dispatcher();

这里,我们已经获取了 dispatcher 并创建了一个新实例AppDispatcher,我们稍后将在我们的操作中使用它。

接下来,添加我们将在应用中使用的常数:

var TodoConstants = keyMirror({

TODO_CREATE: null,

TODO_COMPLETE: null,

TODO_DESTROY: null,

TODO_UNDO_COMPLETE: null

});

现在让我们将 ToDo 应用的操作添加到index.ios.js:

var TodoActions = {

create: function(text) {

AppDispatcher.dispatch({

actionType: TodoConstants.TODO_CREATE,

text: text

});

},

toggleComplete: function(todo) {

var id = todo.id;

if (todo.complete) {

AppDispatcher.dispatch({

actionType: TodoConstants.TODO_UNDO_COMPLETE,

id: id

});

} else {

AppDispatcher.dispatch({

actionType: TodoConstants.TODO_COMPLETE,

id: id

});

}

},

destroy: function(id) {

AppDispatcher.dispatch({

actionType: TodoConstants.TODO_DESTROY,

id: id

});

}

};

这里我们创建了TodoActions,这是一个通过分配一个actionType—TodoConstants.TODO_CREATE来向您的待办事项列表添加新条目的函数。同样,我们有actionTypes对应TODO_UNDO_COMPLETETODO_COMPLETETODO_DESTROY。现在让我们使用存储为这些操作添加逻辑。将以下代码添加到index.ios.js:

var CHANGE_EVENT = 'change';

var _todos = {};

function create(text) {

var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);

_todos[id] = {

id: id,

complete: false,

text: text

};

}

function update(id, updates) {

_todos[id] = assign({}, _todos[id], updates);

}

function destroy(id) {

delete _todos[id];

}

var TodoStore = assign({}, EventEmitter.prototype, {

getAll: function() {

var todos = [];

for(var key in _todos) {

todos.push(_todos[key]);

}

return todos;

},

emitChange: function() {

this.emit(CHANGE_EVENT);

},

addChangeListener: function(callback) {

this.on(CHANGE_EVENT, callback);

},

removeChangeListener: function(callback) {

this.removeListener(CHANGE_EVENT, callback);

}

});

AppDispatcher.register(function(action) {

var text;

switch(action.actionType) {

case TodoConstants.TODO_CREATE:

text = action.text.trim();

if (text !== '') {

create(text);

TodoStore.emitChange();

}

break;

case TodoConstants.TODO_UNDO_COMPLETE:

update(action.id, {complete: false});

TodoStore.emitChange();

break;

case TodoConstants.TODO_COMPLETE:

update(action.id, {complete: true});

TodoStore.emitChange();

break;

case TodoConstants.TODO_DESTROY:

destroy(action.id);

TodoStore.emitChange();

break;

default:

}

});

最后,让我们添加组件和一些样式。还要注册 MainSection 组件:

var MainSection = React.createClass({

propTypes: {

todos: ReactPropTypes.object.isRequired

},

render: function() {

return (

<View>

<ListView dataSource={this.props.todos} renderRow={this.renderItem} />

</View>

);

},

renderItem: function(todo) {

return <TodoItem todo={todo} />;

}

});

var TodoItem = React.createClass({

render: function() {

var todo = this.props.todo;

var todoItemStyle;

todoItemStyle = (todo.complete) ? styles.TodoItemDone: styles.TodoItem;

return (

<View style={todoItemStyle}>

<Text style={styles.text}>{todo.text}</Text>

<Text onPress={() => this._onToggleComplete(todo)}>[Complete]</Text>

<Text onPress={() => this._onDestroy(todo)}>[Delete]</Text>

</View>

);

},

_onToggleComplete: function(todo) {

TodoActions.toggleComplete(todo);

},

_onDestroy: function(todo) {

TodoActions.destroy(todo.id);

}

});

var Header = React.createClass({

render: function() {

return (

<View>

<TodoTextInput />

</View>

);

}

});

var TodoTextInput = React.createClass({

getInitialState: function() {

return {

value: ''

}

},

render: function() {

return (

<View>

<TextInput

style={styles.TodoTextInput}

onChangeText={(text) => this.setState({value: text})}

onBlur={this._save}

placeholder={'What needs to be done?'}

value={this.state.value}

/>

</View>

);

},

_save: function() {

var text = this.state.value;

if(text) TodoActions.create(text);

this.setState({

value: ''

});

}

});

最后,让我们添加样式:

var styles = StyleSheet.create({

TodoApp: {

padding: 20,

paddingTop: 40

},

TodoItem: {

flex: 1,

flexDirection: 'row',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#FFFFFF',

height: 58

},

TodoItemDone: {

flex: 1,

flexDirection: 'row',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#FFFFFF',

height: 58,

opacity: .3

},

text: {

flex: 1,

textAlign: 'left',

fontSize: 16

},

TodoTextInput: {

height: 40,

backgroundColor: '#EEEEEE',

padding: 10,

fontSize: 16

}

});

AppRegistry.registerComponent(‘FluxTodo’, () => FluxTodo);

是时候用 Xcode 构建我们的应用来测试我们所做的事情了。启动 Xcode 并构建 FluxTodo 应用。您将看到如下屏幕,如图 4-7 所示。

A978-1-4842-1395-7_4_Fig7_HTML.jpg

图 4-7。

MainSection of the todo application to add todo tasks

我们可以在文本框中添加任何待办事项任务,该任务将立即填充到文本框下方,如图 4-8 所示。

A978-1-4842-1395-7_4_Fig8_HTML.jpg

图 4-8。

Populated the todo list

我们还可以选择将任务标记为完成或删除它。如果任务被删除,它将从列表中移除,并且当任务被标记为完成时,它将变淡,如图 4-9 所示。

A978-1-4842-1395-7_4_Fig9_HTML.jpg

图 4-9。

The done and deletes tasks from the populated list

摘要

在这一章中,我们学习了 Flux 模式,以及 Flux 与传统 MVC 模式的不同之处,以及它解决问题的方式。我们还深入研究了 Flux 核心概念,然后查看了在项目中使用它的例子。最后,我们用 Flux 创建了一个 React 本地应用。

五、设备功能

一台 iOS 设备不仅仅局限于打电话;这是有史以来发明的最先进的机器之一。真正的力量在于各种设备功能。在本章中,我们将了解以下设备功能:

  • 地理定位
  • 异步存储
  • 本地警报
  • 网络视图
  • 动画片

地理定位

在本节中,您将了解如何通过 React 本机应用使用 iOS 定位服务。定位服务在许多流行的应用中使用非常频繁,尤其是在旅行、导航、出租车共享和不胜枚举的应用中。这个特性极大地改善了用户体验,令人欣慰的是它非常容易实现。

当我们用react-native init设置一个项目时,默认情况下 React Native 中的地理位置属性是启用的。让我们创建一个应用来实现这一功能:

$react-native init GeoLocationMap

使用以下代码更新 index.ios.js:

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

MapView

} = React;

var GeoLocationMap = React.createClass({

getInitialState: function() {

return {

region: {

latitude: 40.712784,

longitude: -74.005941,

latitudeDelta: 10,

longitudeDelta: 10,

},

};

},

render: function() {

return (

<View style={styles.container}>

<View style={styles.desc}>

<Text> Maps </Text>

</View>

<MapView

style={styles.map}

region={this.state.region}

/>

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'column',

backgroundColor: '#F5FCFF',

alignItems: 'stretch'

},

desc: {

flex: 1,

alignItems: 'center',

justifyContent: 'center'

},

map: {

flex: 5,

},

});

AppRegistry.registerComponent('GeoLocationMap', () => GeoLocationMap);

现在,第一次在 Xcode 中构建您的应用,看看图 5-1 中显示的结果。

A978-1-4842-1395-7_5_Fig1_HTML.jpg

图 5-1。

GeoLocation MapView

查看地理地图代码

现在让我们了解一下我们在程序的这一部分做了什么。

var {

AppRegistry,

StyleSheet,

Text,

View,

MapView

} = React;

在这里,我们首先包括 React 本地组件MapView, which是访问位置服务的关键。接下来,我们将创建利用 MapView 的地理定位组件:

var GeoLocationMap = React.createClass({

getInitialState: function() {

return {

region: {

latitude: 40.712784,

longitude: -74.005941,

latitudeDelta: 10,

longitudeDelta: 10,

},

};

},

render: function() {

return (

<View style={styles.container}>

<View style={styles.desc}>

<Text> Maps </Text>

</View>

<MapView

style={styles.map}

region={this.state.region}

/>

</View>

);

}

});

在这里,我们用特定的纬度和经度参数设置了该区域的初始状态,稍后当我们用 MapView 组件呈现该函数时将会设置这些参数。在 MapView 组件中,我们使用 region prop,它提供了 latitude、longitudeDelta 和 latitudeDelta。这些应该总是数字(整数或浮点数),它们帮助我们在地图上绘制特定的区域。最后,我们用 Flex 添加了一些样式,并注册了我们的组件。

在地图上添加注记

接下来,让我们向应用添加注释,并用新的状态注释更新 getInitialState,该状态注释包含纬度、经度、标题和副标题等参数:

getInitialState: function() {

return {

region: {

latitude: 40.712784,

longitude: -74.005941,

latitudeDelta: 10,

longitudeDelta: 10,

},

annotations: [{

latitude: 40.72052634,

longitude:  -73.97686958312988,

title: 'New York',

subtitle: 'This is cool!'

}],

};

},

现在用名为 annotations 的新属性更新 MapView 组件:

<MapView

style={styles.map}

region={this.state.region}

annotations= {this.state.annotations}

/>

除了提到的值,注释属性还可以有以下附加参数:

annotations [{latitude: number, longitude: number, animateDrop: bool, title: string, subtitle: string, hasLeftCallout: bool, hasRightCallout: bool, onLeftCalloutPress: function, onRightCalloutPress: function, id: string}] #

让我们刷新一下,看看图 5-2 所示的变化。

A978-1-4842-1395-7_5_Fig2_HTML.jpg

图 5-2。

MapView with added parameters

显示当前位置的纬度和经度

在我们地理定位应用的最后一部分,我们将在视图上显示我们当前的纬度和经度。在前面的例子中,我们有一个固定的位置;在这一部分,我们将实时飞往我们当前的位置。这听起来很令人兴奋,所以让我们开始建造吧。有两种方法可以在我们的地图上查看当前位置。一种方法是简单地将 showsUserLocation={true}添加到 MapView 组件中。

另一种方法是使用 NSLocationWhenInUseUsageDescription 地理位置。我们将需要更新 info.plist 来添加这个键,但是当您使用react-native init创建一个项目时,它是默认启用的。

用以下代码替换 index.ios.js:

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

MapView

} = React;

var GeoLocationMap = React.createClass({

getInitialState: function() {

return {

region: {

latitude: 40.712784,

longitude: -74.005941,

latitudeDelta: 10,

longitudeDelta: 10,

},

annotations: [{

latitude: 40.72052634,

longitude:  -73.97686958312988,

title: 'New York',

subtitle: 'This is cool!'

}],

};

},

componentDidMount: function() {

navigator.geolocation.getCurrentPosition(

(initialPosition) => this.setState({initialPosition}),

(error) => alert(error.message),

{enableHighAccuracy: true, timeout: 20000, maximumAge: 1000}

);

this.watchID = navigator.geolocation.watchPosition((lastPosition) => {

this.setState({latitude: lastPosition.coords.latitude});

this.setState({longitude: lastPosition.coords.longitude});

var newRegion = {

latitude: lastPosition.coords.latitude,

longitude: lastPosition.coords.longitude,

latitudeDelta: 10,

longitudeDelta: 10,

}

this.setState({  region: newRegion});

this.setState({  annotations: [{

latitude: lastPosition.coords.latitude,

longitude: lastPosition.coords.longitude,

title: 'Current Location',

subtitle: 'You are here'

}]});

});

},

componentWillUnmount: function() {

navigator.geolocation.clearWatch(this.watchID);

},

render: function() {

return (

<View style={styles.container}>

<View style={styles.desc}>

<Text>

latitude:  {this.state.latitude}

</Text>

<Text>

longitude: {this.state.longitude}

</Text>

</View>

<MapView

style={styles.map}

region={this.state.region}

annotations= {this.state.annotations}

/>

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'column',

backgroundColor: '#F5FCFF',

alignItems: 'stretch'

},

desc: {

flex: 1,

alignItems: 'center',

justifyContent: 'center'

},

map: {

flex: 5,

},

});

AppRegistry.registerComponent('GeoLocationMap', () => GeoLocationMap);

现在构建我们的应用,将其加载到图 5-3 所示的 iOS 模拟器上。

A978-1-4842-1395-7_5_Fig3_HTML.jpg

图 5-3。

Access location prompt

如果我们允许这个请求,我们将飞到我们代码中提到的位置;在这种情况下,它是加利福尼亚(图 5-4 )。

A978-1-4842-1395-7_5_Fig4_HTML.jpg

图 5-4。

Fly to a specified location in the code

调试地理定位应用时还有一个额外的步骤。如前所述,在本例中,我们将飞往当前位置。我们已经保存了该需求,以便现在可以完成它。使用 Ctrl + Command + z 打开调试菜单,选择“在 Chrome 中调试”选项,如图 5-5 所示。

A978-1-4842-1395-7_5_Fig5_HTML.jpg

图 5-5。

Select debug in chrome option

一旦你点击“在 Chrome 中调试”,你将得到一个弹出窗口,允许你的位置被提供给 iOS 模拟器应用,如图 5-6 所示。

A978-1-4842-1395-7_5_Fig6_HTML.jpg

图 5-6。

Allow the present location to be shared with the iOS simulator

一旦你允许这样做,你将移动到你在应用中的当前位置(图 5-7 )。

A978-1-4842-1395-7_5_Fig7_HTML.jpg

图 5-7。

Current location is now shown

查看当前位置的地理位置代码

让我们回顾一下我们在本例中编写的程序。

componentDidMount: function() {

navigator.geolocation.getCurrentPosition(

(initialPosition) => this.setState({initialPosition}),

(error) => alert(error.message),

{enableHighAccuracy: true, timeout: 20000, maximumAge: 1000}

);

this.watchID = navigator.geolocation.watchPosition((lastPosition) => {

this.setState({latitude: lastPosition.coords.latitude});

this.setState({longitude: lastPosition.coords.longitude});

var newRegion = {

latitude: lastPosition.coords.latitude,

longitude: lastPosition.coords.longitude,

latitudeDelta: 10,

longitudeDelta: 10,

}

这里,在 componentDidMount 中,我们首先获取当前位置;默认情况下,iOS 模拟器会给我们一个默认值,您可以使用调试➤位置来更改它。如果 navigator . geolocation . getcurrentposition 确实获得了值,那么我们设置各种状态及其参数。在这个例子中,我们有位于视图顶部的纬度和经度描述,并使用 this.setState({latitude :})和 this.setState({longitude:})进行设置。它下面的地图是用 newRegion 绘制的。

this.setState({  region: newRegion});

this.setState({  annotations: [{

latitude: lastPosition.coords.latitude,

longitude: lastPosition.coords.longitude,

title: 'Current Location',

subtitle: 'You are here'

}]});

});

},

每当状态改变时,我们使用 React 的强大功能来重新呈现组件。使用上面的代码片段,我们将使用更新的坐标重新渲染 newRegion。

componentWillUnmount: function() {

navigator.geolocation.clearWatch(this.watchID);

},

使用 componentWillUnmount,我们将清除 navigator.geolocation,以防我们移动到应用的其他部分。

render: function() {

return (

<View style={styles.container}>

<View style={styles.desc}>

<Text>

latitude:  {this.state.latitude}

</Text>

<Text>

longitude: {this.state.longitude}

</Text>

</View>

<MapView

style={styles.map}

region={this.state.region}

annotations= {this.state.annotations}

/>

</View>

);

}

});

最后,我们在视图上呈现组件,纬度和经度描述具有各自的样式,更新的 MapView 组件具有各自的样式、区域映射和注释。

异步存储

AsyncStorage 是一个基于键值的存储系统。它可以很容易地实现,并在全球范围内提供给应用。这个持久性系统简单且异步,也是存储数据的推荐方式。

让我们创建一个 AsyncStorage 示例应用;为此,请执行以下命令:

$react-native init AsyncStorage

很好,现在让我们在 index.ios.js 中添加以下代码

'use strict' ;

var React``= require(``'react-native'

var {

AppRegistry ,

StyleSheet ,

Text ,

View ,

TextInput ,

TouchableHighlight ,

AsyncStorage

} =``React

var STORAGE_KEY``=``'@AsyncStorageExample:someKey'

var AsyncStorageExample = React . createClass

getInitialState:``function

return {

messages``:``''

textInputMessage``:

};

},

componentDidMount() {

AsyncStorage``.getItem(``STORAGE_KEY

this .addMessage(value);

}).done();

},

addMessage(message) {

this``.setState({``messages

},

updateStorage () {

AsyncStorage``.setItem(``STORAGE_KEY``,``this``.``state``.``textInputMessage``);

AsyncStorage``.getItem(``STORAGE_KEY

this .addMessage(value);

}).done();

},

render:``function

return (

<``View style=``{``styles``.``container

<``View style=``{``styles``.``form

<``TextInput style=``{``styles``.``textField

onChangeText= {(textInputMessage) => this .setState({ textInputMessage

value= { this . state . textInputMessage }/>

<``TouchableHighlight style=``{``styles``.``button``}``onPress=``{``this``.updateStorage}>

<``Text``> Update Storage </``Text

</``TouchableHighlight

</``View

<``View style=``{``styles``.``message

<``Text``> Text from local storage:</``Text

<``Text``>{``this``.``state``.``messages``} </``Text``>

</``View

</``View

);

}

});

var styles``=``StyleSheet

container : {

flex : 1,

justifyContent``:``'center'

alignItems``:``'center'

backgroundColor``:``'#F5FCFF'

},

form : {

flex : 1,

justifyContent``:``'center'

alignItems``:

},

textField : {

height : 40,

borderColor``:``'gray'

borderWidth : 1,

width : 180},

message : {

flex : 1,

alignItems``:

},

button : {

backgroundColor``:``'#05A5D1'

marginTop : 10,

height : 40,

width : 180,

alignItems``:``'center'

justifyContent``:

}

});

AppRegistry .registerComponent( 'AsyncStorage' , () => AsyncStorageExample

让我们用 Xcode 构建我们的应用来看看结果。您可以在如图 5-8 所示的文本框中输入文本,然后点击“更新存储”

A978-1-4842-1395-7_5_Fig8_HTML.jpg

图 5-8。

Storage is updated

完成后,刷新结果,如图 5-9 所示。

A978-1-4842-1395-7_5_Fig9_HTML.jpg

图 5-9。

Text from the async storage mechanism

这次“来自本地存储的文本”下面的文本来自异步存储机制,这是我们已经设置好的。

查看异步存储代码

在本例中,我们已经将 AsyncStorage 默认组件包含在我们的用于此应用的组件列表中。

var React``= require(``'react-native'

var {

AppRegistry ,

StyleSheet ,

Text ,

View ,

TextInput ,

TouchableHighlight ,

AsyncStorage

} =``React

var STORAGE_KEY``=``'@AsyncStorageExample:someKey'

我们将在 AsyncStorageExample 组件中使用这个 AsyncStorage React 组件。上面,我们还指定了一个键,我们将在异步存储中使用它。

在我们的 AsyncStorageExample 组件中,我们设置了 getInitialState 和 componentDidMount 方法,还创建了 addMessage 和 updateStorage。让我们逐一讨论。

getInitialState:``function

return {

messages``:``''

textInputMessage``:

};

},

在 getInitialState 中,我们为 messages & textInputMessages 指定了空白值,当它们的状态改变时,我们将不断更新这些值。

componentDidMount() {

AsyncStorage``.getItem(``STORAGE_KEY

this .addMessage(value);

}).done();

},

使用 componentDidMount AsyncStorage,getItem 方法将加载 addMessage 值。这仅在初始渲染时调用,并负责在我们更新存储并再次刷新应用后,显示“来自本地存储的文本”下方的文本。

addMessage(message) {

this``.setState({``messages

},

每当 addMessage 方法被触发时,它就用新的更新值更新消息状态。

updateStorage () {

AsyncStorage``.setItem(``STORAGE_KEY``,``this``.``state``.``textInputMessage``);

AsyncStorage``.getItem(``STORAGE_KEY

this .addMessage(value);

}).done();

},

更新存储更新永久保存的异步存储值。

render:``function

return (

<``View style=``{``styles``.``container

<``View style=``{``styles``.``form

<``TextInput style=``{``styles``.``textField

onChangeText= {(textInputMessage) => this .setState({ textInputMessage

value= { this . state . textInputMessage }/>

<``TouchableHighlight style=``{``styles``.``button``}``onPress=``{``this``.updateStorage}>

<``Text``> Update Storage </``Text

</``TouchableHighlight

</``View

<``View style=``{``styles``.``message

<``Text``> Text from local storage:</``Text

<``Text``>{``this``.``state``.``messages``} </``Text``>

</``View

</``View

);

}

});

使用上面的代码,我们设置了 AsyncStorageExample 组件的各个部分。在这里,我们可以更改文本输入字段来更新 textInputMessage 状态。我们还有一个用于 TouchableHighlight 组件的 onPress prop,它调用 updatedStorage 方法并永久保存值。最后,我们通过访问消息的当前状态来显示保存的消息。

var styles``=``StyleSheet

container : {

flex : 1,

justifyContent``:``'center'

alignItems``:``'center'

backgroundColor``:``'#F5FCFF'

},

form : {

flex : 1,

justifyContent``:``'center'

alignItems``:

},

textField : {

height : 40,

borderColor``:``'gray'

borderWidth : 1,

width : 180},

message : {

flex : 1,

alignItems``:

},

button : {

backgroundColor``:``'#05A5D1'

marginTop : 10,

height : 40,

width : 180,

alignItems``:``'center'

justifyContent``:

}

});

AppRegistry .registerComponent( 'AsyncStorage' , () => AsyncStorageExample

最后,我们用一些不言自明的 Flex 设置建立了一个用户界面样式,并注册了我们的 AsyncStorageExample 组件。

本地警报

警报用于向应用用户提供重要信息。就 iOS 而言,只有在警报视图中选择选项后,我们才能进一步使用该应用。或者,我们可以提供一个按钮列表。点击任何按钮都会触发相应的 onPress 回调并解除警报。默认情况下,只有一个按钮。

让我们创建一个项目来了解更多关于本机警报的信息:

$react-native init NativeAlert

React Native 为创建警告框提供了一个组件警告。让我们创建一个按钮,点击时会打开一个警告框。

使用以下代码更新 index.ios.js:

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

TouchableHighlight,

AlertIOS

} = React;

var NativeAlert = React.createClass({

render: function() {

return (

<View style={styles.container}>

<TouchableHighlight style={styles.wrapper}

onPress={() => AlertIOS.alert(

'Alert Title',

'Alert Message'

)}>

<View style={styles.button}>

<Text style={styles.buttonText}>Click me !!</Text>

</View>

</TouchableHighlight>

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1,

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#FFFFFF',

},

button: {

backgroundColor: '#659EC7',

padding: 10,

margin: 10

},

buttonText: {

color: '#FFFFFF'

}

});

AppRegistry.registerComponent('NativeAlert', () => NativeAlert);

让我们构建应用并在模拟器中测试它。图 5-10 显示了结果。

A978-1-4842-1395-7_5_Fig10_HTML.jpg

图 5-10。

This button will open an alert box when clicked

轻点“点击我!!"按钮看到如图 5-11 所示的警告框。

A978-1-4842-1395-7_5_Fig11_HTML.jpg

图 5-11。

The Alert box appears

查看 NativeAlert 代码

创建新的 NativeAlert 项目后,我们创建了一个新的组件 NativeAlert:

var NativeAlert = React.createClass({

render: function() {

return (

<View style={styles.container}>

<TouchableHighlight style={styles.wrapper}

onPress={() => AlertIOS.alert(

'Alert Title',

'Alert Message'

)}>

<View style={styles.button}>

<Text style={styles.buttonText}>Click me !!</Text>

</View>

</TouchableHighlight>

</View>

);

}

});

在组件 NativeAlert 中,我们使用了 onPress 回调。方法 alert 传递字符串“Alert Title”和“Alert Message ”,这将产生一个具有标题、消息和按钮的警告框。AlertIOS 提供了两种方式:预警和提示。

static alert(title: string, message?: string, buttons?: Array<{ text: ?string; onPress?: ?Function; }>, type?: string)

static prompt(title: string, value?: string, buttons?: Array<{ text: ?string; onPress?: ?Function; }>, callback?: Function)

最后,我们在下面的部分中添加了一些样式,并用 AppRegistry 注册了我们的根组件。

var styles = StyleSheet.create({

container: {

flex: 1,

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#FFFFFF',

},

button: {

backgroundColor: '#659EC7',

padding: 10,

margin: 10

},

buttonText: {

color: '#FFFFFF'

}

});

AppRegistry.registerComponent('NativeAlert', () => NativeAlert);

扩展 NativeAlert 示例

现在,让我们在应用中添加更多按钮,替换 index.ios.js 中 NativeAlert 组件的以下代码:

var NativeAlert = React.createClass({

getInitialState: function(){

return{

textForButton: 'Button text will come here'

}

},

render: function() {

return (

<View style={styles.container}>

<TouchableHighlight style={styles.wrapper}

onPress={() => AlertIOS.alert(

'Alert Title',

'Alert Message'

)}>

<View style={styles.button}>

<Text style={styles.buttonText}>Click me !!</Text>

</View>

</TouchableHighlight>

<TouchableHighlight style={styles.wrapper}

onPress={() => AlertIOS.alert(

'Alert Title',

'Alert Message',

[

{text: 'Button 1', onPress: () => this.setState({textForButton: 'Button 1 clicked'})},

{text: 'Button 2', onPress: () => this.setState({textForButton: 'Button 2 clicked'})}

]

)}>

<View style={styles.button}>

<Text style={styles.buttonText}>Alert with Buttons !!</Text>

</View>

</TouchableHighlight>

<Text> {this.state.textForButton} </Text>

</View>

);

}

});

让我们刷新一下视图,看看图 5-12 中的变化。

A978-1-4842-1395-7_5_Fig12_HTML.jpg

图 5-12。

“Alert with Buttons !!” has been added

点击“带按钮的警报!!"如图 5-13 所示的结果。

A978-1-4842-1395-7_5_Fig13_HTML.jpg

图 5-13。

Select Button 1 or 2

在图 5-14 中,主页显示点击了哪个按钮。

A978-1-4842-1395-7_5_Fig14_HTML.jpg

图 5-14。

Button 1 was clicked

查看扩展的 NativeAlert 示例代码

现在,让我们了解一下我们在这个更新的 NativeAlert 组件中做了什么:

var NativeAlert = React.createClass({

getInitialState: function(){

return{

textForButton: 'Button text will come here'

}

},

render: function() {

return (

<View style={styles.container}>

<TouchableHighlight style={styles.wrapper}

onPress={() => AlertIOS.alert(

'Alert Title',

'Alert Message'

)}>

<View style={styles.button}>

<Text style={styles.buttonText}>Click me !!</Text>

</View>

</TouchableHighlight>

<TouchableHighlight style={styles.wrapper}

onPress={() => AlertIOS.alert(

'Alert Title',

'Alert Message',

[

{text: 'Button 1', onPress: () => this.setState({textForButton: 'Button 1 clicked'})},

{text: 'Button 2', onPress: () => this.setState({textForButton: 'Button 2 clicked'})}

]

)}>

<View style={styles.button}>

<Text style={styles.buttonText}>Alert with Buttons !!</Text>

</View>

</TouchableHighlight>

<Text> {this.state.textForButton} </Text>

</View>

);

}

});

这里,我们用状态 textForButton 设置了 getInitialState 方法,稍后我们将使用我们单击的按钮更新它。轻敲“带按钮的警报!!"button 触发一个 onPress 回调,该回调使用 AlertIOS 的 alert 方法为我们的警告框设置标题、消息和按钮。在 NativeAlert 组件的这一部分中,我们有两个按钮,它们的“textForButton”状态在执行 onPress 回调时用所需的文本更新。

网络视图

在本节中,我们将使用 React Native 创建一个 shell 来加载 WebView 中的任何 URL。让我们从生成应用结构开始:

$ react-native init WebviewShell

接下来,打开 index.ios.js 文件,将其代码替换为以下内容:

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

WebView

} = React;

var WebviewShell = React.createClass({

render: function() {

return (

<View style={styles.container}>

<WebView url={'https://www.facebook.com/

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1

}

});

AppRegistry.registerComponent('WebviewShell', () => WebviewShell);

让我们用 Xcode 构建我们的 WebviewShell 应用,并在 iOS simulator 中加载,以查看如图 5-15 所示的结果。

A978-1-4842-1395-7_5_Fig15_HTML.jpg

图 5-15。

WebView with a URL

查看 WebView 代码

在本例中,我们创建了一个返回视图的组件 WebviewShell。下面的代码创建了一个视图,在 WebView 中加载了我们想要的 URL。

var WebviewShell = React.createClass({

render: function() {

return (

<View style={styles.container}>

<WebView url={'https://www.facebook.com/

</View>

);

}

});

动画片

流畅、有意义的动画对于创造令人惊叹的用户体验至关重要。在本节中,我们将使用 React 本地动画 API 来实现这一点。React Native 的动画围绕着两个互补的系统:LayoutAnimation 用于制作全局布局事务的动画,以及用于对特定属性进行更细粒度控制的动画。

动画库旨在以简单高效的方式制作各种令人惊叹的动画,并与您的应用进行交互。动画库侧重于两者之间的可配置转换,具有输入和输出之间的声明性关系,以及基于时间控制动画的简单起止方法。

让我们通过创建一个 AnimationExample React 原生项目的示例来了解这个库:

$react-native init AnimationExample

将以下代码添加到 index.ios.js 中:

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

} = React;

var AnimationExample = React.createClass({

render: function() {

return (

<View style={styles.container}>

<View

style={{

backgroundColor: '#DC143C',

flex: 1

}} />

<View

style={{

backgroundColor: '#1E90FF',

flex: 1,

}} />

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'column'

},

});

AppRegistry.registerComponent('AnimationExample', () => AnimationExample);

用 Xcode 构建项目,将其加载到 iOS 模拟器上。图 5-16 显示了两个彩盒。

A978-1-4842-1395-7_5_Fig16_HTML.jpg

图 5-16。

The project now has a red and blue box

现在让我们添加一些动画。使用以下代码更新 index.ios.js:

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

Animated

} = React;

var AnimationExample = React.createClass({

getInitialState: function() {

return {

bounceValue: new Animated.Value(0)

};

},

componentDidMount() {

this.state.bounceValue.setValue(1.5);

Animated.spring(

this.state.bounceValue,

{

toValue: 0.8,

friction: 1,

}

).start();

},

render: function() {

return (

<View style={styles.container}>

<Animated.View

style={{

backgroundColor: '#DC143C',

flex: 1,

transform: [

{scale: this.state.bounceValue},

]

}} />

<View

style={{

backgroundColor: '#1E90FF',

flex: 1,

}} />

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'column'

},

});

AppRegistry.registerComponent('AnimationExample', () => AnimationExample);

我们来刷新一下看看有什么变化(图 5-17 )。

A978-1-4842-1395-7_5_Fig17_HTML.jpg

图 5-17。

The red box in the upper half now has a bounce effect in it

很好,现在让我们在下半部分添加一些动画。使用以下代码更新 index.ios.js:

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

Animated

} = React;

var AnimationExample = React.createClass({

getInitialState: function() {

return {

bounceValue: new Animated.Value(0),

fadeAnim: new Animated.Value(0)

};

},

componentDidMount() {

this.state.bounceValue.setValue(1.5);

Animated.spring(

this.state.bounceValue,

{

toValue: 0.8,

friction: 1,

}

).start();

Animated.timing(

this.state.fadeAnim,

{

toValue: 1,

duration: 2000,

},

).start();

},

render: function() {

return (

<View style={styles.container}>

<Animated.View

style={{

backgroundColor: '#DC143C',

flex: 1,

transform: [

{scale: this.state.bounceValue},

]

}} />

<Animated.View

style={{

backgroundColor: '#1E90FF',

flex: 1,

opacity: this.state.fadeAnim,

}} />

</View>

);

}

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'column'

},

});

AppRegistry.registerComponent('AnimationExample', () => AnimationExample);

图 5-18 显示了这些变化。

A978-1-4842-1395-7_5_Fig18_HTML.jpg

图 5-18。

Now there is a bounce in the upper half of the screen and fading animation in the bottom half

现在让我们进入代码,看看我们在这里做了什么。

查看动画代码

在这个例子的第一部分,我们已经创建了 AnimationExample 组件,它有一个分割,有两个部分,我们将在后面的动画中使用。我们还没有使用任何默认的 React 本地组件。

接下来,我们添加了在我们的示例中使用的必要组件。

'use strict';

var React = require('react-native');

var {

AppRegistry,

StyleSheet,

Text,

View,

Animated

} = React;

在这里,我们添加了一个动画 React 本地默认组件,这对于我们的示例的各个片段中的动画是必需的。现在,让我们了解如何用两个不同的动画来塑造我们的 AnimationExample 组件。

var AnimationExample = React.createClass({

getInitialState: function() {

return {

bounceValue: new Animated.Value(0),

fadeAnim: new Animated.Value(0)

};

},

通过 getInitialState,我们为 bounceValue 和 fadeAnim 设置了一个初始状态,bounce value 将用于上半部分的反弹效果,fade anim 将为下半部分添加渐变动画。最初两者都设置为 0。

componentDidMount() {

this.state.bounceValue.setValue(1.5);

Animated.spring(

this.state.bounceValue,

{

toValue: 0.8,

friction: 1,

}

).start();

Animated.timing(

this.state.fadeAnim,

{

toValue: 1,

duration: 2000,

},

).start();

},

使用 componentDidMount,我们将 bounceValue 的值设置为具有弹簧效果。我们还为 fadeAnim 设置了一个值,这样它的淡入淡出效果就有了一定的定时。两者都在应用加载时立即启动。

render: function() {

return (

<View style={styles.container}>

<Animated.View

style={{

backgroundColor: '#DC143C',

flex: 1,

transform: [

{scale: this.state.bounceValue},

]

}} />

<Animated.View

style={{

backgroundColor: '#1E90FF',

flex: 1,

opacity: this.state.fadeAnim,

}} />

</View>

);

}

});

接下来,我们在应用的特定部分渲染我们的动画。在第一个容器中,我们设置反弹效果,在第二个容器中,我们有渐隐动画。

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'column'

},

});

AppRegistry.registerComponent('AnimationExample', () => AnimationExample);

最后,我们有了更多的灵活样式,并注册了我们的 AnimationExample 组件。

如前所述,React Native 的动画有两个系统动画和布局动画。我们已经详细介绍了动画,作为一个练习,用同样的例子来探索布局动画。

摘要

在本章中,我们了解了 iOS 设备的功能,不仅仅是用户界面。我们了解了如何为您的应用使用地理定位和加载地图,如何使用 AsyncStorage 来保存数据,如何使用原生警报来共享应用中的重要信息,如何使用 WebView 来加载 HTML5 内容,以及如何使用动画。

在下一章,我们将学习如何与后端服务器交互,因为可能没有一个真实世界的应用是不完整的,除非连接到后端。

六、与服务器通信

“交流是每个人解决一切问题的灵丹妙药。”—汤姆·彼得斯

在通过多个示例了解了设备功能的强大后,是时候回到您的住房应用了。到目前为止,您已经在本地用一些虚拟数据填充了您的应用,但是没有一个应用可以在不与服务器通信的情况下存活。在本章中,您将学习如何与网络 API 交互。您将探索以下主题:

  • XMLHttpRequest
  • WebSocket
  • 取得
  • 从服务器获取数据
  • 向服务器发布数据

之前,您从一个虚拟数据对象获取所有数据,该对象在您的应用中是静态的。任何生产应用完全使用静态数据的机会都很少。令人高兴的是,React Native 提供了许多与网络 API 交互的方式。以下部分介绍了 React Native 中支持网络栈的方式。

XMLHttpRequest

XMLHttpRequestis 是一个 API,它提供了在客户机和服务器之间传输数据的能力。它提供了一种从 URL 中检索数据的简单方法,而不必刷新整个页面。在 React Native 中,XMLHttpRequest API 应用于 iOS 网络 API 之上。与 Web 的一个显著区别是安全模型;你可以从互联网上的任何网站上阅读,因为没有 CORS 的概念。

var xhttp= new XMLHttpRequest();

xhttp.onreadystatechange = (e) => {

if (xhttp.readyState !== 4) {

return;

}

if (xhttp.status === 200) {

console.log('success', xhttp.responseText);

} else {

console.warn('error');

}

};

xhttp.open('GET', '``https://example.com/myendpoint.php

request.send();

使用 XMLHttpRequest 相当繁琐。但是由于它与浏览器 API 兼容,它允许您直接从 npm 使用第三方库,如 Parse。有关该 API 的更多信息,请参考其在 https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest 的文档。

WebSocket

WebSocket 是一种通过单一 TCP 连接提供全双工通信通道的协议。

var ws = new WebSocket('ws://example.com/path');

ws.on('open', function() {

// connection opened

ws.send('example data');

});

ws.on('message', function(e) {

// a message received

console.log(e.data);

});

ws.on('error', function(e) {

// an error occurred

console.log(e.message);

});

ws.on('close', function(e) {

// connection closed

console.log(e.code, e.reason);

});

取得

Fetch 是一个流行的网络 API。它是由一个标准委员会创建的,具有定义明确的请求、响应和绑定它们的过程。以下是一个带有fetch的 post 请求示例:

fetch('``https://example.com/endpoint/

method: 'POST',

headers: {

'Accept': 'application/json',

'Content-Type': 'application/json',

},

body: JSON.stringify({

firstParam: 'yourValue',

secondParam: 'otherValue',

})

})

Fetch 返回一个承诺,使用thencatch进行处理。

fetch('https:// example.com/endpoint')

.then((response) => response.text())

.then((responseText) => {

console.log(responseText);

})

.catch((error) => {

console.warn(error);

});

既然您已经知道了如何与网络 API 进行交互,那么让我们使用选项之一fetch来获取数据并将数据发送到服务器。为了保持简单,我们托管了一个带有 restful APIs 的简单后端服务器,您可以为您的应用使用这些 API。

我们将使用以下网址获取和发布数据到后端服务器。您可以使用curl来查看向这些 URL 发出请求后得到的响应。

要获取属性的初始种子列表:

$curl 'http://www.akshatpaul.com/list-all-properties

[

{

name: "Mr. Johns Conch house",

address: "12th Street, Neverland",

images: {

thumbnail: "http://hmp.me/ol5

}

},

{

name: "Mr. Pauls Mansion",

address: "625, Sec-5, Ingsoc",

images: {

thumbnail: "http://hmp.me/ol6

}

},

{

name: "Mr. Nalwayas Villa",

address: "11, Heights, Oceania",

images: {

thumbnail: "http://hmp.me/ol7

}

},

{

name: "Mr. Johns Conch house",

address: "12th Street, Neverland",

images: {

thumbnail: "http://hmp.me/ol5

}

},

{

name: "Mr. Pauls Mansion",

address: "625, Sec-5, Ingsoc",

images: {

thumbnail: "http://hmp.me/ol6

}

},

{

name: "Mr. Nalwayas Villa",

address: "11, Heights, Oceania",

images: {

thumbnail: "http://hmp.me/ol7

}

},

{

name: "Mr. Johns Conch house",

address: "12th Street, Neverland",

images: {

thumbnail: "http://hmp.me/ol5

}

},

{

name: "Mr. Pauls Mansion",

address: "625, Sec-5, Ingsoc",

images: {

thumbnail: "http://hmp.me/ol6

}

},

{

name: "Mr. Nalwayas Villa",

address: "11, Heights, Oceania",

images: {

thumbnail: "http://hmp.me/ol7

}

}

]

要获取用户已保存的属性列表:

$curl 'http://www.akshatpaul.com/list-properties

要将数据发送到服务器以保存属性,请执行以下操作:

url: 'http://www.akshatpaul.com/properties

从服务器获取数据

首先,您需要来自我们服务器的一些固定数据来填充属性列表。将以下代码插入到ListProperty.js组件中:

var React = require('react-native');

var {

Image,

StyleSheet,

Text,

View,

ListView,

AlertIOS

} = React;

var REQUEST_URL = 'http://www.akshatpaul.com/list-all-properties

var ListProperty = React.createClass({

getInitialState: function() {

var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});

return {

dataSource: ds,

loaded: false

};

},

componentDidMount: function() {

this.fetchData();

},

fetchData: function() {

fetch(REQUEST_URL)

.then((response) => response.json())

.then((responseData) => {

console.log(responseData);

this.setState({

house: responseData,

dataSource: this.state.dataSource.cloneWithRows(responseData),

loaded: true

});

})

.catch((error) => {

AlertIOS.alert('No Donut for you');

loaded: true

})

.done();

},

render: function() {

if (!this.state.loaded) {

return this.renderLoadingView();

}

return (

<ListView

dataSource={this.state.dataSource}

renderRow={this.renderProperty}

style={styles.listView}/>

);

},

renderProperty: function(property) {

return (

<View style={styles.container}>

<Image

source={{uri: property.images.thumbnail}}

style={styles.thumbnail}/>

<View style={styles.rightContainer}>

<Text style={styles.name}>{property.name}</Text>

<Text style={styles.address}>{property.address}</Text>

</View>

</View>

);

},

renderLoadingView: function() {

return (

<View style={styles.container}>

<Text>

Loading houses…

</Text>

</View>

);

},

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'row',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#F5FCFF',

},

thumbnail: {

width: 63,

height: 91,

},

rightContainer: {

flex: 1,

},

name: {

fontSize: 20,

marginBottom: 8,

textAlign: 'center',

},

address: {

textAlign: 'center',

},

listView: {

paddingTop: 70,

backgroundColor: '#F5FCFF',

},

});

module.exports = ListProperty;

现在,构建或刷新应用。图 6-1 显示了它在 iOS 模拟器上的加载情况。

A978-1-4842-1395-7_6_Fig1_HTML.jpg

图 6-1。

Populating the app with static data fetched from a server

这里使用请求 URL 不是从客户机而是从服务器获取初始种子数据。在componentDidMount方法中,您使用fetch发出请求:

fetchData: function() {

fetch(REQUEST_URL)

.then((response) => response.json())

.then((responseData) => {

console.log(responseData);

this.setState({

house: responseData,

dataSource: this.state.dataSource.cloneWithRows(responseData),

loaded: true

});

})

.catch((error) => {

AlertIOS.alert('No Donut for you');

loaded: true

})

.done();

}

这将返回一个使用thencatch处理的承诺。

将数据保存到服务器

在您的住房应用中,您可以选择向后端应用添加新的属性。您在前面使用了这个特性来学习如何向后端 API 提交数据。将以下代码添加到您的AddProperty.js组件中:

var React = require('react-native');

var {

Image,

StyleSheet,

Text,

View,

ListView,

AlertIOS,

TextInput,

TouchableHighlight

} = React;

var AddProperty = React.createClass({

getInitialState: function() {

return {

name: "",

address: ""

};

},

_onPressButtonPOST: function() {

fetch("http://www.akshatpaul.com/properties

.then((responseData) => {

AlertIOS.alert(

"Created"

)

})

.done();

},

render: function() {

return (

<View style={styles.container}>

<TextInput style={styles.textBox} placeholder='name' onChangeText={(name) => this.setState({name})} value={this.state.name} />

<TextInput style={styles.textBox} placeholder='address' onChangeText={(address) => this.setState({address})} value={this.state.address} />

<TouchableHighlight style={styles.button}

onPress= {this._onPressButtonPOST}

underlayColor='#99d9f4'>

<Text style={styles.buttonText}>Add House</Text>

</TouchableHighlight>

</View>

);

},

});

var styles = StyleSheet.create({

container: {

flex: 1,

flexDirection: 'column',

justifyContent: 'center',

alignItems: 'center',

backgroundColor: '#F5FCFF',

},

textBox: {

width:300,

height:60,

borderColor: 'gray',

borderWidth: 1,

alignSelf: 'center',

marginTop: 10,

},

button: {

height: 60,

backgroundColor: '#48BBEC',

borderColor: '#48BBEC',

borderWidth: 1,

borderRadius: 8,

alignSelf: 'stretch',

justifyContent: 'center',

margin: 10

},

buttonText: {

fontSize: 18,

color: 'white',

alignSelf: 'center'

}

});

module.exports = AddProperty;

刷新以测试此功能。结果如图 6-2 所示。

A978-1-4842-1395-7_6_Fig2_HTML.jpg

图 6-2。

Form to submit record

接下来,添加一些值并将其发布到我们的服务器(图 6-3 )。

A978-1-4842-1395-7_6_Fig3_HTML.jpg

图 6-3。

Values are added to the server.

当保存这些数据时,你会得到一个警告框作为确认,如图 6-4 所示。

A978-1-4842-1395-7_6_Fig4_HTML.jpg

图 6-4。

Confirmation of successful submission

让我们用一个可以获取用户添加的属性列表的 URL 来更新ListProperty.js组件。更新ListProperties.js中的以下变量:

var REQUEST_URL = 'http://www.akshatpaul.com/list-properties

如果您curl这个 URL,您将获得以下用户添加属性的 JSON:

$curl 'http://www.akshatpaul.com/list-properties

[

{

name: "Mr. Paul’s Mansion",

address: "11, Golden View, San Francisco",

images: {

thumbnail: "http://hmp.me/ol7

}

}

]

Note

这个 API 显示了这本书的不同读者提交的数据。您的数据集可能有所不同。

刷新应用并转到“属性列表”部分(图 6-5 )。

A978-1-4842-1395-7_6_Fig5_HTML.jpg

图 6-5。

Output showing Mr. Paul’s Mansion address

现在为帖子请求添加一个 URL:

var REQUEST_URL = 'http://www.akshatpaul.com/properties

接下来,修改onPressButtonPOST功能:

_onPressButtonPOST: function() {

fetch(REQUEST_URL, {method: "POST", body: JSON.stringify({property: {name: this.state.name, address: this.state.address}})})

.then((responseData) => {

AlertIOS.alert(

"Created"

)

})

.done();

}

在这里,您使用fetch发出一个 post 请求并序列化您的 JSON,JSON 返回一个承诺,并在成功时发出一个带有“Created”消息的警告框。

摘要

在本章中,您学习了 React 本地团队从头开始重新实现的各种网络 API。您还学习了各种选项,如 XMLHttpRequest、WebSocket 和 Fetch。由于不进行服务器调用,任何应用都是不完整的,所以您将这一功能添加到了住房应用中,并学习了如何从服务器获取数据、添加新记录以及将它们保存到服务器中。

在本书的最后一章,您将了解可以与 React Native 一起使用的流行节点包,特别是令人敬畏的 React 库,如 Reflux 和 Redux,它们使开发变得稍微简单和更快。

七、React 原生补充

保持灵活性的一个好方法是少写代码—实用程序员

到目前为止,您应该已经习惯于使用 React Native 构建应用,并且已经习惯了 React Native 框架的所有基本功能和许多高级功能。在这最后一章,我们将学习一些补充,这可能不是必要的,但在某些情况下非常有用,并大大减少我们的工作。在本章中,我们将讨论以下主题:

  • 逆流
  • Redux
  • 在设备上调试
  • 流行的节点模块
  • 从这里去哪里

逆流

我们在第四章中学习了 Flux 架构。我们有一些其他方法来实现单向数据流,包括一种称为回流的精益流量方法。这是一个简单的单向数据流库,灵感来自 React 的 Flux。如图 7-1 所示,回流模式有动作和存储。动作启动新数据,新数据通过数据存储传递,然后传递给视图组件,再传递回动作。如果视图组件有一个对数据存储进行更改的事件,它需要通过可用的操作向存储发送一个信号。

A978-1-4842-1395-7_7_Fig1_HTML.jpg

图 7-1。

Reflux unidirectional flow

与 Flux 的差异

回流实际上已经重构了流量,使其更接近功能 React 式编程(FRP)。以下是回流和 Flux 之间的一些差异:

  • 单例调度器不存在,让每个动作都像调度器一样工作。
  • 因为操作是 listenable 的,所以存储会监听它们。存储不需要用大的switch语句对字符串进行静态类型检查(动作类型)。
  • 门店可以听其他门店的;可以创建可以进一步聚合数据的存储,类似于 map/reduce。
  • 不需要动作创建者,因为回流动作是将它们接收的有效载荷传递给任何收听它们的人的功能。
  • waitFor被替换为处理串行和并行数据流。聚合数据存储可以串行监听其他存储,也可以并行监听用于连接监听器的连接。

为了更好地理解回流,让我们创建一个简单的 ToDo 应用,就像我们在第四章中所做的那样。让我们生成一个 React 本机应用:

$ react-native init RefluxTodo

我们的基本项目结构现在已经准备好了。让我们在项目中添加一个回流节点模块:

$ cd RefluxTodo

$ npm install reflux --save-dev

现在我们的项目中已经添加了回流,让我们创建一个可以使用回流的结构。首先,创建一个名为Apps的根文件夹,其中包含子文件夹ActionsComponentsStores,这些子文件夹包含图 7-2 所示的文件。

A978-1-4842-1395-7_7_Fig2_HTML.jpg

图 7-2。

Root folder Apps with subfolders and files

与 Flux 模式不同,在 Reflux 中我们只有动作和存储,因为我们完全放弃了 dispatcher。我们将在我们的Component文件夹中保存各种组件,以保持我们的index.ios.js文件精简。这些文件现在是空的,所以让我们通过下面的步骤在每个文件中添加所需的代码。

Start with Actions. Open Apps/Actions/TodoActions.js and add the following code: 'use strict'; var Reflux = require('reflux'); module.exports = Reflux.createActions([   'todoCreate',   'todoUpdate',   'todoComplete',   'todoUndoComplete',   'todoDestroy', ]);   Add the following code to Stores, which resides in Apps/Stores/Todostore.js: 'use strict'; var Reflux = require('reflux'); var _ = require("underscore"); module.exports = Reflux.createStore({   listenables: [require('../Actions/TodoActions')],   todos: {},   onTodoCreate: function(text) {     var id = (+new Date() + Math.floor(Math.random() * 999999)).toString(36);     this.todos[id] = {       id: id,       complete: false,       text: text,     };     this.trigger(null);   },   onTodoUpdate: function(id, updates) {     this.todos[id] = _.extend({}, this.todos[id], updates);     this.trigger(null);   },   onTodoComplete: function(id) {     this.onTodoUpdate(id, {complete: true});   },   onTodoUndoComplete: function(id) {     this.onTodoUpdate(id, {complete: false});   },   onTodoDestroy: function(id) {     delete this.todos[id];     this.trigger(null);   },   getAll: function() {     return _.values(this.todos);   }, });   Create the components that will interact with Actions, which in turn interacts with Stores. Let’s add the following code to Apps/Components/TodoForm.js: TodoForm.js 'use strict'; var React = require('react-native'); var {   StyleSheet,   TextInput,   View, } = React; var TodoActions = require('../Actions/TodoActions'); var TodoStore = require('../Stores/TodoStore'); module.exports = React.createClass({   propTypes: {     value: React.PropTypes.string,   },   getInitialState: function() {     return { value: '',     };   },   render: function() {     return (       <View style={styles.header}>         <TextInput           style={styles.textInput}           onChangeText={(text) => this.setState({value: text})}           onBlur={this._save}           placeholder={'What needs to be done?'}           value={this.state.value}         />       </View>     );   },   _save: function() {     var text = this.state.value;     if (text) {       TodoActions.todoCreate(text); this.setState({ value: ''       });     }   }, }); var styles = StyleSheet.create({   header: {     marginTop: 21,   },   textInput: {     height: 40,     backgroundColor: '#EEEEEE',     padding: 10,     fontSize: 16   }, });   Add the following code to the TodoItem component in Apps/Components/TodoItem.js: TodoItem.js 'use strict'; var React = require('react-native'); var {   StyleSheet,   Text,   View, } = React; var Reflux = require('reflux'); var TodoActions = require('../Actions/TodoActions'); module.exports = React.createClass({   render: function() {     var todo = this.props.todo;     var styleTodoItemComplete = (todo.complete) ? styles.todoItemComplete : null;     return (       <View>         <View style={[styles.todoItem, styleTodoItemComplete]}>           <Text style={styles.text}>{todo.text}</Text>           <Text style={styles.text}>{todo.complete}</Text>           <Text onPress={() => this._onToggleComplete(todo)}>|Mark as done|</Text>           <Text onPress={() => this._onDestroy(todo)}> Delete|</Text>         </View>         <View style={styles.separator} />       </View>     );   },   _onToggleComplete: function(todo) {     if (todo.complete) {       TodoActions.todoUndoComplete(todo.id);     } else {       TodoActions.todoComplete(todo.id);     }   },   _onDestroy: function(todo) {     TodoActions.todoDestroy(todo.id);   } }); var styles = StyleSheet.create({ todoItem: {     flex: 1,     flexDirection: 'row',     justifyContent: 'center', alignItems: 'center',     backgroundColor: '#FFFFFF',     padding: 10,     height: 58,   },   todoItemComplete: {     opacity: 0.3,   },   text: {     flex: 1,     textAlign: 'left',     fontSize: 16,   },   separator: {     height: 1,     backgroundColor: '#CCCCCC',   }, });   Add the following code to TodoList.js, which is found in Apps/Components/TodoList.js: Todolist.js 'use strict'; var React = require('react-native'); var {   ListView,   StyleSheet, } = React; var Reflux = require('reflux'); var TodoStore = require('../Stores/TodoStore'); var TodoItem = require('./TodoItem'); module.exports = React.createClass({   mixins: [Reflux.listenTo(TodoStore, 'handlerTodoUpdate')],   getInitialState: function() {     var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});     return {       todoDataSource: ds.cloneWithRows(TodoStore.getAll()),     };   },   handlerTodoUpdate: function(err) {     if (err) {         return     }     this.setState({       todoDataSource: this.state.todoDataSource.cloneWithRows(TodoStore.getAll()),     });   },   render: function() {     return (       <ListView         dataSource={this.state.todoDataSource}         renderRow={(rowData) => <TodoItem todo={rowData} />}       />     );   }, }); var styles = StyleSheet.create({   list: {     flex: 1,     backgroundColor: '#0FF',   }, }); index.ios.js 'use strict'; var React = require('react-native'); var {   AppRegistry, } = React; var TodoApp = require('./Apps/Components/TodoApp'); AppRegistry.registerComponent('TodoProject', () => TodoApp);   Add the following code to TodoApp.js, found in Apps/Components/TodoApp.js: TodoApp.js 'use strict'; var React = require('react-native'); var {   StyleSheet,   View, } = React; var TodoForm = require('./TodoForm'); var TodoList= require('./TodoList'); module.exports = React.createClass({   render: function() {     return (       <View style={styles.container}>         <TodoForm />         <TodoList />       </View>     );   }, }); var styles = StyleSheet.create({   container: {     flex: 1,   }, });   In order to use the components just created, update index.ios. js with the following code: 'use strict'; var React = require('react-native'); var {   AppRegistry, } = React; var TodoApp = require('./Apps/Components/TodoApp'); AppRegistry.registerComponent('RefluxTodoProject', () => TodoApp);

完成这些步骤后,我们现在可以用 Xcode 构建我们的应用,以便运行我们的应用。一旦加载了应用,就会出现图 7-3 中的主屏幕。在这里,我们可以将任何项目添加到待办事项列表(“需要做什么?”).

A978-1-4842-1395-7_7_Fig3_HTML.jpg

图 7-3。

To do list is ready to be populated with user input

添加完成后,项目会一个接一个地列出,每行的右侧会显示“标记为完成”或“删除”选项,如图 7-4 所示。

A978-1-4842-1395-7_7_Fig4_HTML.jpg

图 7-4。

Each item in the populated list now has two options

单击“标记为完成”时,该行会变淡,表示此项目已完成。再次单击此选项将取消该行的折叠。点击“删除”从列表中完全删除项目(参见图 7-5 )。

A978-1-4842-1395-7_7_Fig5_HTML.jpg

图 7-5。

Two items have been marked as done

Redux

Reduxis 是 JavaScript 应用的可预测状态容器,它已经变得非常流行,尤其是在 React 社区中。尽管您可以在 React、AngularJS 或任何框架中使用 Redux 概念。Redux 是开源的,Dan Abramov 是它的首席开发者。Redux 通过对状态更新的方式和时间施加一定的控制,使状态突变变得可预测。

到目前为止,我们已经看到状态在 React 原生应用中被大量使用,并且状态值总是在变化。当我们的实际应用增长时,处理这些状态变化会变得不可预测。使用 React with Flux 是一种很好的方法,通过这种方法可以使用突变和同步来解决问题,因为 React 已经从视图层和 direct-DOM 操作中去除了异步。然而,管理数据的状态是留给开发人员的。Redux 使用三个原则解决了管理状态的问题:

  • 真理的单一来源
  • 状态为只读
  • 突变被写成纯函数

真理的单一来源

应用的存储存储在单个存储内的对象树中。单状态树完成了所有的状态更改。这使得创建通用应用变得容易,因为我们知道客户端应用执行的所有操作都已完成。这也使得调试更加容易,因为我们可以跟踪所有的状态变化。

状态为只读

改变状态的唯一方法是发出一个动作一个描述发生了什么的对象。因此,我们不能直接更新状态;它只能通过操作进行更新。视图和网络调用不能直接更新状态。

突变被写成纯函数

为了指定状态树是如何被动作转换的,你需要编写纯 reducers。Reducers 将采用旧的状态和动作,并返回新的状态。Reducers 是普通的 JavaScript 函数。理解 reducers 不更新状态是很重要的,它们只是返回新的状态。

在设备上调试

到目前为止,我们一直在 iOS 模拟器上调试和测试我们的应用,这在大多数时候都运行得很好,但是在现实世界的项目中,我们需要在 iOS 设备上一次又一次地加载我们的应用以进行测试和调试。

要调试您的应用 iOS 设备,只需插入您的设备(在我们的例子中是 iPad)。打开 Xcode 并选择您的设备而不是模拟器(参见图 7-6 )。

A978-1-4842-1395-7_7_Fig6_HTML.jpg

图 7-6。

Selecting an iPad as your device

我们在这个部分使用 RefluxTodo 应用,但是您可以使用到目前为止您已经构建的任何 React 本地项目。构建应用以将其加载到设备上。应用图标应该会出现,但是我们会得到如图 7-7 所示的错误。

A978-1-4842-1395-7_7_Fig7_HTML.jpg

图 7-7。

“Unable to connect” error message

这个错误的原因是我们的应用无法连接到开发服务器。让我们通过进入我们的应用代码来解决这个问题。转到AppDelegate.m文件,该文件通常位于project-name/ios/project-name/AppDelegate.m中,并更改以下行:

jsCodeLocation``= NSURL URLWithString:@"``http://localhost:8081/index.ios.bundle?platform=ios&dev=true

收件人:

jsCodeLocation = [NSURL URLWithString:@"``http://machine-ip-address:8081/index.ios.bundle?platform=ios&dev=true

这里,我们必须用我们机器的 IP 地址替换localhost。这样做可以让我们的应用在设备上正确加载和运行(参见图 [7-8 )。

A978-1-4842-1395-7_7_Fig8_HTML.jpg

图 7-8。

Application is now running properly

要访问开发者菜单,只需摇动您的设备,菜单将从视图底部出现(参见图 7-9 )。

A978-1-4842-1395-7_7_Fig9_HTML.jpg

图 7-9。

Developer menu is now visible

我们可以启用实时重新加载,就像在 iOS 模拟器中一样,所有的更改也会自动反映在设备上。点击 Chrome 中的 Debug,可以看到如图 7-10 所示的结果。

A978-1-4842-1395-7_7_Fig10_HTML.jpg

图 7-10。

WebSocket error message

然而,我们得到另一个错误。为了解决这个问题,我们必须回到我们的代码并做一些修改。为此,我们需要转到位于 React 节点模块中的RCTWebSocketExecuter.m,遍历到/your-project-name/node_module/react-native/Libraries/WebSocket/ RCTWebSocketExecuter.m,并且我们需要在下面的代码段中再次将我们的localhost更改为机器的 IP 地址:

From

- (instancetype)init

{

return [self initWithURL:[RCTConvert NSURL:@"``http://localhost:8081/debugger-proxy

}

to

- (instancetype)init

{

return [self initWithURL:[RCTConvert NSURL:@"``http://your-machine-ip-address:8081/debugger-proxy

}

有了这个改变,我们可以调试我们的应用,就像我们到目前为止使用模拟器一样。

React Native 的流行模块

React 当地社区可能还年轻,但它已经开始繁荣。像任何其他流行的框架一样,已经创建了许多开源项目,这有助于开发人员避免重复开发已经开发的功能。你可以在 GitHub 上找到上百个这样的节点模块;我们列出了一些可以帮助您快速构建和交付下一个 React 本机应用的工具。

react-native-fbsdk

React Native FBSDK 是 iOS 脸书 SDK 的包装器,有助于脸书与 React Native 应用的集成。文档化的 JavaScript 模块有助于使用三个 npm 模块访问本地组件,如登录、共享和共享。你必须包含三个 npm 包:react-native-fbsdkcorereact-native-fbsdksharereact-native-fbsdklogin—就是这样。

要获得完整的文档或对这个项目做出贡献,您可以访问以下网站: https://github.com/facebook/react-native-fbsdk

React-本机-可滚动-选项卡-视图

流畅的动画真的给应用中的任何特性增加了意义。React 本地动画 API 变得非常健壮;为选项卡内的滚动创建平滑的动画是由react-native-scrollable-tab-view npm 包解决的一个问题。这是 React Native 的一个非常简单的纯 JavaScript 实现。

要获得完整的文档或对这个项目做出贡献,您可以访问以下网站: https://github.com/brentvatne/react-native-scrollable-tab-view

react-native-webpack-server

react-native- webpack-server允许您使用 React Native 的 webpack 生态系统。它是一个开发服务器,使用 Webpack Dev Server 和 React Packager 来构建 React 本地 JavaScript 包。

要获得完整的文档或对这个项目做出贡献,您可以访问以下网站: https://github.com/mjohnston/react-native-webpack-server

React-本地-侧面-菜单

这是一个非常简单的可扩展侧边菜单的实现,这个包可以帮助你添加一个侧边菜单,用一个简单的滑动手势就可以从左边或者右边滚动。

要获得完整的文档或对这个项目做出贡献,您可以访问以下网站: https://github.com/react-native-fellowship/react-native-side-menu#component-props

以下是其他一些值得一提的地方:

  • react-native- vector-icons
  • react-native- facebook-login
  • react-native- google-places-autocomplete
  • react-native- sqlite-storage
  • react-native- tableview
  • react-native- background-geolocation

从这里去哪里

恭喜你。您已经了解了很多关于 React 原生框架的知识。但这仅仅是个开始,因为 React 本地社区和生态系统每天都在快速扩张。新的开发、创新的变化和前沿的特性一直被添加到框架中简而言之,每天都有新的东西要学。对于勇敢的人来说,这是一个拥抱的机会。所以我想到的一个明显的问题是,下一步是什么?答案是,很多。

如果你在寻找已经构建好并可供使用的 React 原生组件,可以访问react.parts,里面有所有可用可重用模块的详尽列表;https://react.parts/native见。

对于任何问题或疑问,您都可以使用带有标签react-native的 stackoverflow。对于来自开发伙伴的真正快速的响应,您可以随时在 IRC 上的#reactnative 上跳转到 IRC 频道。你也可以在 https://discordapp.com/invite/0ZcbPKXt5bWJVmUY 使用 discord channel Reactiflux 与 React 原生社区聊天(是的,React 原生社区足够酷,可以使用游戏玩家使用的聊天平台)。

React Native 是一个非常年轻的框架,在使用它时,您可能会提出一些对框架有帮助的建议,或者一些对框架来说有些多余的东西。React 本地社区非常乐于接受建议;如果你有,你可以在 https://discuss.reactjs.org/ 开始一个线程。这个讨论页面也是一个从其他人那里学习最佳实践和从旧线索中学习的好地方。此外,这是一个让专家解决您的问题的好地方。更好的是,你也可能得到一些工作机会。

最后,要成为 React Native 的大师,没有比简单地构建应用更好的方法了。没有比参与现实世界的项目、解决现实生活中的问题更快成为技术大师的方法了。由于 React 原生社区仍处于起步阶段,因此通过创建自己的模块来学习和分享是一个很好的机会,这可以帮助社区的其他人,这是一个额外的好处。我们希望你喜欢阅读和学习这本书,并且现在已经发展成为一名 React 本地开发者来构建 iOS 应用。就像你一样,我们对 React Native 感到非常兴奋,并期待看到你的作品在 iOS 和 React Native 的世界中留下印记。再见。

posted @ 2024-10-01 21:05  绝不原创的飞龙  阅读(32)  评论(0编辑  收藏  举报