ReactNative IOS 开发教程(二)
四、Flux:以不同的方式解决问题
简单是可靠的先决条件—吉克斯特拉
Flux 是脸书向世界介绍的一种应用架构,用于构建客户端应用。它通过利用单向数据流来补充 React 本机可组合视图组件。与其说它是一个合适的框架,不如说它是一种模式,人们可以立即开始使用 Flux,而不需要过多的代码。在我们深入研究细节之前,了解常用的流行模式 MVC 以及它与 Flux 的区别是很重要的。在本章中,我们将了解以下主题:
- MVC 模式
- MVC 问题
- 流量
- Flux 深潜
- 带电抗器的 Flux 示例
- React 原生的 Flux 示例
MVC 模式
历史上,MVC 模式将代码分成三个不同的部分:模型、视图和控制器。这种模式的主要目的是将信息的表示与用户交互隔离开来。让我们分别了解这些部分:
- 模型:管理应用的行为和数据
- 视图:用户界面中模型的表现层
- 控制器:接受用户输入并对模型进行必要的操作,使视图得到更新
MVC 问题
MVC 是设计应用的一种非常流行的模式,但是它也有自己的问题。你的源代码变得越复杂,事情就变得越复杂。图 4-1 显示了 MVC 最简单的实现,它在小型应用中运行良好。但是随着应用的增长,它需要新的特性,所以应该有空间来容纳更多的模型和视图。
图 4-1。
Basic MVC implementation
图 4-2 显示了当模型和视图增加时会发生什么。
图 4-2。
An explosion of arrows
有如此多的模型和视图相互交互,当我们跟踪一个模型时,它触发一个视图,视图触发另一个模型,这就像意大利面条一样继续下去,很多时候以无限循环结束。最糟糕的是,在这种情况下调试代码真的很难,最终使系统变得脆弱。脸书也面临过类似的挫折,他用一种叫做“流动”的新模式解决了这个问题。
流量
Flux 放弃 MVC,支持单向数据流。Flux 工作得很好,因为随着应用的增长和变得更加复杂,单向数据流使得理解和修改应用变得容易。之前,我们发现双向数据绑定会导致级联更新,其中一个数据模型的更改会导致另一个数据模型的更新,这使得很难预测单个用户交互的结果会是什么。如图 4-3 所示,Flux 应用有三个主要部分:dispatcher、stores 和 views(这里我们使用 React 本地组件)。这些不应该与 MVC 模式的模型视图控制器元素相比较。
图 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 架构最重要的部分是调度程序,它是一个单独的程序,指导数据流并确保更新不会级联。
图 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'])
});
这里,我们有两个任务:browserify和copy。browserify所做的是抓取、转换和连接main.js(我们的应用代码所在的地方)并把它放在我们的分发文件夹中,这个文件夹就是dist。下一个任务copy将index.html复制到dist文件夹中。
我们还有另一个任务default,它既运行这两个任务,也持续观察与这些任务相关的文件是否有任何变化。
接下来,让我们创建我们的文件夹结构,它应该支持 Flux 架构。用根文件夹中的空文件创建如图 4-5 所示的文件夹结构:
图 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 所示的结果。
图 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_COMPLETE、TODO_COMPLETE和TODO_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 所示。
图 4-7。
MainSection of the todo application to add todo tasks
我们可以在文本框中添加任何待办事项任务,该任务将立即填充到文本框下方,如图 4-8 所示。
图 4-8。
Populated the todo list
我们还可以选择将任务标记为完成或删除它。如果任务被删除,它将从列表中移除,并且当任务被标记为完成时,它将变淡,如图 4-9 所示。
图 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 中显示的结果。
图 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 所示的变化。
图 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 模拟器上。
图 5-3。
Access location prompt
如果我们允许这个请求,我们将飞到我们代码中提到的位置;在这种情况下,它是加利福尼亚(图 5-4 )。
图 5-4。
Fly to a specified location in the code
调试地理定位应用时还有一个额外的步骤。如前所述,在本例中,我们将飞往当前位置。我们已经保存了该需求,以便现在可以完成它。使用 Ctrl + Command + z 打开调试菜单,选择“在 Chrome 中调试”选项,如图 5-5 所示。
图 5-5。
Select debug in chrome option
一旦你点击“在 Chrome 中调试”,你将得到一个弹出窗口,允许你的位置被提供给 iOS 模拟器应用,如图 5-6 所示。
图 5-6。
Allow the present location to be shared with the iOS simulator
一旦你允许这样做,你将移动到你在应用中的当前位置(图 5-7 )。
图 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 所示的文本框中输入文本,然后点击“更新存储”
图 5-8。
Storage is updated
完成后,刷新结果,如图 5-9 所示。
图 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 显示了结果。
图 5-10。
This button will open an alert box when clicked
轻点“点击我!!"按钮看到如图 5-11 所示的警告框。
图 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 中的变化。
图 5-12。
“Alert with Buttons !!” has been added
点击“带按钮的警报!!"如图 5-13 所示的结果。
图 5-13。
Select Button 1 or 2
在图 5-14 中,主页显示点击了哪个按钮。
图 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 所示的结果。
图 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 显示了两个彩盒。
图 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 )。
图 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 显示了这些变化。
图 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 内容,以及如何使用动画。
在下一章,我们将学习如何与后端服务器交互,因为可能没有一个真实世界的应用是不完整的,除非连接到后端。