Reskilling ❘ Programming

iphoneアプリを作ろう!飲食店の注文アプリ編

#リスキリング #レジリエンス #ICT教育 #ITスキル #プログラミング

iphoneアプリを作ろう!
飲食店の注文アプリ編

スマホから注文できるモバイルオーダーを使ったことがありますか?

席に座ったまま、あるいはお店から離れていても注文ができる、とても便利なアプリです。

タブレットに入っているタイプも多いですね。

今回は実際に飲食店の注文アプリを作りながら、プログラミングを学んでみましょう!

  • 2015年

    創業

  • 120名

    従業員数

  • 2.3M

    月間UU

  • 1,800戸

    管理戸数

注文アプリを作ってみよう

今回は、スマートフォンで使える「注文アプリ」を作ります。

メニューを見て商品をカートに追加し、数量を変更して、最後に注文を確定するところまで作ります。

プログラミングが初めてでも進められるように、1ステップずつアプリの画面を確認しながら作っていきます。

はじめに:Expo Snackを開こう

Expo Snackを開いて、React Nativeのアプリを作成できる状態にします。

今回のアプリは、基本的に App.js の内容を書き換えながら作っていきます。

画面の右側に表示されるプレビューを確認しながら進めましょう。

うまく動かなくなった場合は、そのステップに掲載されているソースをApp.jsに丸ごと貼り付けてOK

ですが、理解を深めるためにも、ぜひできるだけ自分の力で完成させていきましょう!

STEP 1:アプリの画面を作ろう

まずは、スマートフォンの画面に「注文アプリ」と表示してみましょう。

今回使う部品を読み込み、アプリの基本となる画面を作ります。

  1. App.js の内容をすべて削除します。

  2. 次のソースをすべてコピーして貼り付けます。

  3. プレビューに「注文アプリ」と表示されることを確認します。

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 の中にデザインを定義します。

たとえば、文字の大きさを変更したり、背景色を変更したり、余白を変更したりできます。

まずは、よく使うスタイルを一覧で確認してみましょう。

スタイル

何を変更する?

記述サンプル

color

文字色

color: 'red'

fontSize

文字サイズ

fontSize: 24

fontWeight

文字の太さ

fontWeight: 'bold'

textAlign

文字の位置

textAlign: 'center'

backgroundColor

背景色

backgroundColor: 'skyblue'

width

横幅

width: 200

height

高さ

height: 100

padding

内側の余白

padding: 20

paddingTop

上側の内側余白

paddingTop: 20

paddingBottom

下側の内側余白

paddingBottom: 20

paddingLeft

左側の内側余白

paddingLeft: 20

paddingRight

右側の内側余白

paddingRight: 20

margin

外側の余白

margin: 20

marginTop

上側の外側余白

marginTop: 20

marginBottom

下側の外側余白

marginBottom: 20

marginLeft

左側の外側余白

marginLeft: 20

marginRight

右側の外側余白

marginRight: 20

borderWidth

枠線の太さ

borderWidth: 1

borderColor

枠線の色

borderColor: 'gray'

borderRadius

角の丸み

borderRadius: 10

opacity

透明度

opacity: 0.5

flex

利用できるスペースの割合

flex: 1

flexDirection

子要素の並び方向

flexDirection: 'row'

justifyContent

子要素の縦方向・横方向の配置

justifyContent: 'center'

alignItems

子要素の反対方向の配置

alignItems: 'center'

ポイント

数値を指定するスタイルでは、基本的に px などの単位は付けません。

たとえば、fontSize: 24 は「文字サイズ24」です。

よく使う色の指定方法

色は、名前で指定する方法と、カラーコードで指定する方法があります。

色

記述サンプル

赤

color: 'red'

青

color: 'blue'

緑

color: 'green'

白

color: 'white'

黒

color: 'black'

グレー

color: 'gray'

カラーコード

color: '#ff6600'

カラーコードを使うと、より細かく色を指定できます。

例: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教育やプログラミング・マーケティング授業も手がける。

公開中のリスキリング体験授業・学習教材

リスキリングに役立つ学習教材や体験授業を公開しています。ご自身の学習にお役立てください。

  • 2015年

    創業

  • 120名

    従業員数

  • 2.3M

    月間UU

  • 1,800戸

    管理戸数