在本章中,我们将介绍以下配方:
- 本地存储和检索数据
- 从远程 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 。
在前面的章节中,我们使用了 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
回调时,将自动设置正确的范围。
在前面的配方中,我们介绍了如何使用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
方法。最后,我们使用输入文本来显示结果。
在此配方中,我们将在 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 = '[email protected]';
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 调用,而用户刚刚失去连接时,会发生什么情况?幸运的是,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 是现存最大的社交媒体平台,在全球拥有超过 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 。