
iphoneアプリを作ろう!
飲食店の注文アプリ編
スマホから注文できるモバイルオーダーを使ったことがありますか?
席に座ったまま、あるいはお店から離れていても注文ができる、とても便利なアプリです。
タブレットに入っているタイプも多いですね。
今回は実際に飲食店の注文アプリを作りながら、プログラミングを学んでみましょう!
2015年
創業
120名
従業員数
2.3M
月間UU
1,800戸
管理戸数
注文アプリを作ってみよう
今回は、スマートフォンで使える「注文アプリ」を作ります。
メニューを見て商品をカートに追加し、数量を変更して、最後に注文を確定するところまで作ります。
プログラミングが初めてでも進められるように、1ステップずつアプリの画面を確認しながら作っていきます。
はじめに:Expo Snackを開こう
Expo Snackを開いて、React Nativeのアプリを作成できる状態にします。
今回のアプリは、基本的に App.js の内容を書き換えながら作っていきます。
画面の右側に表示されるプレビューを確認しながら進めましょう。
うまく動かなくなった場合は、そのステップに掲載されているソースをApp.jsに丸ごと貼り付けてOK
ですが、理解を深めるためにも、ぜひできるだけ自分の力で完成させていきましょう!
STEP 1:アプリの画面を作ろう
まずは、スマートフォンの画面に「注文アプリ」と表示してみましょう。
今回使う部品を読み込み、アプリの基本となる画面を作ります。
App.jsの内容をすべて削除します。次のソースをすべてコピーして貼り付けます。
プレビューに「注文アプリ」と表示されることを確認します。
import React from 'react';
import {
SafeAreaView,
View,
Text,
Image,
Pressable,
ScrollView,
StyleSheet,
} from 'react-native';
export default function App() {
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>注文アプリ</Text>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scroll: {
padding: 24,
},
});画面に「注文アプリ」と表示されれば成功です。
ポイント
Textは文字を表示するための部品です。Viewは画面の中のまとまりを作るための部品です。SafeAreaViewはスマートフォンの画面内に安全に表示するための部品です。
STEP 2:メニューの商品データを用意しよう
次に、注文できる商品を用意します。
今回は、商品名・価格・画像を1つのデータとして管理します。
商品は6種類あります。
このデータを使って、あとで画面にメニューを表示します。
import React from 'react';
import {
SafeAreaView,
View,
Text,
Image,
Pressable,
ScrollView,
StyleSheet,
} from 'react-native';
// 商品データをこのファイル内で管理する
const MENU = [
{
id: 1,
name: 'ハンバーガー',
price: 650,
image: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=500',
},
{
id: 2,
name: 'チーズバーガー',
price: 750,
image: 'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=500',
},
{
id: 3,
name: 'フライドポテト',
price: 350,
image: 'https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=500',
},
{
id: 4,
name: 'チキンナゲット',
price: 400,
image: 'https://images.unsplash.com/photo-1562967916-eb82221dfb92?w=500',
},
{
id: 5,
name: 'コーラ',
price: 250,
image: 'https://images.unsplash.com/photo-1554866585-cd94860890b7?w=500',
},
{
id: 6,
name: 'オレンジジュース',
price: 250,
image: 'https://images.unsplash.com/photo-1600271886742-f049cd451bba?w=500',
},
];
export default function App() {
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>注文アプリ</Text>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scroll: {
padding: 24,
},
});商品画像を変えてみたい場合
今回の商品画像はUnsplashの画像を使用しています。
商品ごとの
imageのURLを変更すると、表示する画像を変更できます。まずは授業では、用意されているURLのまま進めてみましょう。
STEP 3:メニューを画面に表示しよう
商品データを用意しただけでは、まだ画面には表示されません。
そこで MENU.map を使って、商品を1つずつ画面に表示します。
そこで MENU.map を使って、商品を1つずつ画面に表示します。
import React from 'react';
import {
SafeAreaView,
View,
Text,
Image,
Pressable,
ScrollView,
StyleSheet,
} from 'react-native';
// 商品データをこのファイル内で管理する
const MENU = [
{
id: 1,
name: 'ハンバーガー',
price: 650,
image: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=500',
},
{
id: 2,
name: 'チーズバーガー',
price: 750,
image: 'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=500',
},
{
id: 3,
name: 'フライドポテト',
price: 350,
image: 'https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=500',
},
{
id: 4,
name: 'チキンナゲット',
price: 400,
image: 'https://images.unsplash.com/photo-1562967916-eb82221dfb92?w=500',
},
{
id: 5,
name: 'コーラ',
price: 250,
image: 'https://images.unsplash.com/photo-1554866585-cd94860890b7?w=500',
},
{
id: 6,
name: 'オレンジジュース',
price: 250,
image: 'https://images.unsplash.com/photo-1600271886742-f049cd451bba?w=500',
},
];
export default function App() {
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>注文アプリ</Text>
<Text style={styles.sectionTitle}>メニュー</Text>
<View style={styles.menuArea}>
{MENU.map((item) => (
<View key={item.id} style={styles.menuItem}>
<Image
source=
style={styles.itemImage}
/>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円
</Text>
</View>
<Pressable
style={styles.smallButton}
>
<Text style={styles.buttonText}>追加</Text>
</Pressable>
</View>
))}
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scroll: {
padding: 24,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
margin: 8,
},
itemImage: {
width: 90,
height: 90,
margin: 8,
},
itemInfo: {
flex: 1,
padding: 8,
},
smallButton: {
borderWidth: 1,
padding: 18,
alignItems: 'center',
},
});プレビューに商品画像、商品名、価格、「追加」ボタンが表示されれば成功です。
ポイント
MENU.mapは、MENUの中にある商品を1つずつ取り出して画面に表示しています。今は「追加」ボタンを押しても、まだ何も起こりません。
次のステップでボタンを動かします。
STEP 4:「追加」ボタンを動かそう
ここからは、アプリの中に「状態」を持たせます。
まず、useState を使ってカートを用意します。
import React, { useState } from 'react';
import {
SafeAreaView,
View,
Text,
Image,
Pressable,
ScrollView,
StyleSheet,
} from 'react-native';
// 商品データをこのファイル内で管理する
const MENU = [
{
id: 1,
name: 'ハンバーガー',
price: 650,
image: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=500',
},
{
id: 2,
name: 'チーズバーガー',
price: 750,
image: 'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=500',
},
{
id: 3,
name: 'フライドポテト',
price: 350,
image: 'https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=500',
},
{
id: 4,
name: 'チキンナゲット',
price: 400,
image: 'https://images.unsplash.com/photo-1562967916-eb82221dfb92?w=500',
},
{
id: 5,
name: 'コーラ',
price: 250,
image: 'https://images.unsplash.com/photo-1554866585-cd94860890b7?w=500',
},
{
id: 6,
name: 'オレンジジュース',
price: 250,
image: 'https://images.unsplash.com/photo-1600271886742-f049cd451bba?w=500',
},
];
export default function App() {
const [cart, setCart] = useState({});
// 商品をカートに追加する
const addToCart = (item) => {
setCart((current) => ({
...current,
[item.id]: (current[item.id] || 0) + 1,
}));
};
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>注文アプリ</Text>
<Text style={styles.sectionTitle}>メニュー</Text>
<View style={styles.menuArea}>
{MENU.map((item) => (
<View key={item.id} style={styles.menuItem}>
<Image
source=
style={styles.itemImage}
/>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円
</Text>
</View>
<Pressable
style={styles.smallButton}
onPress={() => addToCart(item)}
>
<Text style={styles.buttonText}>追加</Text>
</Pressable>
</View>
))}
</View>
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scroll: {
padding: 24,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
margin: 8,
},
itemImage: {
width: 90,
height: 90,
margin: 8,
},
itemInfo: {
flex: 1,
padding: 8,
},
smallButton: {
borderWidth: 1,
padding: 18,
alignItems: 'center',
},
});プレビューで「追加」を押してみましょう。
この時点では、画面上の変化はありません。
しかし、カートの中には商品が追加されています。
ポイント
useStateは、アプリの中で変化する情報を覚えておくために使います。今回は
cartに「どの商品を何個追加したか」を保存しています。
STEP 5:カートを画面に表示しよう
次は、カートに入っている商品を画面に表示します。
商品の個数も計算して、「カートは空です」または商品一覧を表示できるようにします。
import React, { useState } from 'react';
import {
SafeAreaView,
View,
Text,
Image,
Pressable,
ScrollView,
StyleSheet,
} from 'react-native';
// 商品データをこのファイル内で管理する
const MENU = [
{
id: 1,
name: 'ハンバーガー',
price: 650,
image: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=500',
},
{
id: 2,
name: 'チーズバーガー',
price: 750,
image: 'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=500',
},
{
id: 3,
name: 'フライドポテト',
price: 350,
image: 'https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=500',
},
{
id: 4,
name: 'チキンナゲット',
price: 400,
image: 'https://images.unsplash.com/photo-1562967916-eb82221dfb92?w=500',
},
{
id: 5,
name: 'コーラ',
price: 250,
image: 'https://images.unsplash.com/photo-1554866585-cd94860890b7?w=500',
},
{
id: 6,
name: 'オレンジジュース',
price: 250,
image: 'https://images.unsplash.com/photo-1600271886742-f049cd451bba?w=500',
},
];
export default function App() {
const [cart, setCart] = useState({});
// 商品をカートに追加する
const addToCart = (item) => {
setCart((current) => ({
...current,
[item.id]: (current[item.id] || 0) + 1,
}));
};
// カートの商品数を計算する
const cartCount = Object.values(cart).reduce(
(total, count) => total + count,
0
);
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>注文アプリ</Text>
<Text style={styles.sectionTitle}>メニュー</Text>
<View style={styles.menuArea}>
{MENU.map((item) => (
<View key={item.id} style={styles.menuItem}>
<Image
source=
style={styles.itemImage}
/>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円
</Text>
</View>
<Pressable
style={styles.smallButton}
onPress={() => addToCart(item)}
>
<Text style={styles.buttonText}>追加</Text>
</Pressable>
</View>
))}
</View>
<Text style={styles.sectionTitle}>カート</Text>
{cartCount === 0 ? (
<Text style={styles.emptyText}>
カートは空です。
</Text>
) : (
<View style={styles.cartArea}>
{MENU.filter((item) => cart[item.id]).map((item) => (
<View key={item.id} style={styles.cartItem}>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円 × {cart[item.id]}
</Text>
</View>
</View>
))}
</View>
)}
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scroll: {
padding: 24,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
margin: 8,
},
itemImage: {
width: 90,
height: 90,
margin: 8,
},
itemInfo: {
flex: 1,
padding: 8,
},
smallButton: {
borderWidth: 1,
padding: 18,
alignItems: 'center',
},
cartItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
},
});「追加」を押すと、カートに商品が表示されます。
同じ商品を何回か追加すると、個数も増えます。
STEP 6:商品の数量を変更できるようにしよう
次は、カートの商品を減らせるようにします。
「−」を押すと1個減り、「+」を押すと1個増えます。
「−」を押すと1個減り、「+」を押すと1個増えます。
import React, { useState } from 'react';
import {
SafeAreaView,
View,
Text,
Image,
Pressable,
ScrollView,
StyleSheet,
} from 'react-native';
// 商品データをこのファイル内で管理する
const MENU = [
{
id: 1,
name: 'ハンバーガー',
price: 650,
image: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=500',
},
{
id: 2,
name: 'チーズバーガー',
price: 750,
image: 'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=500',
},
{
id: 3,
name: 'フライドポテト',
price: 350,
image: 'https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=500',
},
{
id: 4,
name: 'チキンナゲット',
price: 400,
image: 'https://images.unsplash.com/photo-1562967916-eb82221dfb92?w=500',
},
{
id: 5,
name: 'コーラ',
price: 250,
image: 'https://images.unsplash.com/photo-1554866585-cd94860890b7?w=500',
},
{
id: 6,
name: 'オレンジジュース',
price: 250,
image: 'https://images.unsplash.com/photo-1600271886742-f049cd451bba?w=500',
},
];
export default function App() {
const [cart, setCart] = useState({});
// 商品をカートに追加する
const addToCart = (item) => {
setCart((current) => ({
...current,
[item.id]: (current[item.id] || 0) + 1,
}));
};
// カートの商品を1個減らす
const removeFromCart = (item) => {
setCart((current) => {
const next = { ...current };
if (next[item.id] > 1) {
next[item.id] -= 1;
} else {
delete next[item.id];
}
return next;
});
};
// カートの商品数を計算する
const cartCount = Object.values(cart).reduce(
(total, count) => total + count,
0
);
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>注文アプリ</Text>
<Text style={styles.sectionTitle}>メニュー</Text>
<View style={styles.menuArea}>
{MENU.map((item) => (
<View key={item.id} style={styles.menuItem}>
<Image
source=
style={styles.itemImage}
/>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円
</Text>
</View>
<Pressable
style={styles.smallButton}
onPress={() => addToCart(item)}
>
<Text style={styles.buttonText}>追加</Text>
</Pressable>
</View>
))}
</View>
<Text style={styles.sectionTitle}>カート</Text>
{cartCount === 0 ? (
<Text style={styles.emptyText}>
カートは空です。
</Text>
) : (
<View style={styles.cartArea}>
{MENU.filter((item) => cart[item.id]).map((item) => (
<View key={item.id} style={styles.cartItem}>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円 × {cart[item.id]}
</Text>
</View>
<View style={styles.quantityArea}>
<Pressable
style={styles.quantityButton}
onPress={() => removeFromCart(item)}
>
<Text style={styles.buttonText}>−</Text>
</Pressable>
<Text style={styles.quantity}>
{cart[item.id]}
</Text>
<Pressable
style={styles.quantityButton}
onPress={() => addToCart(item)}
>
<Text style={styles.buttonText}>+</Text>
</Pressable>
</View>
</View>
))}
</View>
)}
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scroll: {
padding: 24,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
margin: 8,
},
itemImage: {
width: 90,
height: 90,
margin: 8,
},
itemInfo: {
flex: 1,
padding: 8,
},
smallButton: {
borderWidth: 1,
padding: 18,
alignItems: 'center',
},
cartItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
},
quantityArea: {
flexDirection: 'row',
},
quantityButton: {
borderWidth: 1,
padding: 14,
alignItems: 'center',
},
quantity: {
margin: 12,
},
});プレビューで「+」「−」を押して、商品の数量が変わることを確認しましょう。
商品が1個しかない状態で「−」を押すと、その商品がカートからなくなります。
STEP 7:合計金額と注文ボタンを作ろう
次は、カートに入っている商品の合計金額を計算します。
そして、カートの下に合計金額と「注文を確定する」ボタンを表示します。
そして、カートの下に合計金額と「注文を確定する」ボタンを表示します。
import React, { useState } from 'react';
import {
SafeAreaView,
View,
Text,
Image,
Pressable,
ScrollView,
StyleSheet,
} from 'react-native';
// 商品データをこのファイル内で管理する
const MENU = [
{
id: 1,
name: 'ハンバーガー',
price: 650,
image: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=500',
},
{
id: 2,
name: 'チーズバーガー',
price: 750,
image: 'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=500',
},
{
id: 3,
name: 'フライドポテト',
price: 350,
image: 'https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=500',
},
{
id: 4,
name: 'チキンナゲット',
price: 400,
image: 'https://images.unsplash.com/photo-1562967916-eb82221dfb92?w=500',
},
{
id: 5,
name: 'コーラ',
price: 250,
image: 'https://images.unsplash.com/photo-1554866585-cd94860890b7?w=500',
},
{
id: 6,
name: 'オレンジジュース',
price: 250,
image: 'https://images.unsplash.com/photo-1600271886742-f049cd451bba?w=500',
},
];
export default function App() {
const [cart, setCart] = useState({});
// 商品をカートに追加する
const addToCart = (item) => {
setCart((current) => ({
...current,
[item.id]: (current[item.id] || 0) + 1,
}));
};
// カートの商品を1個減らす
const removeFromCart = (item) => {
setCart((current) => {
const next = { ...current };
if (next[item.id] > 1) {
next[item.id] -= 1;
} else {
delete next[item.id];
}
return next;
});
};
// カートの商品数を計算する
const cartCount = Object.values(cart).reduce(
(total, count) => total + count,
0
);
// カートの合計金額を計算する
const totalPrice = MENU.reduce(
(total, item) => total + item.price * (cart[item.id] || 0),
0
);
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>注文アプリ</Text>
<Text style={styles.sectionTitle}>メニュー</Text>
<View style={styles.menuArea}>
{MENU.map((item) => (
<View key={item.id} style={styles.menuItem}>
<Image
source=
style={styles.itemImage}
/>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円
</Text>
</View>
<Pressable
style={styles.smallButton}
onPress={() => addToCart(item)}
>
<Text style={styles.buttonText}>追加</Text>
</Pressable>
</View>
))}
</View>
<Text style={styles.sectionTitle}>カート</Text>
{cartCount === 0 ? (
<Text style={styles.emptyText}>
カートは空です。
</Text>
) : (
<View style={styles.cartArea}>
{MENU.filter((item) => cart[item.id]).map((item) => (
<View key={item.id} style={styles.cartItem}>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円 × {cart[item.id]}
</Text>
</View>
<View style={styles.quantityArea}>
<Pressable
style={styles.quantityButton}
onPress={() => removeFromCart(item)}
>
<Text style={styles.buttonText}>−</Text>
</Pressable>
<Text style={styles.quantity}>
{cart[item.id]}
</Text>
<Pressable
style={styles.quantityButton}
onPress={() => addToCart(item)}
>
<Text style={styles.buttonText}>+</Text>
</Pressable>
</View>
</View>
))}
<View style={styles.totalArea}>
<Text style={styles.totalLabel}>合計</Text>
<Text style={styles.totalPrice}>
{totalPrice.toLocaleString()}円
</Text>
</View>
<Pressable
style={styles.orderButton}
>
<Text style={styles.buttonText}>
注文を確定する
</Text>
</Pressable>
</View>
)}
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scroll: {
padding: 24,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
margin: 8,
},
itemImage: {
width: 90,
height: 90,
margin: 8,
},
itemInfo: {
flex: 1,
padding: 8,
},
smallButton: {
borderWidth: 1,
padding: 18,
alignItems: 'center',
},
cartItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
},
quantityArea: {
flexDirection: 'row',
},
quantityButton: {
borderWidth: 1,
padding: 14,
alignItems: 'center',
},
quantity: {
margin: 12,
},
totalArea: {
flexDirection: 'row',
justifyContent: 'space-between',
margin: 16,
},
orderButton: {
borderWidth: 1,
padding: 16,
alignItems: 'center',
},
});カートに商品を追加して、合計金額が変化することを確認しましょう。
STEP 8:注文完了画面を作ろう
最後に、注文を確定したときの画面を作ります。
「注文を確定する」を押すと、注文完了画面に切り替わります。
「新しい注文をする」を押すと、最初の状態に戻ります。
これで注文アプリの完成です。
import React, { useState } from 'react';
import {
SafeAreaView,
View,
Text,
Image,
Pressable,
ScrollView,
StyleSheet,
} from 'react-native';
// 商品データをこのファイル内で管理する
const MENU = [
{
id: 1,
name: 'ハンバーガー',
price: 650,
image: 'https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=500',
},
{
id: 2,
name: 'チーズバーガー',
price: 750,
image: 'https://images.unsplash.com/photo-1572802419224-296b0aeee0d9?w=500',
},
{
id: 3,
name: 'フライドポテト',
price: 350,
image: 'https://images.unsplash.com/photo-1573080496219-bb080dd4f877?w=500',
},
{
id: 4,
name: 'チキンナゲット',
price: 400,
image: 'https://images.unsplash.com/photo-1562967916-eb82221dfb92?w=500',
},
{
id: 5,
name: 'コーラ',
price: 250,
image: 'https://images.unsplash.com/photo-1554866585-cd94860890b7?w=500',
},
{
id: 6,
name: 'オレンジジュース',
price: 250,
image: 'https://images.unsplash.com/photo-1600271886742-f049cd451bba?w=500',
},
];
export default function App() {
const [cart, setCart] = useState({});
const [ordered, setOrdered] = useState(false);
// 商品をカートに追加する
const addToCart = (item) => {
setCart((current) => ({
...current,
[item.id]: (current[item.id] || 0) + 1,
}));
};
// カートの商品を1個減らす
const removeFromCart = (item) => {
setCart((current) => {
const next = { ...current };
if (next[item.id] > 1) {
next[item.id] -= 1;
} else {
delete next[item.id];
}
return next;
});
};
// カートの商品数を計算する
const cartCount = Object.values(cart).reduce(
(total, count) => total + count,
0
);
// カートの合計金額を計算する
const totalPrice = MENU.reduce(
(total, item) => total + item.price * (cart[item.id] || 0),
0
);
// 注文を確定する
const order = () => {
if (cartCount === 0) {
return;
}
setOrdered(true);
};
// 注文完了画面
if (ordered) {
return (
<SafeAreaView style={styles.container}>
<View style={styles.completeArea}>
<Text style={styles.title}>注文完了</Text>
<Text style={styles.message}>
ご注文ありがとうございます。
</Text>
<Text style={styles.message}>
商品の準備を開始しました。
</Text>
<Pressable
style={styles.button}
onPress={() => {
setCart({});
setOrdered(false);
}}
>
<Text style={styles.buttonText}>
新しい注文をする
</Text>
</Pressable>
</View>
</SafeAreaView>
);
}
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}>注文アプリ</Text>
<Text style={styles.sectionTitle}>メニュー</Text>
<View style={styles.menuArea}>
{MENU.map((item) => (
<View key={item.id} style={styles.menuItem}>
<Image
source=
style={styles.itemImage}
/>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円
</Text>
</View>
<Pressable
style={styles.smallButton}
onPress={() => addToCart(item)}
>
<Text style={styles.buttonText}>追加</Text>
</Pressable>
</View>
))}
</View>
<Text style={styles.sectionTitle}>カート</Text>
{cartCount === 0 ? (
<Text style={styles.emptyText}>
カートは空です。
</Text>
) : (
<View style={styles.cartArea}>
{MENU.filter((item) => cart[item.id]).map((item) => (
<View key={item.id} style={styles.cartItem}>
<View style={styles.itemInfo}>
<Text style={styles.itemName}>
{item.name}
</Text>
<Text style={styles.price}>
{item.price.toLocaleString()}円 × {cart[item.id]}
</Text>
</View>
<View style={styles.quantityArea}>
<Pressable
style={styles.quantityButton}
onPress={() => removeFromCart(item)}
>
<Text style={styles.buttonText}>−</Text>
</Pressable>
<Text style={styles.quantity}>
{cart[item.id]}
</Text>
<Pressable
style={styles.quantityButton}
onPress={() => addToCart(item)}
>
<Text style={styles.buttonText}>+</Text>
</Pressable>
</View>
</View>
))}
<View style={styles.totalArea}>
<Text style={styles.totalLabel}>合計</Text>
<Text style={styles.totalPrice}>
{totalPrice.toLocaleString()}円
</Text>
</View>
<Pressable
style={styles.orderButton}
onPress={order}
>
<Text style={styles.buttonText}>
注文を確定する
</Text>
</Pressable>
</View>
)}
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scroll: {
padding: 24,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
margin: 8,
},
itemImage: {
width: 90,
height: 90,
margin: 8,
},
itemInfo: {
flex: 1,
padding: 8,
},
smallButton: {
borderWidth: 1,
padding: 18,
alignItems: 'center',
},
cartItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
padding: 16,
},
quantityArea: {
flexDirection: 'row',
},
quantityButton: {
borderWidth: 1,
padding: 14,
alignItems: 'center',
},
quantity: {
margin: 12,
},
totalArea: {
flexDirection: 'row',
justifyContent: 'space-between',
margin: 16,
},
orderButton: {
borderWidth: 1,
padding: 16,
alignItems: 'center',
},
completeArea: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
button: {
borderWidth: 1,
padding: 16,
alignItems: 'center',
},
});「追加」→「+」「−」→「注文を確定する」の順に操作してみましょう。
「注文完了」と表示されれば完成です。
完成!
今回は、商品データ、画面表示、ボタン操作、カート、数量変更、合計金額、注文完了画面までを自分で作りました。
プログラムをすべて理解できなくても大丈夫です。
「ボタンを押したら画面が変わる」「データが変わると画面も変わる」という、スマートフォンアプリ開発の基本的な仕組みを体験できたことがポイントです。
STEP 9:デザインを変更してみよう
ここまでで注文アプリが完成しました。
最後は、アプリの見た目を変更するために使う「スタイル」を学びます。
React Nativeでは、StyleSheet.create の中にデザインを定義します。
たとえば、文字の大きさを変更したり、背景色を変更したり、余白を変更したりできます。
まずは、よく使うスタイルを一覧で確認してみましょう。
スタイル | 何を変更する? | 記述サンプル |
|---|---|---|
| 文字色 |
|
| 文字サイズ |
|
| 文字の太さ |
|
| 文字の位置 |
|
| 背景色 |
|
| 横幅 |
|
| 高さ |
|
| 内側の余白 |
|
| 上側の内側余白 |
|
| 下側の内側余白 |
|
| 左側の内側余白 |
|
| 右側の内側余白 |
|
| 外側の余白 |
|
| 上側の外側余白 |
|
| 下側の外側余白 |
|
| 左側の外側余白 |
|
| 右側の外側余白 |
|
| 枠線の太さ |
|
| 枠線の色 |
|
| 角の丸み |
|
| 透明度 |
|
| 利用できるスペースの割合 |
|
| 子要素の並び方向 |
|
| 子要素の縦方向・横方向の配置 |
|
| 子要素の反対方向の配置 |
|
ポイント
数値を指定するスタイルでは、基本的に
pxなどの単位は付けません。たとえば、
fontSize: 24は「文字サイズ24」です。
よく使う色の指定方法
色は、名前で指定する方法と、カラーコードで指定する方法があります。
色 | 記述サンプル |
|---|---|
赤 |
|
青 |
|
緑 |
|
白 |
|
黒 |
|
グレー |
|
カラーコード |
|
カラーコードを使うと、より細かく色を指定できます。
例:
backgroundColor: '#f5f5f5'
配置を変更するスタイル
特にアプリのデザインでよく使うのが、flexDirection、justifyContent、alignItems です。
横方向に並べる:
flexDirection: 'row'縦方向に並べる:
flexDirection: 'column'中央に配置する:
justifyContent: 'center'両端に配置する:
justifyContent: 'space-between'横方向の中央に配置する:
alignItems: 'center'
スタイルを組み合わせてみよう
1つのスタイルに複数の指定を書くこともできます。
title: { fontSize: 28, fontWeight: 'bold', textAlign: 'center' }
このようにすると、文字サイズ・太さ・位置をまとめて変更できます。
スタイル一覧を実際に確認してみよう
ここからは、今回作った注文アプリとは関係のない、スタイルを確認するためだけのサンプルアプリを使います。
次のソースをExpo Snackに貼り付けて、プレビューを確認してみましょう。
import React from 'react';
import {
SafeAreaView,
View,
Text,
StyleSheet,
} from 'react-native';
export default function App() {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>スタイル一覧</Text>
<View style={styles.card}>
<Text style={styles.redText}>color</Text>
<Text style={styles.largeText}>fontSize</Text>
<Text style={styles.boldText}>fontWeight</Text>
<Text style={styles.centerText}>textAlign</Text>
<View style={styles.backgroundBox}>
<Text>backgroundColor</Text>
</View>
<View style={styles.sizeBox}>
<Text>width / height</Text>
</View>
<View style={styles.paddingBox}>
<Text>padding</Text>
</View>
<View style={styles.marginBox}>
<Text>margin</Text>
</View>
<View style={styles.borderBox}>
<Text>borderWidth / borderColor</Text>
</View>
<View style={styles.radiusBox}>
<Text>borderRadius</Text>
</View>
<View style={styles.row}>
<Text>flexDirection</Text>
<Text>row</Text>
</View>
<View style={styles.centerBox}>
<Text>justifyContent / alignItems</Text>
</View>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
title: {
fontSize: 28,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 20,
},
card: {
padding: 20,
backgroundColor: '#f5f5f5',
},
redText: {
color: 'red',
},
largeText: {
fontSize: 24,
},
boldText: {
fontWeight: 'bold',
},
centerText: {
textAlign: 'center',
},
backgroundBox: {
backgroundColor: 'skyblue',
padding: 10,
margin: 5,
},
sizeBox: {
width: 200,
height: 50,
backgroundColor: 'lightgray',
padding: 10,
margin: 5,
},
paddingBox: {
padding: 20,
backgroundColor: 'white',
margin: 5,
},
marginBox: {
margin: 20,
backgroundColor: 'white',
padding: 10,
},
borderBox: {
borderWidth: 2,
borderColor: 'gray',
padding: 10,
margin: 5,
},
radiusBox: {
borderWidth: 1,
borderRadius: 15,
padding: 10,
margin: 5,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 10,
margin: 5,
backgroundColor: 'white',
},
centerBox: {
justifyContent: 'center',
alignItems: 'center',
height: 60,
margin: 5,
backgroundColor: 'white',
},
});プレビューを見ながら、それぞれのスタイルが画面のどこに影響しているのか確認してみましょう。
特に、数字を変更すると見た目がどう変わるのかを試してみると、スタイルの仕組みが分かりやすくなります。
チャレンジ
上のサンプルで、次の値を自由に変更してみましょう。
fontSize
backgroundColor
padding
margin
borderWidth
borderRadius
flexDirection
justifyContent
alignItems
数字や色を変更するだけでも、アプリの印象が大きく変わります。
今回作った注文アプリに戻って、同じようにスタイルを変更してみるのもおすすめです。

・本講座の制作
CIT経営開発事務所
・監修
CIT経営開発事務所 代表
井上 隆寛(いのうえ・たかひろ)
事業コンサルタント
AI開発エンジニア
行政書士R6合格者未登録
大手システム開発会社にてフルスタックSE兼Webデザイナーとして従事。2021年にコンサルタントとして独立し、企業に対するITコンサルティング・ソリューション導入支援事業を開始。2023年にはイベント企画・運営事業を新たに展開、2024年には行政書士試験に合格。現在はAIコンサルティング、システム開発、エンターテイメントの3事業を柱に、企業の技術顧問や講師としてICT教育やプログラミング・マーケティング授業も手がける。
公開中のリスキリング体験授業・学習教材
リスキリングに役立つ学習教材や体験授業を公開しています。ご自身の学習にお役立てください。
データサイエンス入門 | 統計基礎 回帰分析編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
データサイエンス入門 | 統計基礎 確率分布編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
iphoneアプリを作ろう!クラッシュパズル編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
iphoneアプリを作ろう!飲食店の注文アプリ編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
iphoneアプリを作ろう!カレンダー編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
法律入門 | 民法基礎 総則編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
Python 3 エンジニア認定基礎試験 | 基礎講座 初級編 前編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
Python 3 エンジニア認定基礎試験 | 基礎講座 初級編 中編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
ディフェンスゲームプログラミング
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
RobloxでAPIと非同期処理を学ぼう プログラ
ミング
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
物理演算ゲーム プログラミング
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
プログラミング体験 | マインクラフト
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
データサイエンス統計 | 統計調査 統計法規
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
データサイエンス統計 | 統計調査 企画・設計
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
プログラミング体験授業 | カレンダー制作
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
Webデザイン体験授業 | LP制作体験
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
Webデザイン入門 | 自己紹介サイト制作体験
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
データサイエンス入門 | 統計基礎
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
データサイエンス入門 | 統計基礎 シグマ編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
データサイエンス入門 | 統計基礎 分散編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
データサイエンス入門 | 統計基礎 相関編
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
Unityゲームプログラミング体験
●開催:オンラインまたはオフライン
●会場:福岡市 NPO・ボランティア交流センター(福岡県福岡市中央区今泉1丁目19−22 西鉄天神クラス 4階)
2015年
創業
120名
従業員数
2.3M
月間UU
1,800戸
管理戸数
