八、使用应用逻辑和数据

在本章中,我们将介绍以下配方:

  • 本地存储和检索数据
  • 从远程 API 检索数据
  • 向远程 API 发送数据
  • 与 WebSocket 建立实时通信
  • 将持久数据库功能与领域集成
  • 在网络连接丢失时屏蔽应用
  • 使用远程 API 同步本地持久化数据

介绍

开发任何应用最重要的方面之一就是处理数据。这些数据可能来自本地用户,可能由公开 API 的远程服务器提供,或者与大多数业务应用一样,可能是两者的某种组合。您可能想知道什么策略最适合处理数据,或者甚至想知道如何完成简单的任务,例如发出 HTTP 请求。幸运的是,React Native 通过提供方便处理来自所有不同来源的数据的机制,使您的生活变得更加简单。

开源社区更进一步,提供了一些可以与 React Native 一起使用的优秀模块。在本章中,我们将讨论如何在各个方面使用数据,以及如何将数据集成到我们的 React 本机应用中。

本地存储和检索数据

当开发一个移动应用时,我们需要考虑需要克服的网络挑战。设计良好的应用应允许用户在没有互联网连接时继续使用该应用。这要求应用在没有 internet 连接时将数据本地保存在设备上,并在网络再次可用时将数据与服务器同步。

另一个需要克服的挑战是网络连接,它可能很慢或有限。为了提高应用的性能,我们应该将关键数据保存在本地设备上,以避免给服务器 API 带来压力。

在本食谱中,我们将学习一种基本而有效的策略,用于从设备本地保存和检索数据。我们将创建一个带有文本输入和两个按钮的简单应用,一个用于保存字段内容,另一个用于加载现有内容。我们将使用AsyncStorage课程来实现我们的目标。

准备

我们需要创建一个名为local-data-storage的空应用。

怎么做。。。

  1. 我们将从App组件开始。让我们从导入所有依赖项开始:
import React, { Component } from 'react';
import {
  Alert,
  AsyncStorage,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native';
  1. 现在,让我们创建App类。我们将创建一个key常量,以便设置用于保存内容的键的名称。在state上,我们将有两个属性:一个用于保存文本输入组件的值,另一个用于加载和显示当前存储的值:
const key = '@MyApp:key';

export default class App extends Component {
  state = {
    text: '',
    storedValue: '',
  };
  //Defined in later steps
}
  1. 当组件挂载时,我们希望加载现有的存储值(如果存在)。我们将在应用加载后显示内容,因此我们需要在componentWillMount生命周期方法中读取本地值:
  componentWillMount() {
    this.onLoad();
  }
  1. 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');
    }
  }
  1. 保存数据也很简单。我们将通过AsyncStoragesetItem方法声明一个密钥,以保存我们想要与该密钥关联的任何数据:
  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');
    }
  }
  1. 接下来,我们需要一个将输入文本中的值保存到state的函数。当输入值发生变化时,我们会得到新的值并保存到state
        onChange = (text) => { 
          this.setState({ text }); 
        }  
  1. 我们的 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>
    );
  }
  1. 最后,让我们添加一些样式。这将是简单的颜色、填充、边距和布局,如第 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,
  },
});
  1. 最终的应用应类似于以下屏幕截图:

它是如何工作的。。。

AsyncStorage类允许我们在本地设备上轻松保存数据。在 iOS 上,这是通过在文本文件上使用字典来实现的。在 Android 上,它将使用 RocksDB 或 SQLite,具体取决于可用的资源。

It's not recommended to save sensitive information using this method, as the data is not encrypted.

步骤 4中,我们加载了当前保存的数据。AsyncStorageAPI 包含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

怎么做。。。

  1. 让我们首先将依赖项导入到App.js文件中:
import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View
} from 'react-native';
  1. 我们将在state上定义一个results属性。此属性将保存来自 API 的响应。收到响应后,我们需要更新视图:
export default class App extends Component {
  state = {
    results: '',
  };
  // Defined later
}

const styles = StyleSheet.create({
  // Defined later
});
  1. 按下按钮时,我们将发送请求。接下来,让我们创建一个方法来处理该请求:
  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 });
  }
  1. 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>
    );
  }
  1. 最后,我们将添加一些样式。同样,这将只是布局、颜色、边距和填充:
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,
  },
});
  1. 最终的应用应类似于以下屏幕截图:

它是如何工作的。。。

步骤 4中,我们向 API 发送了请求。我们使用fetch方法进行请求。第一个参数是带有端点 URL 的字符串,而第二个参数是配置对象。对于这个请求,我们需要定义的唯一选项是GETrequest方法,但是我们也可以使用这个对象来定义头、cookie、参数和许多其他东西。

我们还使用了async/await语法来等待响应,并最终将其设置为state。如果你愿意,你当然可以用承诺来代替。

另外,请注意我们在这里是如何使用 arrow 函数来正确处理作用域的。将此方法分配给onPress回调时,将自动设置正确的范围。

向远程 API 发送数据

在前面的配方中,我们介绍了如何使用fetch从 API 获取数据。在这个配方中,我们将学习如何将POST数据传输到相同的 API。此应用将模拟创建论坛帖子,帖子请求将具有titlebodyuser参数

准备

在使用此配方之前,我们需要创建一个名为remote-api-post的新空应用

在此配方中,我们还将使用非常流行的axios包来处理我们的 API 请求。您可以通过带有yarn的终端进行安装:

yarn add axios

或者,您可以使用npm

npm install axios --save

怎么做。。。

  1. 首先,我们需要打开App.js文件并导入我们将使用的依赖项:
import React, { Component } from 'react';
import axios from 'axios';
import {
  Alert,
  ScrollView,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  SafeAreaView,
} from 'react-native';
  1. 我们将使用具有三个属性的state对象定义App类。titlebody属性将用于发出请求,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 
  }); 
}
  1. 保存新帖子后,我们将从 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 });
  }
  1. 让我们来保存新数据。首先,我们需要从state中获取值。我们也可以在这里运行一些验证,以确保titlebody不是空的。在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}`);
    }
  }
  1. 保存功能已完成。接下来,我们需要将titlebody保存到state的方法。这些方法将在用户输入文本时执行,跟踪state对象上的值:
  onTitleChange = (title) => this.setState({ title });
  onPostChange = (body) => this.setState({ body });
  1. 我们已经具备了功能所需的一切,所以让我们添加 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>
    );
  }
  1. 最后,让我们添加样式以定义布局、颜色、填充和边距:
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,
 },
});
  1. 最终的应用应类似于以下屏幕截图:

它是如何工作的。。。

步骤 2中,我们在state上定义了三个属性。results属性将包含来自服务器 API 的响应,我们稍后将使用该响应在 UI 中显示值。

我们使用titlebody属性保存来自输入文本组件的值,以便用户可以创建新的帖子。当按下保存按钮时,这些值将被发送到 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 本机应用,我们就可以使用服务器在客户端之间进行通信。

怎么做。。。

  1. 首先,让我们创建App.js文件并导入我们将使用的所有依赖项:
import React, { Component } from 'react';
import {
  Dimensions,
  ScrollView,
  StyleSheet,
  Text,
  TextInput,
  SafeAreaView,
  View,
  Platform
} from 'react-native';
  1. state对象上,我们将声明一个history属性。此属性将是一个数组,用于保存用户之间来回发送的所有消息:
export default class App extends Component {
  state = {
    history: [],
  };
    // Defined in later steps 
} 

const styles = StyleSheet.create({ 
  // Defined in later steps 
}); 
  1. 现在,我们需要将 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;
  }
  1. 让我们为打开/关闭的连接和处理收到的错误定义回调。我们只是要记录这些操作,但在这里,我们可以在连接关闭时显示警报消息,或者在服务器抛出错误时显示错误消息:
  onOpenConnection = () => {
    console.log('Open!');
  }

  onError = (event) => {
    console.log('onerror', event.message);
  }

  onCloseConnection = (event) => {
    console.log('onclose', event.code, event.reason);
  }
  1. 当接收到来自服务器的新消息时,我们需要将其添加到state上的history属性中,以便在新内容到达后立即呈现:
  onMessageReceived = (event) => {
    this.setState({
      history: [
        ...this.state.history,
        { isSentByMe: false, messageText: event.data },
      ],
    });
  }
  1. 现在,继续发送消息。我们需要定义一个方法,当用户按下键盘上的返回键时,该方法将被执行。此时我们需要做两件事:将新消息添加到history,然后通过套接字发送消息:
  onSendMessage = () => {
    const { text } = this.state;

    this.setState({
      text: '',
      history: [
        ...this.state.history,
        { isSentByMe: true, messageText: text },
      ],
    });
    this.ws.send(text);
  }
  1. 在上一步中,我们从state中获得了text属性。每当用户在输入中键入内容时,我们都需要跟踪值,因此我们需要一个函数来监听击键并将值保存到state
  onChangeText = (text) => {
    this.setState({ text });
  }
  1. 我们已经准备好了所有的功能,所以让我们来处理 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>
    );
  }
  1. 为了呈现来自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>
    );
  }
  1. 最后,让我们来研究一下样式!让我们向工具栏、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
}); 
  1. 现在,我们来看看每条消息的样式。我们将为所有消息创建一个名为msg的通用样式对象,然后为设备上用户发送的消息创建样式,最后为其他用户发送的消息创建样式,并相应地更改颜色和对齐方式:
  msg: {
    margin: 5,
    padding: 10,
    borderRadius: 10,
  },
  me: {
    alignSelf: 'flex-start',
    backgroundColor: '#1abc9c',
    marginRight: 100,
  },
  friend: {
    alignSelf: 'flex-end',
    backgroundColor: '#fff',
    marginLeft: 100,
  }
  1. 最终的应用应类似于以下屏幕截图:

它是如何工作的。。。

步骤 2中,我们使用history数组声明了state对象,用于跟踪消息。history属性将保存表示客户端之间交换的所有消息的对象。每个对象都有两个属性:一个包含消息文本的字符串和一个用于确定发送者的布尔标志。我们可以在这里添加更多数据,例如用户的姓名、化身图像的 URL 或我们可能需要的任何其他信息。

步骤 3中,我们连接到 WebSocket 服务器提供的套接字,并设置回调以处理套接字事件。我们指定了服务器地址和端口。

步骤 5中,我们定义了从服务器接收新消息时执行的回调。在这里,每次收到新消息时,我们都会在state上的history数组中添加一个新对象。每个消息对象都有属性isSentByMemessageText

步骤 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

怎么做。。。

  1. 首先,让我们打开App.js并导入我们将使用的依赖项:
import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  TouchableOpacity
} from 'react-native';
import Realm from 'realm';
  1. 接下来,我们需要实例化领域数据库,我们将在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.
}
  1. 为了创建User条目,我们将使用randomuser.me提供的随机用户生成器 API。让我们用getRandomUser函数创建一个方法。这将fetch此数据:
  getRandomUser() {
    return fetch('https://randomuser.me/api/')
      .then(response => response.json());
  }
  1. 我们还需要一种在应用中创建用户的方法。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')});
      });
    });
  }
  1. 由于我们正在与数据库交互,我们还应该添加一个函数来更新数据库中的UserupdateUser为了简单起见,将获取集合中的第一条记录并更改其信息:
  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);
      }
    });
  }
  1. 最后,让我们添加一种删除用户的方法。我们将添加一个deleteUsers方法来删除所有用户。这是通过使用执行realm.deleteAll的回调函数调用realm.write来实现的:
  deleteUsers = () => {
    const realm = this.realm;
    realm.write(() => {
      realm.deleteAll();
      this.setState({users:realm.objects('User')});
    });
  }
  1. 让我们构建我们的 UI。我们将为我们的createupdatedelete方法呈现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>
    );
  }
  1. 一旦我们在任何一个平台上运行应用,我们与数据库交互的三个按钮应显示在保存在 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

怎么做。。。

  1. 首先,我们将必要的依赖项导入到App.js中:
import React, { Component } from 'react';
import {
  SafeAreaView,
  NetInfo,
  StyleSheet,
  Text,
  View,
  Platform
} from 'react-native';
  1. 接下来,我们将定义用于存储连接状态的App类和state对象。如果已连接,online布尔值将为true,如果未连接,offline布尔值将为true
export default class App extends Component {
  state = {
    online: null,
    offline: null,
  };

  // Defined in later steps
}
  1. 创建组件后,我们需要获得初始网络状态。我们将使用NetInfo类的getConnectionInfo方法来获取当前状态,并且我们还将设置一个回调,当状态更改时将执行该回调:
  componentWillMount() {
    NetInfo.getConnectionInfo().then((connectionInfo) => {
      this.onConnectivityChange(connectionInfo);
    });
    NetInfo.addEventListener('connectionChange', 
    this.onConnectivityChange);
  }
  1. 当组件即将被销毁时,我们需要通过componentWillUnmount生命周期删除侦听器:
  componentWillUnmount() {
    NetInfo.removeEventListener('connectionChange', 
    this.onConnectivityChange);
  }
  1. 让我们添加在网络状态更改时执行的回调。只需检查当前网络类型是否为none,并相应设置state
  onConnectivityChange = connectionInfo => {
    this.setState({
      online: connectionInfo.type !== 'none',
      offline: connectionInfo.type === 'none',
    });
  }
  1. 现在,我们知道网络何时开启或关闭,但我们仍然需要一个用于显示信息的 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>
    );
  }
  1. 从上一步可以看到,有一个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>
      );
    }
  }
  1. 最后,让我们为我们的应用添加样式。我们将从工具栏和内容开始:
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
}
  1. 对于断开连接消息,我们将在所有内容的顶部呈现一个黑色遮罩,并在屏幕中央呈现一个包含文本的容器。对于mask,我们需要将位置设置为absolute,然后将topbottomrightleft设置为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,
  }
});
  1. 要查看模拟器中显示的掩码,必须断开模拟设备与 internet 的连接。对于 iOS 模拟器,只需断开 Mac 的 Wi-Fi 或拔下以太网即可断开模拟器与 internet 的连接。在 Android emulator 上,您可以通过工具栏禁用手机的 Wi-Fi 连接:

  1. 一旦设备与 internet 断开连接,掩码应相应显示:

它是如何工作的。。。

步骤 2中,我们创建了具有两个属性的初始state对象:online在网络连接可用时为true,在网络连接不可用时为offline

步骤 3中,我们检索了初始网络状态,并设置了一个侦听器来检查状态何时发生变化。NetInfo返回的网络类型为wificellularunknownnone。Android 还提供了额外的选项bluetoothethernetWiMAX(用于 WiMAX 连接)。您可以阅读文档以查看所有可用值:https://facebook.github.io/react-native/docs/netinfo.html

步骤 5中,我们定义了当网络状态发生变化时将执行的方法,并相应地设置了onlineofflinestate值。更新状态会重新呈现 DOM,如果没有连接,则会显示掩码。

使用远程 API 同步本地持久化数据

当使用移动应用时,网络连接通常被认为是理所当然的。但是,当您的应用需要进行 API 调用,而用户刚刚失去连接时,会发生什么情况?幸运的是,React Native 有一个对网络连接状态做出反应的模块。通过在网络连接恢复后自动同步数据,我们可以以支持连接丢失的方式构建应用。

这个方法将展示一个使用NetInfo模块控制应用是否进行 API 调用的简单实现。如果连接丢失,我们将保留挂起请求的引用,并在恢复网络访问时完成它。我们将使用http://jsonplaceholder.typicode.com 再次向实时服务器发出POST请求。

准备

对于此配方,我们将使用名为syncing-data的空 React 本机应用。

怎么做。。。

  1. 我们将通过将依赖项导入到App.js中来开始此配方:
import React from 'react';
import {
  StyleSheet,
  Text,
  View,
  NetInfo,
  TouchableOpacity
} from 'react-native';
  1. 我们需要添加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
}
  1. componentWillMount生命周期钩子中,我们将通过NetInfo.isConnected.fetch方法获取网络连接的状态,通过响应设置状态的isConnected属性。我们还将向connectionChange事件添加一个事件侦听器,用于跟踪连接的更改:
  componentWillMount() {
    NetInfo.isConnected.fetch().then(isConnected => {
      this.setState({isConnected});
    });
    NetInfo.isConnected.addEventListener('connectionChange', 
    this.onConnectionChange);
  }
  1. 接下来,让我们实现将由我们在上一步中定义的事件侦听器执行的回调。在这个方法中,我们更新了stateisConnected属性。然后,如果定义了pendingSync类变量,这意味着我们有一个缓存的POST请求,因此我们将提交该请求并相应地更新状态:
  onConnectionChange = (isConnected) => {
    this.setState({isConnected});
    if (this.pendingSync) {
      this.setState({syncStatus : 'Syncing'});
      this.submitData(this.pendingSync).then(() => {
        this.setState({syncStatus : 'Sync Complete'});
      });
    }
  }
  1. 接下来,我们需要实现一个函数,当存在活动网络连接时,该函数将实际进行 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
      });
    });
  }
  1. 在处理 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'});
    }
  }
  1. 现在,我们可以构建 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>
    );
  }
  1. 您可以按照前面配方步骤 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 后,请记下它,因为我们需要它来制作配方。

怎么做。。。

  1. 让我们首先打开App.js文件并添加我们的导入:
import React from 'react';
import {
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Alert
} from 'react-native';
import Expo from 'expo';
  1. 接下来,我们将声明App类并添加state对象。state将跟踪用户是否使用loggedIn布尔值登录,并将从 Facebook 检索到的用户数据保存在一个名为facebookUserInfo的对象中:
export default class App extends React.Component {
  state = {
    loggedIn: false,
    facebookUserInfo: {}
  }
  // Defined in later steps
}
  1. 接下来,让我们定义我们类的logIn方法。这将是按下登录按钮时调用的方法。此方法使用Facebook方法的logInWithReadPermissionsAsyncExpo helper 类向用户提示 Facebook 登录屏幕。将以下代码中标记为APP_ID的第一个参数替换为您的应用 ID:
  logIn = async () => {
    const { type, token } = await 
    Facebook.logInWithReadPermissionsAsync(APP_ID, {
      permissions: ['public_profile'],
    });

    // Defined in next step
  }
  1. 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
 });
 }
  }
  1. 我们还需要一个简单的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>
    );
  }
  1. 正如您在前面的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;
  }
  1. 最后,我们将添加样式以完成布局、设置填充、边距、颜色和字体大小:
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'
  }
});
  1. 现在,如果我们运行应用,我们将看到我们的登录按钮、按下登录按钮时的登录模式以及用户信息,用户成功登录后将显示这些信息:

它是如何工作的。。。

通过世博会的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