Expo是一系列工具,可让您更轻松地编写React Native应用程序。 在本教程中,我将向您展示如何使用Expo快速创建React Native应用。
使用Expo,开发人员可以创建React Native应用程序,而不会因安装和配置软件依赖项(例如Android Studio,Xcode或开发和运行React Native应用程序所需的所有其他工具)而带来的所有麻烦。
在本教程中,我将向您展示如何使用Expo创建一个简单的记忆游戏。 在此过程中,您还将学到以下内容:
- 如何使用Expo提供的工具。 这包括CLI,SDK和Expo客户端应用程序。
- 如何使用Expo创建React Native应用
什么是世博会?
Expo是一个用于快速开发React Native应用程序的框架。 就像PHP开发人员使用Laravel或Symphony,Ruby开发人员使用Ruby on Rails。 Expo在React Native API之上提供了一层,使它们更易于使用和管理。 它还提供了一些工具,使引导和测试React Native应用程序变得更加容易。 最后,它提供仅在安装第三方React Native组件时才可用的UI组件和服务。 所有这些都可以通过Expo SDK获得。
博览会的局限性
在继续进行之前,了解Expo的一些局限性很重要:
- 博览会应用程序 不 支持后台代码执行。 例如,这意味着您无法运行关闭应用程序时侦听位置更改的代码。
- Expos应用程序仅限于Expo SDK支持的本机API。 这意味着,如果您的应用具有非常特定的用例,例如与Bluetooth外设通信,则实现此类功能的唯一选择是使用纯React Native,或使用称为ExpoKit的库编写本机代码。
- 世博会将您锁定 在 他们的工具 集中 。 这意味着您不能简单地安装和使用大多数可用于React Native开发的出色工具,例如命令行工具,脚手架和UI框架。 但是,好处是Expo SDK与普通的React Native应用程序兼容,因此从Expo退出应用程序时不会有任何问题。
- Expo应用程序的独立二进制文件只能在线构建。 Expo提供了一个名为Exp的命令行工具。 这使开发人员可以在Expo服务器上启动构建过程。 完成后,将提供一个URL以下载.apk或.ipa文件。
即使有这些限制,也要记住,Expo是一个功能齐全的框架,并为常用的Android或iOS API提供了大量支持。 这意味着它可以满足您应用程序通常需要的大多数功能。 因此,通常无需查看Expo即可实现本机功能。
应用概述
我们将要创建的应用是一款记忆游戏。 您可能熟悉这种类型的游戏-用户必须通过一次翻两张牌来找到匹配对。 这是默认屏幕的外观:

这是所有对都打开后的样子:

解决游戏问题后,用户可以点击“ 重置”按钮以将项目重置为其初始状态。 这使他们可以重新开始游戏。
安装博览会
与必须安装和配置Android Studio或Xcode以及其他依赖项的普通React Native不同,使用Expo只需几步即可开始开发应用程序:
- 下载 并安装Node.js。 Expo依靠Node.js平台来提供命令行工具和依赖项管理。
- 在 iOS 或 Android 设备 上安装Expo Client 。 用于在开发应用程序时预览应用程序。
- 安装命令行工具。 这使您可以生成新的Expo项目,启动构建过程等。 执行以下命令进行安装:
npm install exp --global
生成新的Expo App
安装完所有依赖项后,您现在可以生成一个新的Expo应用程序:
exp init MemoryGame
完成后,它将创建一个名为MemoryGame的新文件夹。 在其中导航并开始运行开发服务器:
cd MemoryGame
exp start
或者,您也可以使用Expo XDE。 这使您可以通过GUI创建和运行Expo应用。 您可以从Expo GitHub存储库下载安装程序。 当前,它仅支持Windows和Mac。 因此,如果您使用的是Ubuntu或Linux,最好暂时坚持使用命令行。
开发服务器运行后,您现在应该可以看到如下内容:

这就是指向项目实时预览的QR码。 打开手机上的Expo客户端应用程序,然后使用QR扫描仪扫描代码。 此时,您现在应该可以查看默认屏幕。 每次在任何项目文件上按Control-S时,预览应自动重新加载以反映所做的更改。
您可以在其GitHub repo上找到该项目的完整源代码。 或者,如果您想尝试一下该应用程序,则可以查看演示 。 只需选择QR Code,然后使用Expo Client应用程序在手机上对其进行扫描。
编写应用程式
现在我们准备对应用程序进行编码。 让我们从一些UI组件开始,然后再返回并实现主要组件。
标头组件
标头用于显示应用程序的标题。 创建一个组件文件夹。 在其中创建一个Header.js文件,并添加以下内容:
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class Header extends React.Component {
render() {
return (
<View style={styles.header}>
<Text style={styles.header_text}>MemoryGame</Text>
</View>
);
}
}
const styles = StyleSheet.create({
header: {
flex: 1,
flexDirection: 'column',
alignSelf: 'stretch',
paddingTop: 20,
paddingBottom: 5,
backgroundColor: '#f3f3f3'
},
header_text: {
fontWeight: 'bold',
fontSize: 17,
textAlign: 'center'
}
});
这只是一个基本的React Native组件,具有一些样式可以匹配我们应用的UI。
分数成分
接下来是显示分数的组件 ( components / Score.js ):
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class Score extends React.Component {
render() {
return (
<View style={styles.score_container}>
<Text style={styles.score}>{this.props.score}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
score_container: {
flex: 1,
alignItems: 'center',
padding: 10
},
score: {
fontSize: 40,
fontWeight: 'bold'
}
});
同样,只是一个具有文本视图和一些基本样式的简单显示组件。
卡组件
卡组件( components / Card.js )将显示卡。 这些卡使用来自Expo Vector图标集的图标 。 这是使用Expo时开箱即用的功能之一:它包含来自FontAwesome , Entypo和Ionicons等图标集的图标。
在下面的代码中,您可以看到我们仅使用FontAwesome。 它具有我们想要显示卡的默认状态的图标:问号。 稍后您将在主要应用程序组件中看到,我们还将使用Entypo和Ionicons中的图标。 对这些图标源的引用将传递到此组件,因此无需在此处指定它们:
import React from 'react';
import { StyleSheet, Text, View, TouchableHighlight } from 'react-native';
import { FontAwesome } from '@expo/vector-icons'; // use FontAwesome from the expo vector icons
在render()
方法内部,如果打开卡,我们仅使用作为道具传递的源和图标。 默认情况下,它将仅显示FontAwesome中的问号图标。 但是,如果卡片是打开的,它将使用作为道具传递的图标来源,图标和颜色。
每个卡都可以被点击。 点击后,将运行clickCard()
函数,该函数也将通过props传递。 稍后您将看到函数的功能,但是现在,仅知道它会更新状态以显示卡上的图标:
export default class Card extends React.Component {
render() {
let CardSource = FontAwesome; // set FontAwesome as the default icon source
let icon_name = 'question-circle';
let icon_color = '#393939';
if(this.props.is_open){
CardSource = this.props.src;
icon_name = this.props.name;
icon_color = this.props.color;
}
return (
<View style={styles.card}>
<TouchableHighlight onPress={this.props.clickCard} activeOpacity={0.75} underlayColor={"#f1f1f1"}>
<CardSource
name={icon_name}
size={50}
color={icon_color}
/>
</TouchableHighlight>
</View>
);
}
}
不要忘记添加样式:
const styles = StyleSheet.create({
card: {
flex: 1,
alignItems: 'center'
},
card_text: {
fontSize: 50,
fontWeight: 'bold'
}
});
帮手
我们还将使用一个名为shuffle()
的辅助函数。 这使我们可以按随机顺序对纸牌阵列进行排序,以便每次重置游戏时它们的顺序都会不同:
Array.prototype.shuffle = function() {
var i = this.length, j, temp;
if(i == 0) return this;
while(--i){
j = Math.floor(Math.random() * (i + 1));
temp = this[i];
this[i] = this[j];
this[j] = temp;
}
return this;
}
主要成分
主要组件( App.js )包含主要的应用程序逻辑,并将所有内容组合在一起。 首先包括我们将要使用的React和Expo软件包。 这次,我们使用来自Expo矢量图标的所有图标源:
import React from 'react';
import { StyleSheet, View, Button } from 'react-native';
import { Ionicons, FontAwesome, Entypo } from '@expo/vector-icons';
接下来,包括我们之前创建的组件和帮助器:
import Header from './components/Header';
import Score from './components/Score';
import Card from './components/Card';
import helpers from './helpers';
在构造函数内部,我们首先创建代表唯一卡的数组。 src
是图标的来源, name
是图标的名称(如果要使用其他图标,可以在GitHub上找到名称),并且color
自然是图标的颜色:
export default class App extends React.Component {
constructor(props) {
super(props);
// bind the functions to the class
this.renderCards = this.renderCards.bind(this);
this.resetCards = this.resetCards.bind(this);
// icon sources
let sources = {
'fontawesome': FontAwesome,
'entypo': Entypo,
'ionicons': Ionicons
};
// the unique icons to be used
let cards = [
{
src: 'fontawesome',
name: 'heart',
color: 'red'
},
{
src: 'entypo',
name: 'feather',
color: '#7d4b12'
},
{
src: 'entypo',
name: 'flashlight',
color: '#f7911f'
},
{
src: 'entypo',
name: 'flower',
color: '#37b24d'
},
{
src: 'entypo',
name: 'moon',
color: '#ffd43b'
},
{
src: 'entypo',
name: 'youtube',
color: '#FF0000'
},
{
src: 'entypo',
name: 'shop',
color: '#5f5f5f'
},
{
src: 'fontawesome',
name: 'github',
color: '#24292e'
},
{
src: 'fontawesome',
name: 'skype',
color: '#1686D9'
},
{
src: 'fontawesome',
name: 'send',
color: '#1c7cd6'
},
{
src: 'ionicons',
name: 'ios-magnet',
color: '#d61c1c'
},
{
src: 'ionicons',
name: 'logo-facebook',
color: '#3C5B9B'
}
];
// next: add code creating the clone and setting the cards in the state
}
}
请注意,我们不是直接为每个对象将src
指定为FontAwesome
, Entypo
或Ionicons
,而是使用sources
对象中使用的属性名称。 这是因为我们将需要创建卡阵列的副本,以便每个卡都具有一对。 使用诸如slice()
类的数组方法创建副本将创建该数组的副本,但是问题在于,一旦在副本或原始对象中修改了各个对象,两个数组也会被修改。
这将我们带到下面的解决方案,该解决方案是通过将cards
数组转换为字符串,然后对其进行解析以将其转换回数组来创建一个全新的对象。 这就是我们使用字符串的原因,因为函数无法转换为字符串。 然后,我们将两者结合起来以提供一个数组,其中包含我们需要的所有卡:
let clone = JSON.parse(JSON.stringify(cards)); // create a completely new array from the array of cards
this.cards = cards.concat(clone); // combine the original and the clone
接下来,遍历该数组并为每个数组生成唯一的ID,设置图标源,然后默认将其设置为关闭状态:
// add the ID, source and set default state for each card
this.cards.map((obj) => {
let id = Math.random().toString(36).substring(7);
obj.id = id;
obj.src = sources[obj.src];
obj.is_open = false;
});
随机对卡进行排序并设置默认状态:
this.cards = this.cards.shuffle(); // sort the cards randomly
// set the default state
this.state = {
current_selection: [], // this array will contain an array of card objects which are currently selected by the user. This will only contain two objects at a time.
selected_pairs: [], // the names of the icons. This array is used for excluding them from further selection
score: 0, // default user score
cards: this.cards // the shuffled cards
}
render()
方法呈现标题,卡片,得分和用于重置当前游戏的按钮。 它使用renderRows()
函数呈现单个卡片行。 屏幕将有六行,每行包含四张卡:
render() {
return (
<View style={styles.container}>
<Header />
<View style={styles.body}>
{
this.renderRows.call(this)
}
</View>
<Score score={this.state.score} />
<Button
onPress={this.resetCards}
title="Reset"
color="#008CFA"
/>
</View>
);
}
这是renderRows()
函数的代码。 这使用了getRowContents()
函数,该函数负责创建一个数组,每个数组包含四个项目。 这允许我们渲染每一行,然后使用另一个函数为map()
函数的每次迭代渲染卡片:
renderRows() {
let contents = this.getRowContents(this.state.cards);
return contents.map((cards, index) => {
return (
<View key={index} style={styles.row}>
{ this.renderCards(cards) }
</View>
);
});
}
这是getRowContents()
函数:
getRowContents(cards) {
let contents_r = [];
let contents = [];
let count = 0;
cards.forEach((item) => {
count += 1;
contents.push(item);
if(count == 4){
contents_r.push(contents)
count = 0;
contents = [];
}
});
return contents_r;
}
接下来是renderCards()
函数。 这接受卡对象数组,并通过Card
组件渲染它们。 我们需要做的就是将每个卡片对象的各个属性作为道具传递。 然后,如您在Card
组件的代码中所看到的,它用于呈现正确的图标。 clickCard()
函数也作为道具传递。 卡ID传递给该功能,以便可以识别和更新唯一卡:
renderCards(cards) {
return cards.map((card, index) => {
return (
<Card
key={index}
src={card.src}
name={card.name}
color={card.color}
is_open={card.is_open}
clickCard={this.clickCard.bind(this, card.id)}
/>
);
});
}
在clickCard()
函数内部,我们获取所选卡的详细信息,并检查是否应对其进行进一步处理:
clickCard(id) {
let selected_pairs = this.state.selected_pairs;
let current_selection = this.state.current_selection;
let score = this.state.score;
// get the index of the currently selected card
let index = this.state.cards.findIndex((card) => {
return card.id == id;
});
let cards = this.state.cards;
// the card shouldn't already be opened and is not on the array of cards whose pairs are already selected
if(cards[index].is_open == false && selected_pairs.indexOf(cards[index].name) === -1){
// next: add code for processing the selected card
}
}
现在,让我们填写用于处理所选卡的代码。
首先,我们打开卡片并将其添加到当前选择的卡片阵列中:
cards[index].is_open = true;
current_selection.push({
index: index,
name: cards[index].name
});
// next: add code for determining whether the user has selected the correct pair or not
当前选择的卡阵列中有两项时,我们将检查图标名称是否相同。 如果是,则表示用户已选择正确的对。 如果它们不相同,那就是不正确的对。 在这种情况下,我们将关闭选择的第一张卡,然后在关闭第二张卡之前增加一些延迟。 (通过这种方式,用户可以在卡片图标恢复为关闭状态之前看到它。)
if(current_selection.length == 2){
if(current_selection[0].name == current_selection[1].name){
score += 1; // increment the score
selected_pairs.push(cards[index].name);
}else{
cards[current_selection[0].index].is_open = false; // close the first
// delay closing the currently selected card by half a second.
setTimeout(() => {
cards[index].is_open = false;
this.setState({
cards: cards
});
}, 500);
}
current_selection = [];
}
// next: add code for updating the state
在click事件处理程序中,我们需要做的最后一件事是更新状态以反映UI中的更改:
this.setState({
score: score,
cards: cards,
current_selection: current_selection
});
一个相关的功能是重置事件处理程序。 轻按重置按钮后,我们只需关闭所有卡并重新排列即可恢复默认状态。
resetCards() {
// close all cards
let cards = this.cards.map((obj) => {
obj.is_open = false;
return obj;
});
cards = cards.shuffle(); // re-shuffle the cards
// update to default state
this.setState({
current_selection: [],
selected_pairs: [],
cards: cards,
score: 0
});
}
最后,我们将添加一些基本样式以使我们的应用看起来不错。
const styles = StyleSheet.create({
container: {
flex: 1,
alignSelf: 'stretch',
backgroundColor: '#fff'
},
row: {
flex: 1,
flexDirection: 'row'
},
body: {
flex: 18,
justifyContent: 'space-between',
padding: 10,
marginTop: 20
}
});
测试应用
由于您的Expo开发服务器一直在运行,因此每次更改都应通过实时重新加载推送到您的移动设备上。 试用该应用程序,并确保其可以正常运行。
结论
而已! 在本教程中,您学习了如何使用Expo XDE快速连接React Native应用程序。 Expo是开始开发React Native应用程序的一种非常好的方法,因为它消除了安装很多软件的需求,而这通常会使人们感到沮丧,特别是对于初学者。 它还提供了一些工具,可在开发过程中非常轻松地预览应用程序。 如果您想了解更多信息,请务必查看世博网站上提到的资源 。
翻译自: https://code.tutsplus.com/tutorials/easier-react-native-development-with-expo--cms-30546