八、使用应用逻辑和数据
在本章中,我们将介绍以下配方:
- 本地存储和检索数据
- 从远程 API 检索数据
- 向远程 API 发送数据
- 与 WebSocket 建立实时通信
- 将持久数据库功能与领域集成
- 在网络连接丢失时屏蔽应用
- 使用远程 API 同步本地持久化数据
介绍
开发任何应用最重要的方面之一就是处理数据。这些数据可能来自本地用户,可能由公开 API 的远程服务器提供,或者与大多数业务应用一样,可能是两者的某种组合。您可能想知道什么策略最适合处理数据,或者甚至想知道如何完成简单的任务,例如发出 HTTP 请求。幸运的是,React Native 通过提供方便处理来自所有不同来源的数据的机制,使您的生活变得更加简单。
开源社区更进一步,提供了一些可以与 React Native 一起使用的优秀模块。在本章中,我们将讨论如何在各个方面使用数据,以及如何将数据集成到我们的 React 本机应用中。
本地存储和检索数据
当开发一个移动应用时,我们需要考虑需要克服的网络挑战。设计良好的应用应允许用户在没有互联网连接时继续使用该应用。这要求应用在没有 internet 连接时将数据本地保存在设备上,并在网络再次可用时将数据与服务器同步。
另一个需要克服的挑战是网络连接,它可能很慢或有限。为了提高应用的性能,我们应该将关键数据保存在本地设备上,以避免给服务器 API 带来压力。
在本食谱中,我们将学习一种基本而有效的策略,用于从设备本地保存和检索数据。我们将创建一个带有文本输入和两个按钮的简单应用,一个用于保存字段内容,另一个用于加载现有内容。我们将使用AsyncStorage
课程来实现我们的目标。
准备
我们需要创建一个名为local-data-storage
的空应用。
怎么做。。。
- 我们将从
App
组件开始。让我们从导入所有依赖项开始:
import React, { Component } from 'react';
import {
Alert,
AsyncStorage,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from 'react-native';
- 现在,让我们创建
App
类。我们将创建一个key
常量,以便设置用于保存内容的键的名称。在state
上,我们将有两个属性:一个用于保存文本输入组件的值,另一个用于加载和显示当前存储的值:
const key = '@MyApp:key';
export default class App extends Component {
state = {
text: '',
storedValue: '',
};
//Defined in later steps
}
- 当组件挂载时,我们希望加载现有的存储值(如果存在)。我们将在应用加载后显示内容,因此我们需要在
componentWillMount
生命周期方法中读取本地值:
componentWillMount() {
this.onLoad();
}
onLoad
功能从本地存储器加载当前内容。就像浏览器中的localStorage
一样,保存数据时使用我们定义的键也很简单:
onLoad = async () => {
try {
const storedValue = await AsyncStorage.getItem(key);
this.setState({ storedValue });
} catch (error) {
Alert.alert('Error', 'There was an error while loading the
data');
}
}
- 保存数据也很简单。我们将通过
AsyncStorage
的setItem
方法声明一个密钥,以保存我们想要与该密钥关联的任何数据:
onSave = async () => {
const { text } = this.state;
try {
await AsyncStorage.setItem(key, text);
Alert.alert('Saved', 'Successfully saved on device');
} catch (error) {
Alert.alert('Error', 'There was an error while saving the
data');
}
}
- 接下来,我们需要一个将输入文本中的值保存到
state
的函数。当输入值发生变化时,我们会得到新的值并保存到state
:
onChange = (text) => {
this.setState({ text });
}
- 我们的 UI 将很简单:只需一个
Text
元素来呈现保存的内容,一个TextInput
组件允许用户输入新值,以及两个按钮。一个按钮调用onLoad
功能加载当前保存的值,另一个按钮保存文本输入的值:
render() {
const { storedValue, text } = this.state;
return (
<View style={styles.container}>
<Text style={styles.preview}>{storedValue}</Text>
<View>
<TextInput
style={styles.input}
onChangeText={this.onChange}
value={text}
placeholder="Type something here..."
/>
<TouchableOpacity onPress={this.onSave} style=
{styles.button}>
<Text>Save locally</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.onLoad} style=
{styles.button}>
<Text>Load data</Text>
</TouchableOpacity>
</View>
</View>
);
}
- 最后,让我们添加一些样式。这将是简单的颜色、填充、边距和布局,如第 2 章中所述,创建一个简单的 React 原生应用:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
preview: {
backgroundColor: '#bdc3c7',
width: 300,
height: 80,
padding: 10,
borderRadius: 5,
color: '#333',
marginBottom: 50,
},
input: {
backgroundColor: '#ecf0f1',
borderRadius: 3,
width: 300,
height: 40,
padding: 5,
},
button: {
backgroundColor: '#f39c12',
padding: 10,
borderRadius: 3,
marginTop: 10,
},
});
- 最终的应用应类似于以下屏幕截图:
它是如何工作的。。。
AsyncStorage
类允许我们在本地设备上轻松保存数据。在 iOS 上,这是通过在文本文件上使用字典来实现的。在 Android 上,它将使用 RocksDB 或 SQLite,具体取决于可用的资源。
It's not recommended to save sensitive information using this method, as the data is not encrypted.
在步骤 4中,我们加载了当前保存的数据。AsyncStorage
API 包含getItem
方法。此方法接收我们要作为参数检索的密钥。我们在这里使用的是await
/async
语法,因为这个调用是异步的。得到值后,我们只需将其设置为state
;这样,我们将能够在视图上渲染数据。
在步骤 7中,我们保存了state
中的文本。使用setItem
方法,我们可以用我们想要的任何值设置一个新的key
。此调用是异步的,因此我们使用了await
/async
语法。
另见
一篇关于 JavaScript 中async
/await
如何工作的优秀文章,可在上找到 https://ponyfoo.com/articles/understanding-javascript-async-await 。
从远程 API 检索数据
在前面的章节中,我们使用了 JSON 文件中的数据或直接在源代码中定义的数据。虽然这对我们以前的配方有效,但在实际应用中却很少有帮助。
在这个配方中,我们将学习如何从 API 请求数据。我们将从 API 发出GET
请求以获得 JSON 响应。但是,现在我们只在文本元素中显示 JSON。我们将使用假的在线 RESTAPI 进行测试和原型制作,托管在http://jsonplaceholder.typicode.com 由优秀的开发测试 API 软件,JSON 服务器(支持 https://github.com/typicode/json-server )。
我们将保持此应用的简单性,以便我们能够专注于数据管理。我们将有一个文本组件,它将显示来自 API 的响应,并添加一个按钮,当按下时请求数据。
准备
我们需要创建一个空的应用。让我们把这个命名为remote-api
。
怎么做。。。
- 让我们首先将依赖项导入到
App.js
文件中:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View
} from 'react-native';
- 我们将在
state
上定义一个results
属性。此属性将保存来自 API 的响应。收到响应后,我们需要更新视图:
export default class App extends Component {
state = {
results: '',
};
// Defined later
}
const styles = StyleSheet.create({
// Defined later
});
- 按下按钮时,我们将发送请求。接下来,让我们创建一个方法来处理该请求:
onLoad = async () => {
this.setState({ results: 'Loading, please wait...' });
const response = await fetch('http://jsonplaceholder.typicode.com/users', {
method: 'GET',
});
const results = await response.text();
this.setState({ results });
}
- 在
render
方法中,我们将显示响应,该响应将从state
中读取。我们将使用TextInput
来显示 API 数据。通过属性,我们将声明编辑已禁用,并支持多行功能。该按钮将调用我们在上一步中创建的onLoad
函数:
render() {
const { results } = this.state;
return (
<View style={styles.container}>
<View>
<TextInput
style={styles.preview}
value={results}
placeholder="Results..."
editable={false}
multiline
/>
<TouchableOpacity onPress={this.onLoad} style=
{styles.btn}>
<Text>Load data</Text>
</TouchableOpacity>
</View>
</View>
);
}
- 最后,我们将添加一些样式。同样,这将只是布局、颜色、边距和填充:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
preview: {
backgroundColor: '#bdc3c7',
width: 300,
height: 400,
padding: 10,
borderRadius: 5,
color: '#333',
marginBottom: 50,
},
btn: {
backgroundColor: '#3498db',
padding: 10,
borderRadius: 3,
marginTop: 10,
},
});
- 最终的应用应类似于以下屏幕截图:
它是如何工作的。。。
在步骤 4中,我们向 API 发送了请求。我们使用fetch
方法进行请求。第一个参数是带有端点 URL 的字符串,而第二个参数是配置对象。对于这个请求,我们需要定义的唯一选项是GET
的request
方法,但是我们也可以使用这个对象来定义头、cookie、参数和许多其他东西。
我们还使用了async
/await
语法来等待响应,并最终将其设置为state
。如果你愿意,你当然可以用承诺来代替。
另外,请注意我们在这里是如何使用 arrow 函数来正确处理作用域的。将此方法分配给onPress
回调时,将自动设置正确的范围。
向远程 API 发送数据
在前面的配方中,我们介绍了如何使用fetch
从 API 获取数据。在这个配方中,我们将学习如何将POST
数据传输到相同的 API。此应用将模拟创建论坛帖子,帖子请求将具有title
、body
和user
参数
准备
在使用此配方之前,我们需要创建一个名为remote-api-post
的新空应用
在此配方中,我们还将使用非常流行的axios
包来处理我们的 API 请求。您可以通过带有yarn
的终端进行安装:
yarn add axios
或者,您可以使用npm
:
npm install axios --save
怎么做。。。
- 首先,我们需要打开
App.js
文件并导入我们将使用的依赖项:
import React, { Component } from 'react';
import axios from 'axios';
import {
Alert,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
SafeAreaView,
} from 'react-native';
- 我们将使用具有三个属性的
state
对象定义App
类。title
和body
属性将用于发出请求,results
将保存 API 的响应:
const endpoint = 'http://jsonplaceholder.typicode.com/posts';
export default class App extends Component {
state = {
results: '',
title: '',
body: '',
};
const styles = StyleSheet.create({
// Defined later
});
}
- 保存新帖子后,我们将从 API 请求所有帖子。我们将定义一个
onLoad
方法来获取新数据。此代码的工作原理与前面配方中的onLoad
方法相同,但这次,我们将使用axios
包创建请求:
onLoad = async () => {
this.setState({ results: 'Loading, please wait...' });
const response = await axios.get(endpoint);
const results = JSON.stringify(response);
this.setState({ results });
}
- 让我们来保存新数据。首先,我们需要从
state
中获取值。我们也可以在这里运行一些验证,以确保title
和body
不是空的。在POST
请求中,我们需要定义请求的内容类型,在本例中为 JSON。我们将把userId
属性硬编码为1
。在真实的应用中,我们可能会从以前的 API 请求中获得此值。请求完成后,我们得到 JSON 响应,如果成功,将触发我们之前定义的onLoad
方法:
onSave = async () => {
const { title, body } = this.state;
try {
const response = await axios.post(endpoint, {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
params: {
userId: 1,
title,
body
}
});
const results = JSON.stringify(response);
Alert.alert('Success', 'Post successfully saved');
this.onLoad();
} catch (error) {
Alert.alert('Error', `There was an error while saving the
post: ${error}`);
}
}
- 保存功能已完成。接下来,我们需要将
title
和body
保存到state
的方法。这些方法将在用户输入文本时执行,跟踪state
对象上的值:
onTitleChange = (title) => this.setState({ title });
onPostChange = (body) => this.setState({ body });
- 我们已经具备了功能所需的一切,所以让我们添加 UI。
render
方法将显示一个工具栏、两个输入文本和一个保存按钮,用于调用步骤 4中定义的onSave
方法:
render() {
const { results, title, body } = this.state;
return (
<SafeAreaView style={styles.container}>
<Text style={styles.toolbar}>Add a new post</Text>
<ScrollView style={styles.content}>
<TextInput
style={styles.input}
onChangeText={this.onTitleChange}
value={title}
placeholder="Title"
/>
<TextInput
style={styles.input}
onChangeText={this.onPostChange}
value={body}
placeholder="Post body..."
/>
<TouchableOpacity onPress={this.onSave} style=
{styles.button}>
<Text>Save</Text>
</TouchableOpacity>
<TextInput
style={styles.preview}
value={results}
placeholder="Results..."
editable={false}
multiline
/>
</ScrollView>
</SafeAreaView>
);
}
- 最后,让我们添加样式以定义布局、颜色、填充和边距:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
toolbar: {
backgroundColor: '#3498db',
color: '#fff',
textAlign: 'center',
padding: 25,
fontSize: 20,
},
content: {
flex: 1,
padding: 10,
},
preview: {
backgroundColor: '#bdc3c7',
flex: 1,
height: 500,
},
input: {
backgroundColor: '#ecf0f1',
borderRadius: 3,
height: 40,
padding: 5,
marginBottom: 10,
flex: 1,
},
button: {
backgroundColor: '#3498db',
padding: 10,
borderRadius: 3,
marginBottom: 30,
},
});
- 最终的应用应类似于以下屏幕截图:
它是如何工作的。。。
在步骤 2中,我们在state
上定义了三个属性。results
属性将包含来自服务器 API 的响应,我们稍后将使用该响应在 UI 中显示值。
我们使用title
和body
属性保存来自输入文本组件的值,以便用户可以创建新的帖子。当按下保存按钮时,这些值将被发送到 API。
在步骤 6中,我们在 UI 上声明了元素。我们使用了两个 post 数据输入和 Save 按钮,按下该按钮时调用onSave
方法。最后,我们使用输入文本来显示结果。
与 WebSocket 建立实时通信
在此配方中,我们将在 React 本机应用中集成 WebSocket。我们将使用 WebSockets 应用的Hello World,即一个简单的聊天应用。此应用将允许用户发送和接收消息。
准备
为了在 React Native 上支持 WebSocket,我们需要运行一个服务器来处理所有连接的客户端。当服务器从任何连接的客户端接收到消息时,它应该能够广播消息。
我们将从一个新的、空的本地应用开始。我们将其命名为web-sockets
,在项目的根目录中,我们添加一个server
文件夹,其中包含一个index.js
文件。如果还没有,则需要节点来运行服务器。您可以从获取 Node.jshttps://nodejs.org/ 或使用节点版本管理器(https://github.com/creationix/nvm )。
我们将使用优秀的 WebSocket 软件包ws
。您可以通过yarn
终端添加套餐:
yarn add ws
或者,您可以使用npm
:
npm install --save ws
安装包后,将以下代码添加到/server/index.js
文件中。一旦此服务器运行,它将通过server.on('connection')
监听传入连接,并通过socket.on('message')
监听传入消息。有关ws
如何工作的更多信息,您可以在查阅文档 https://github.com/websockets/ws :
const port = 3001;
const WebSocketServer = require('ws').Server;
const server = new WebSocketServer({ port });
server.on('connection', (socket) => {
socket.on('message', (message) => {
console.log('received: %s', message);
server.clients.forEach(client => {
if (client !== socket) {
client.send(message);
}
});
});
});
console.log(`Web Socket Server running on port ${port}`);
服务器代码就位后,可以通过在项目根目录下的终端中运行以下命令,使用节点启动服务器:
node server/index.js
让服务器保持运行,这样,一旦我们构建了 React 本机应用,我们就可以使用服务器在客户端之间进行通信。
怎么做。。。
- 首先,让我们创建
App.js
文件并导入我们将使用的所有依赖项:
import React, { Component } from 'react';
import {
Dimensions,
ScrollView,
StyleSheet,
Text,
TextInput,
SafeAreaView,
View,
Platform
} from 'react-native';
- 在
state
对象上,我们将声明一个history
属性。此属性将是一个数组,用于保存用户之间来回发送的所有消息:
export default class App extends Component {
state = {
history: [],
};
// Defined in later steps
}
const styles = StyleSheet.create({
// Defined in later steps
});
- 现在,我们需要将 WebSocket 集成到我们的应用中,方法是连接到服务器并设置回调函数以接收消息、错误以及连接何时打开或关闭。我们将在创建组件后使用
componentWillMount
生命周期挂钩执行此操作:
componentWillMount() {
const localhost = Platform.OS === 'android' ? '10.0.3.2' :
'localhost';
this.ws = new WebSocket(`ws://${localhost}:3001`);
this.ws.onopen = this.onOpenConnection;
this.ws.onmessage = this.onMessageReceived;
this.ws.onerror = this.onError;
this.ws.onclose = this.onCloseConnection;
}
- 让我们为打开/关闭的连接和处理收到的错误定义回调。我们只是要记录这些操作,但在这里,我们可以在连接关闭时显示警报消息,或者在服务器抛出错误时显示错误消息:
onOpenConnection = () => {
console.log('Open!');
}
onError = (event) => {
console.log('onerror', event.message);
}
onCloseConnection = (event) => {
console.log('onclose', event.code, event.reason);
}
- 当接收到来自服务器的新消息时,我们需要将其添加到
state
上的history
属性中,以便在新内容到达后立即呈现:
onMessageReceived = (event) => {
this.setState({
history: [
...this.state.history,
{ isSentByMe: false, messageText: event.data },
],
});
}
- 现在,继续发送消息。我们需要定义一个方法,当用户按下键盘上的返回键时,该方法将被执行。此时我们需要做两件事:将新消息添加到
history
,然后通过套接字发送消息:
onSendMessage = () => {
const { text } = this.state;
this.setState({
text: '',
history: [
...this.state.history,
{ isSentByMe: true, messageText: text },
],
});
this.ws.send(text);
}
- 在上一步中,我们从
state
中获得了text
属性。每当用户在输入中键入内容时,我们都需要跟踪值,因此我们需要一个函数来监听击键并将值保存到state
:
onChangeText = (text) => {
this.setState({ text });
}
- 我们已经准备好了所有的功能,所以让我们来处理 UI。在
render
方法中,我们将添加工具栏、滚动视图以呈现history
中的所有消息,以及文本输入以允许用户发送新消息:
render() {
const { history, text } = this.state;
return (
<SafeAreaView style={[styles.container, android]}>
<Text style={styles.toolbar}>Simple Chat</Text>
<ScrollView style={styles.content}>
{ history.map(this.renderMessage) }
</ScrollView>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={text}
onChangeText={this.onChangeText}
onSubmitEditing={this.onSendMessage}
/>
</View>
</SafeAreaView>
);
}
- 为了呈现来自
history
的消息,我们将通过history
数组循环,并通过renderMessage
方法呈现每条消息。我们需要检查当前消息是否属于此设备上的用户,以便应用适当的样式:
renderMessage(item, index){
const sender = item.isSentByMe ? styles.me : styles.friend;
return (
<View style={[styles.msg, sender]} key={index}>
<Text>{item.msg}</Text>
</View>
);
}
- 最后,让我们来研究一下样式!让我们向工具栏、
history
组件和文本输入添加样式。我们需要将history
容器设置为灵活,因为我们希望它占据所有可用的垂直空间:
const styles = StyleSheet.create({
container: {
backgroundColor: '#ecf0f1',
flex: 1,
},
toolbar: {
backgroundColor: '#34495e',
color: '#fff',
fontSize: 20,
padding: 25,
textAlign: 'center',
},
content: {
flex: 1,
},
inputContainer: {
backgroundColor: '#bdc3c7',
padding: 5,
},
input: {
height: 40,
backgroundColor: '#fff',
},
// Defined in next step
});
- 现在,我们来看看每条消息的样式。我们将为所有消息创建一个名为
msg
的通用样式对象,然后为设备上用户发送的消息创建样式,最后为其他用户发送的消息创建样式,并相应地更改颜色和对齐方式:
msg: {
margin: 5,
padding: 10,
borderRadius: 10,
},
me: {
alignSelf: 'flex-start',
backgroundColor: '#1abc9c',
marginRight: 100,
},
friend: {
alignSelf: 'flex-end',
backgroundColor: '#fff',
marginLeft: 100,
}
- 最终的应用应类似于以下屏幕截图:
它是如何工作的。。。
在步骤 2中,我们使用history
数组声明了state
对象,用于跟踪消息。history
属性将保存表示客户端之间交换的所有消息的对象。每个对象都有两个属性:一个包含消息文本的字符串和一个用于确定发送者的布尔标志。我们可以在这里添加更多数据,例如用户的姓名、化身图像的 URL 或我们可能需要的任何其他信息。
在步骤 3中,我们连接到 WebSocket 服务器提供的套接字,并设置回调以处理套接字事件。我们指定了服务器地址和端口。
在步骤 5中,我们定义了从服务器接收新消息时执行的回调。在这里,每次收到新消息时,我们都会在state
上的history
数组中添加一个新对象。每个消息对象都有属性isSentByMe
和
messageText
。
在步骤 6中,我们将消息发送到服务器。我们需要将该消息添加到历史记录中,因为服务器将向所有其他客户端广播该消息,而不是消息的作者。要跟踪此消息,我们需要手动将其添加到历史记录中。
将持久数据库功能与领域集成
随着应用变得越来越复杂,您可能需要在设备上存储数据。这可以是业务数据,例如用户列表,以避免与远程 API 建立昂贵的网络连接。也许您根本没有 API,您的应用作为一个自给自足的实体工作。无论在何种情况下,利用数据库存储数据都可能会使您受益。React 本机应用有多个选项。第一个选项是AsyncStorage
,我们在本章的本地存储和检索数据配方中介绍了该选项。您也可以考虑 SQLite,或者您可以向 OS 特定的数据提供者编写适配器,例如核心数据。
另一个很好的选择是使用移动数据库,比如 Realm。Realm 是一个非常快速、线程安全、事务性、基于对象的数据库。它主要是为移动设备设计的,带有简单的 JavaScript API。它支持其他功能,如加密、复杂查询、UI 绑定等。您可以在上阅读相关内容 https://realm.io/products/realm-mobile-database/ 。
在本食谱中,我们将在 React Native 中使用领域。我们将创建一个简单的数据库并执行基本操作,例如插入、更新和删除记录。然后,我们将在 UI 中显示这些记录。
准备
让我们创建一个名为realm-db
的新空本地应用。
安装 Realm 需要运行以下命令:
react-native link
因此,我们将开发一款从世博会中退出的应用。这意味着您可以使用以下命令创建此应用:
react-native init
或者,您可以使用以下命令创建新的 Expo 应用:
expo init
然后,您可以通过以下命令弹出使用 Expo 创建的应用:
expo eject
创建 React 本机应用后,请确保在新应用中使用cd
并运行以下程序,通过ios
目录安装 CocoaPods 依赖项:
pod install
请参阅第 10 章、应用工作流和第三方插件,以深入了解 CocoaPods 的工作原理,以及弹出(或纯 React-Native)应用与 Expo-React-Native 应用的区别。
在向远程 API发送数据的过程中,我们使用axios
包处理 AJAX 调用。在此配方中,我们将使用本机 JavaScriptfetch
方法进行 AJAX 调用。这两种方法都能很好地发挥作用,并且两者都有可能让你决定你的项目更喜欢哪种方法。
完成创建弹出应用后,使用yarn
安装 Realm:
yarn add realm
或者,您可以使用npm
:
npm install --save realm
安装包后,您可以使用以下代码链接本机包:
react-native link realm
怎么做。。。
- 首先,让我们打开
App.js
并导入我们将使用的依赖项:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity
} from 'react-native';
import Realm from 'realm';
- 接下来,我们需要实例化领域数据库,我们将在
componentWillMount
方法中执行此操作。我们将使用realm
类变量保留对它的引用:
export default class App extends Component {
realm;
componentWillMount() {
const realm = this.realm = new Realm({
schema: [
{
name: 'User',
properties: {
firstName: 'string',
lastName: 'string',
email: 'string'
}
}
]
});
}
// Defined in later steps.
}
- 为了创建
User
条目,我们将使用randomuser.me提供的随机用户生成器 API。让我们用getRandomUser
函数创建一个方法。这将fetch
此数据:
getRandomUser() {
return fetch('https://randomuser.me/api/')
.then(response => response.json());
}
- 我们还需要一种在应用中创建用户的方法。
createUser
方法将使用我们之前定义的函数获取随机用户,然后使用realm.write
方法和realm.create
方法将其保存到我们的领域数据库中:
createUser = () => {
const realm = this.realm;
this.getRandomUser().then((response) => {
const user = response.results[0];
const userName = user.name;
realm.write(() => {
realm.create('User', {
firstName: userName.first,
lastName: userName.last,
email: user.email
});
this.setState({users:realm.objects('User')});
});
});
}
- 由于我们正在与数据库交互,我们还应该添加一个函数来更新数据库中的
User
。updateUser
为了简单起见,将获取集合中的第一条记录并更改其信息:
updateUser = () => {
const realm = this.realm;
const users = realm.objects('User');
realm.write(() => {
if(users.length) {
let firstUser = users.slice(0,1)[0];
firstUser.firstName = 'Bob';
firstUser.lastName = 'Cookbook';
firstUser.email = 'react.native@cookbook.com';
this.setState(users);
}
});
}
- 最后,让我们添加一种删除用户的方法。我们将添加一个
deleteUsers
方法来删除所有用户。这是通过使用执行realm.deleteAll
的回调函数调用realm.write
来实现的:
deleteUsers = () => {
const realm = this.realm;
realm.write(() => {
realm.deleteAll();
this.setState({users:realm.objects('User')});
});
}
- 让我们构建我们的 UI。我们将为我们的
create
、update
和delete
方法呈现User
对象列表和按钮:
render() {
const realm = this.realm;
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to Realm DB Test!
</Text>
<View style={styles.buttonContainer}>
<TouchableOpacity style={styles.button}
onPress={this.createUser}>
<Text style={styles.buttontext}>Add User</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}
onPress={this.updateUser}>
<Text>Update First User</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.button}
onPress={this.deleteUsers}>
<Text>Remove All Users</Text>
</TouchableOpacity>
</View>
<View style={styles.container}>
<Text style={styles.welcome}>Users:</Text>
{this.state.users.map((user, idx) => {
return <Text key={idx}>{user.firstName} {user.lastName}
{user.email}</Text>;
})}
</View>
</View>
);
}
- 一旦我们在任何一个平台上运行应用,我们与数据库交互的三个按钮应显示在保存在 Realm 数据库中的实时数据上:
它是如何工作的。。。
领域数据库是在 C++中构建的,其核心被称为领域对象存储 AutoT1。每个主要平台(Java、Objective-C、Swift、Xamarin 和 React Native)都有封装此对象存储的产品。React 本机实现是 Realm 的 JavaScript 适配器。从 React Native 方面来说,我们不需要担心实现细节。相反,我们得到了一个用于持久化和检索数据的干净 API。步骤 4到步骤 6演示了使用一些基本领域方法。如果您想了解更多关于 API 的功能,请查看相关文档,可在中找到 https://realm.io/docs/react-native/latest/api/ 。
在网络连接丢失时屏蔽应用
互联网连接并不总是可用的,尤其是当人们在城市里走动、坐火车或在山里徒步旅行时。良好的用户体验将在用户与 internet 的连接丢失时通知用户。
在此配方中,我们将创建一个应用,在网络连接丢失时显示消息。
准备
我们需要创建一个空的应用。让我们把它命名为network-loss
怎么做。。。
- 首先,我们将必要的依赖项导入到
App.js
中:
import React, { Component } from 'react';
import {
SafeAreaView,
NetInfo,
StyleSheet,
Text,
View,
Platform
} from 'react-native';
- 接下来,我们将定义用于存储连接状态的
App
类和state
对象。如果已连接,online
布尔值将为true
,如果未连接,offline
布尔值将为true
:
export default class App extends Component {
state = {
online: null,
offline: null,
};
// Defined in later steps
}
- 创建组件后,我们需要获得初始网络状态。我们将使用
NetInfo
类的getConnectionInfo
方法来获取当前状态,并且我们还将设置一个回调,当状态更改时将执行该回调:
componentWillMount() {
NetInfo.getConnectionInfo().then((connectionInfo) => {
this.onConnectivityChange(connectionInfo);
});
NetInfo.addEventListener('connectionChange',
this.onConnectivityChange);
}
- 当组件即将被销毁时,我们需要通过
componentWillUnmount
生命周期删除侦听器:
componentWillUnmount() {
NetInfo.removeEventListener('connectionChange',
this.onConnectivityChange);
}
- 让我们添加在网络状态更改时执行的回调。只需检查当前网络类型是否为
none
,并相应设置state
:
onConnectivityChange = connectionInfo => {
this.setState({
online: connectionInfo.type !== 'none',
offline: connectionInfo.type === 'none',
});
}
- 现在,我们知道网络何时开启或关闭,但我们仍然需要一个用于显示信息的 UI。让我们呈现一个工具栏,其中包含一些虚拟文本作为内容:
render() {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.toolbar}>My Awesome App</Text>
<Text style={styles.text}>Lorem...</Text>
<Text style={styles.text}>Lorem ipsum...</Text>
{this.renderMask()}
</SafeAreaView>
);
}
- 从上一步可以看到,有一个
renderMask
函数。当网络处于脱机状态时,此函数将返回模式,如果网络处于联机状态,则不会返回任何内容:
renderMask() {
if (this.state.offline) {
return (
<View style={styles.mask}>
<View style={styles.msg}>
<Text style={styles.alert}>Seems like you do not have
network connection anymore.</Text>
<Text style={styles.alert}>You can still continue
using the app, with limited content.</Text>
</View>
</View>
);
}
}
- 最后,让我们为我们的应用添加样式。我们将从工具栏和内容开始:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
toolbar: {
backgroundColor: '#3498db',
padding: 15,
fontSize: 20,
color: '#fff',
textAlign: 'center',
},
text: {
padding: 10,
},
// Defined in next step
}
- 对于断开连接消息,我们将在所有内容的顶部呈现一个黑色遮罩,并在屏幕中央呈现一个包含文本的容器。对于
mask
,我们需要将位置设置为absolute
,然后将top
、bottom
、right
、left
设置为0
。我们还将为遮罩的背景色添加不透明度,并将内容对齐到中心:
const styles = StyleSheet.create({
// Defined in previous step
mask: {
alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
bottom: 0,
justifyContent: 'center',
left: 0,
position: 'absolute',
top: 0,
right: 0,
},
msg: {
backgroundColor: '#ecf0f1',
borderRadius: 10,
height: 200,
justifyContent: 'center',
padding: 10,
width: 300,
},
alert: {
fontSize: 20,
textAlign: 'center',
margin: 5,
}
});
- 要查看模拟器中显示的掩码,必须断开模拟设备与 internet 的连接。对于 iOS 模拟器,只需断开 Mac 的 Wi-Fi 或拔下以太网即可断开模拟器与 internet 的连接。在 Android emulator 上,您可以通过工具栏禁用手机的 Wi-Fi 连接:
- 一旦设备与 internet 断开连接,掩码应相应显示:
它是如何工作的。。。
在步骤 2中,我们创建了具有两个属性的初始state
对象:online
在网络连接可用时为true
,在网络连接不可用时为offline
。
在步骤 3中,我们检索了初始网络状态,并设置了一个侦听器来检查状态何时发生变化。NetInfo
返回的网络类型为wifi
、cellular
、unknown
或none
。Android 还提供了额外的选项bluetooth
、ethernet
和WiMAX
(用于 WiMAX 连接)。您可以阅读文档以查看所有可用值:https://facebook.github.io/react-native/docs/netinfo.html 。
在步骤 5中,我们定义了当网络状态发生变化时将执行的方法,并相应地设置了online
和offline
的state
值。更新状态会重新呈现 DOM,如果没有连接,则会显示掩码。
使用远程 API 同步本地持久化数据
当使用移动应用时,网络连接通常被认为是理所当然的。但是,当您的应用需要进行 API 调用,而用户刚刚失去连接时,会发生什么情况?幸运的是,React Native 有一个对网络连接状态做出反应的模块。通过在网络连接恢复后自动同步数据,我们可以以支持连接丢失的方式构建应用。
这个方法将展示一个使用NetInfo
模块控制应用是否进行 API 调用的简单实现。如果连接丢失,我们将保留挂起请求的引用,并在恢复网络访问时完成它。我们将使用http://jsonplaceholder.typicode.com 再次向实时服务器发出POST
请求。
准备
对于此配方,我们将使用名为syncing-data
的空 React 本机应用。
怎么做。。。
- 我们将通过将依赖项导入到
App.js
中来开始此配方:
import React from 'react';
import {
StyleSheet,
Text,
View,
NetInfo,
TouchableOpacity
} from 'react-native';
- 我们需要添加
pendingSync
类变量,当没有可用的网络连接时,我们将使用该变量存储挂起的请求。我们还将创建具有属性的state
对象,用于跟踪应用是否已连接(isConnected
),同步状态(syncStatus
),以及发出POST
请求后服务器的响应(serverResponse
:
export default class App extends React.Component {
pendingSync;
state = {
isConnected: null,
syncStatus: null,
serverResponse: null
}
// Defined in later steps
}
- 在
componentWillMount
生命周期钩子中,我们将通过NetInfo.isConnected.fetch
方法获取网络连接的状态,通过响应设置状态的isConnected
属性。我们还将向connectionChange
事件添加一个事件侦听器,用于跟踪连接的更改:
componentWillMount() {
NetInfo.isConnected.fetch().then(isConnected => {
this.setState({isConnected});
});
NetInfo.isConnected.addEventListener('connectionChange',
this.onConnectionChange);
}
- 接下来,让我们实现将由我们在上一步中定义的事件侦听器执行的回调。在这个方法中,我们更新了
state
的isConnected
属性。然后,如果定义了pendingSync
类变量,这意味着我们有一个缓存的POST
请求,因此我们将提交该请求并相应地更新状态:
onConnectionChange = (isConnected) => {
this.setState({isConnected});
if (this.pendingSync) {
this.setState({syncStatus : 'Syncing'});
this.submitData(this.pendingSync).then(() => {
this.setState({syncStatus : 'Sync Complete'});
});
}
}
- 接下来,我们需要实现一个函数,当存在活动网络连接时,该函数将实际进行 API 调用:
submitData(requestBody) {
return fetch('http://jsonplaceholder.typicode.com/posts', {
method : 'POST',
body : JSON.stringify(requestBody)
}).then((response) => {
return response.text();
}).then((responseText) => {
this.setState({
serverResponse : responseText
});
});
}
- 在处理 UI 之前,我们需要做的最后一件事是在提交数据按钮上添加一个处理
onPress
事件的函数。如果没有网络连接,这将立即执行呼叫或保存在this.pendingSync
中:
onSubmitPress = () => {
const requestBody = {
title: 'foo',
body: 'bar',
userId: 1
};
if (this.state.isConnected) {
this.submitData(requestBody);
} else {
this.pendingSync = requestBody;
this.setState({syncStatus : 'Pending'});
}
}
- 现在,我们可以构建 UI,它将呈现“提交数据”按钮,并显示当前连接状态、同步状态和 API 的最新响应:
render() {
const {
isConnected,
syncStatus,
serverResponse
} = this.state;
return (
<View style={styles.container}>
<TouchableOpacity onPress={this.onSubmitPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Submit Data</Text>
</View>
</TouchableOpacity>
<Text style={styles.status}>
Connection Status: {isConnected ? 'Connected' :
'Disconnected'}
</Text>
<Text style={styles.status}>
Sync Status: {syncStatus}
</Text>
<Text style={styles.status}>
Server Response: {serverResponse}
</Text>
</View>
);
}
- 您可以按照前面配方步骤 10中描述的相同方式在模拟器中禁用网络连接:
它是如何工作的。。。
此方法利用NetInfo
模块控制何时应发出 AJAX 请求。
在步骤 6中,我们定义了按下提交数据按钮时执行的方法。如果没有连接,我们将请求主体保存到pendingSync
类变量中。
在步骤 3中,我们定义了componentWillMount
生命周期挂钩。这里,两个NetInfo
方法调用检索当前网络连接状态,并将事件侦听器附加到更改事件。
在步骤 4中,我们定义了当网络连接发生变化时将执行的函数,该函数适当地通知状态的isConnected
布尔属性。如果设备已连接,我们还将检查是否存在挂起的 API 调用,如果存在,则完成请求。
这个方法还可以扩展到支持挂起调用的队列系统,这将允许多个 AJAX 请求被延迟,直到重新建立 internet 连接。
使用 Facebook 登录
Facebook 是现存最大的社交媒体平台,在全球拥有超过 10 亿用户。这意味着你的用户很有可能拥有 Facebook 帐户。你的应用可以注册并链接到他们的帐户,允许你使用他们的 Facebook 凭据登录你的应用。根据请求的权限,这还允许您访问用户信息和图片等数据,甚至允许您访问共享内容。您可以在上的 Facebook 文档中阅读更多关于可用权限选项的信息 https://developers.facebook.com/docs/facebook-login/permissions#reference-公共 _ 档案。
在本食谱中,我们将介绍通过应用登录 Facebook 以获取会话令牌的基本方法。然后,我们将使用该令牌访问 Facebook 的 Graph API 提供的基本/me
端点,该端点将为我们提供用户名和 ID。有关与 Facebook Graph API 的更复杂交互,您可以查看文档,可以在中找到 https://developers.facebook.com/docs/graph-api/using-graph-api 。
为了简单起见,我们将构建一个世博会应用,它使用Expo.Facebook.logInWithReadPermissionsAsync
方法完成登录 Facebook 的繁重工作,这也将允许我们绕过许多其他应用所需的设置。如果您希望在不使用 Expo 的情况下与 Facebook 进行交互,则可能需要使用 React 原生 Facebook SDK,这需要更多步骤。您可以在找到 SDKhttps://github.com/facebook/react-native-fbsdk 。
准备
对于这个配方,我们将创建一个名为facebook-login
的新应用。你需要有一个活跃的 Facebook 帐户来测试它的功能。
Facebook 开发者帐户也是这个配方所必需的。前往https://developers.facebook.com 如果您没有,请注册。登录后,您可以使用仪表板创建新的应用。创建应用 ID 后,请记下它,因为我们需要它来制作配方。
怎么做。。。
- 让我们首先打开
App.js
文件并添加我们的导入:
import React from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Alert
} from 'react-native';
import Expo from 'expo';
- 接下来,我们将声明
App
类并添加state
对象。state
将跟踪用户是否使用loggedIn
布尔值登录,并将从 Facebook 检索到的用户数据保存在一个名为facebookUserInfo
的对象中:
export default class App extends React.Component {
state = {
loggedIn: false,
facebookUserInfo: {}
}
// Defined in later steps
}
- 接下来,让我们定义我们类的
logIn
方法。这将是按下登录按钮时调用的方法。此方法使用Facebook
方法的logInWithReadPermissionsAsync
Expo helper 类向用户提示 Facebook 登录屏幕。将以下代码中标记为APP_ID
的第一个参数替换为您的应用 ID:
logIn = async () => {
const { type, token } = await
Facebook.logInWithReadPermissionsAsync(APP_ID, {
permissions: ['public_profile'],
});
// Defined in next step
}
- 在
logIn
方法的后半部分,如果请求成功,我们将使用登录时收到的令牌调用 Facebook Graph API,以请求登录用户的信息。一旦响应解决,我们将相应地设置状态:
logIn = async () => {
//Defined in step above
if (type === 'success') {
const response = await fetch(`https://graph.facebook.com/me?
access_token=${token}`);
const facebookUserInfo = await response.json();
this.setState({
facebookUserInfo,
loggedIn: true
});
}
}
- 我们还需要一个简单的
render
函数。我们将显示用于登录的登录按钮,以及成功完成登录后显示用户信息的Text
元素:
render() {
return (
<View style={styles.container}>
<Text style={styles.headerText}>Login via Facebook</Text>
<TouchableOpacity
onPress={this.logIn}
style={styles.button}
>
<Text style={styles.buttonText}>Login</Text>
</TouchableOpacity>
{this.renderFacebookUserInfo()}
</View>
);
}
- 正如您在前面的
render
函数中所看到的,我们正在调用this.renderFacebookUserInfo
来呈现用户信息。此方法仅检查用户是否通过this.state.loggedIn
登录。如果是,我们将显示用户的信息。否则返回null
不显示:
renderFacebookUserInfo = () => {
return this.state.loggedIn ? (
<View style={styles.facebookUserInfo}>
<Text style={styles.facebookUserInfoLabel}>Name:</Text>
<Text style={styles.facebookUserInfoText}>
{this.state.facebookUserInfo.name}</Text>
<Text style={styles.facebookUserInfoLabel}>User ID:</Text>
<Text style={styles.facebookUserInfoText}>
{this.state.facebookUserInfo.id}</Text>
</View>
) : null;
}
- 最后,我们将添加样式以完成布局、设置填充、边距、颜色和字体大小:
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
button: {
marginTop: 30,
padding: 10,
backgroundColor: '#3B5998'
},
buttonText: {
color: '#fff',
fontSize: 30
},
headerText: {
fontSize: 30
},
facebookUserInfo: {
paddingTop: 30
},
facebookUserInfoText: {
fontSize: 24
},
facebookUserInfoLabel: {
fontSize: 20,
marginTop: 10,
color: '#474747'
}
});
- 现在,如果我们运行应用,我们将看到我们的登录按钮、按下登录按钮时的登录模式以及用户信息,用户成功登录后将显示这些信息:
它是如何工作的。。。
通过世博会的Facebook
助手库,在我们的 React 原生应用中与 Facebook 进行交互变得比其他应用简单得多。
在步骤 5中,我们创建了logIn
函数,该函数使用Facebook.logInWithReadPermissionsAsync
向 Facebook 发出登录请求。它需要两个参数:一个appID
和一个选项对象。在我们的例子中,我们只设置权限选项。permissions 选项为请求的每种类型的权限获取一个字符串数组,但出于我们的目的,我们只使用最基本的权限'public_profile'
在步骤 6中,我们完成了logIn
功能。成功登录后,它使用从logInWithReadPermissionsAsync
返回的数据提供的令牌调用 Facebook 的 Graph API 端点/me
。用户信息和登录状态保存为 state,这将触发重新渲染并在屏幕上显示用户数据。
此配方有意只调用一个简单的 API 端点。您可以使用此端点的返回数据在应用中填充用户数据。或者,您可以使用从登录中收到的相同令牌来执行 Graph API 提供的任何操作。要通过 API 查看您可以使用的数据类型,您可以在查看参考文档 https://developers.facebook.com/docs/graph-api/reference 。
版权属于:月萌API www.moonapi.com,转载请注明出处