📄

React NativeでExpoを使用してToDoアプリを作成する!

2024/08/09に公開

概要

日本語の情報が少ないと思ったので作成してみました。
序盤は初心者向けに説明しているのでわかる人は中盤くらいから見る事をお勧めします。
このガイドでは、React NativeとExpoを使って簡単なToDoアプリを作成する方法を初心者向けに解説します。React Nativeは、iOSとAndroidの両方のプラットフォームで動作するアプリを作るためのフレームワークであり、Expoはその開発をさらに簡単にするツールです。アプリの開発にあたり、特別な設定や複雑なコーディングは必要なく、誰でも簡単に実践できるので一緒に作成してみましょう!!

なぜExpoを使うのか?

ExpoはReact Nativeアプリの開発をスムーズにするツールセットです。これを使うことで、以下のような利点があります:

  • 簡単なセットアップ: Expoは複雑な設定やネイティブコードの記述なしで、すぐにReact Nativeアプリの開発を始められます。
  • 即時プレビュー: Expo Goアプリを使って、スマートフォンでリアルタイムにアプリの変更を確認できます。
  • 豊富なライブラリ: Expoには、多くの便利なライブラリやAPIが組み込まれており、機能追加が簡単です。

開発に必要なもの

  • Node.js: React NativeとExpoのツールを動作させるために必要です。
  • テキストエディタ: VSCodeなど、JavaScriptのコードを書くためのエディタが必要です。
  • スマートフォンまたはエミュレーター: 実際にアプリを確認するために使用します。
  • Expo Go : AppStore、GooglePlayからインストールが必要です。

実装

アプリケーションの作成から起動まで

コマンドラインにコードを入力して作成しましょう。

npx create-expo-app TodoApp --template

ファイルに移動(Change Directory)

cd TodoApp

まだ何も書き足していませんが一度動かしてみます。
以下のコマンドで開発サーバーを起動します。

npx expo start 

今回はQRコードをIOSのExpo Goで読み取っています。

上のように表示されたら成功です!

App.jsをカスタマイズ

今回のサンプルコード
import React, { useState } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet, TouchableOpacity } from 'react-native';

const App = () => {
  const [text, setText] = useState(''); // 現在の入力テキスト
  const [editingId, setEditingId] = useState(null); // 編集中のToDoのID
  const [todos, setTodos] = useState([]); // ToDoアイテムの配列

  // ToDoを追加または更新する関数
  const handleSubmit = () => {
    if (text.trim()) { // テキストが空でない場合のみ
      if (editingId) {
        // 編集中のToDoを更新する
        const updatedTodos = todos.map(todo => {
          if (todo.id === editingId) {
            // 編集中のToDoを更新
            return { ...todo, text };
          }
          // 編集中でないToDoはそのまま返す
          return todo;
        });

        // 更新されたToDoリストを状態にセット
        setTodos(updatedTodos);
        setEditingId(null); // 編集IDをリセット
      } else {
        // 新しいToDoを追加する
        const newTodo = { id: Date.now().toString(), text };
        setTodos([...todos, newTodo]);
      }
      setText(''); // テキスト入力をクリア
    }
  };

  // 指定したIDのToDoを編集する関数
  const handleEdit = (id) => {
    const todoToEdit = todos.find(todo => todo.id === id);
    if (todoToEdit) {
      setText(todoToEdit.text); // テキスト入力にToDoの内容をセット
      setEditingId(id); // 編集中のIDを設定
    }
  };

  // 指定したIDのToDoを削除する関数
  const handleRemove = (id) => {
    const filteredTodos = todos.filter(todo => todo.id !== id);
    setTodos(filteredTodos);

    if (editingId === id) {
      setText(''); // 編集中のToDoを削除した場合、テキスト入力をクリア
      setEditingId(null); // 編集IDをリセット
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>ToDoリスト</Text>
      <TextInput
        style={styles.input}
        value={text}
        onChangeText={setText}
        placeholder="新しいToDoを追加または編集"
      />
      <Button title={editingId ? "更新" : "追加"} onPress={handleSubmit} />
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <View style={styles.todoContainer}>
            <Text style={styles.todoText}>{item.text}</Text>
            <View style={styles.buttonsContainer}>
              <TouchableOpacity onPress={() => handleEdit(item.id)}>
                <Text style={styles.editButton}>編集</Text>
              </TouchableOpacity>
              <TouchableOpacity onPress={() => handleRemove(item.id)}>
                <Text style={styles.removeButton}>削除</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}
        keyExtractor={item => item.id}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    marginBottom: 16,
    textAlign: 'center',
  },
  input: {
    height: 40,
    borderColor: '#ddd',
    borderWidth: 1,
    paddingHorizontal: 8,
    marginBottom: 8,
  },
  todoContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 8,
    borderBottomColor: '#ddd',
    borderBottomWidth: 1,
  },
  todoText: {
    fontSize: 18,
  },
  buttonsContainer: {
    flexDirection: 'row',
  },
  editButton: {
    color: 'blue',
    fontSize: 18,
    marginRight: 8,
  },
  removeButton: {
    color: 'red',
    fontSize: 18,
  },
});

export default App;

全文のサンプルコードを上に用意したのでコピーして使用してみてください。
細かい部分について説明していきます。
基本的にはApp関数内に処理を記述していきます。

必要なモジュールのインポート

import React, { useState } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet, TouchableOpacity } from 'react-native';

使用するコンポーネント(View,Text....など)を事前にインポートします。
インポートするのを忘れてエラーが起こることがあるので注意です。
VSCodeを使うと自動で補間してくれたりします。

状態変数を定義(useState)

  const [text, setText] = useState('');
  const [editingId, setEditingId] = useState(null);
  const [todos, setTodos] = useState([]);

useStateについて簡単に説明するとReact の関数コンポーネントで状態を管理するためのフックです。useState(initialValue) を使って、状態変数とその更新関数を定義します。状態が変更されると、コンポーネントが再レンダリングされ、新しい状態が反映されます。今回は入力されている文字、編集中のアイテム、ToDo一覧の配列を状態変数で定義しています。

ToDoの追加または更新

const handleSubmit = () => {
  if (text.trim()) {
    if (editingId) {
      const updatedTodos = todos.map(todo => {
        if (todo.id === editingId) {
          return { ...todo, text };
        }
        return todo;
      });
      setTodos(updatedTodos);
      setEditingId(null);
    } else {
      const newTodo = { id: Date.now().toString(), text };
      setTodos([...todos, newTodo]);
    }
    setText('');
  }
};

handleSubmit はToDoを追加または更新するための関数です。
text.trim() は空白文字を削除したテキストがあるか確認します。
editingId が設定されている場合、つまり編集中の場合は、そのToDoを更新します。
editingId がない場合は、新しいToDoを追加します。新しいToDoには現在の時刻(Date.now())をIDとして使用します。
ToDoを追加または更新後、入力フィールドをクリアします。

編集中のToDoを設定

const handleEdit = (id) => {
  const todoToEdit = todos.find(todo => todo.id === id);
  if (todoToEdit) {
    setText(todoToEdit.text);
    setEditingId(id);
  }
};

handleEdit は、ToDoアイテムを編集するための関数です。
編集するToDoのIDを受け取り、そのIDに一致するToDoを見つけます。
見つかったToDoの内容を入力フィールドにセットし、editingId にそのIDを設定します。

ToDoの削除

const handleRemove = (id) => {
  const filteredTodos = todos.filter(todo => todo.id !== id);
  setTodos(filteredTodos);

  if (editingId === id) {
    setText('');
    setEditingId(null);
  }
};

handleRemove はToDoを削除するための関数です。
削除したいToDoのIDを受け取り、そのID以外のToDoだけを残します。
削除したToDoが現在編集中のものであれば、入力フィールドをクリアし、editingId をリセットします。

UIの構成

ReactNativeではHTMLのような形式で記述していきます。HTMLと似ている部分がほとんどですが独自の
物もあるので公式ドキュメントを見るとよいでしょう。今回は以下のコード内に書いてあるものについて簡単に説明しています。

return (
  <View style={styles.container}>
    <Text style={styles.title}>ToDoリスト</Text>
    <TextInput
      style={styles.input}
      value={text}
      onChangeText={setText}
      placeholder="新しいToDoを追加または編集"
    />
    <Button title={editingId ? "更新" : "追加"} onPress={handleSubmit} />
    <FlatList
      data={todos}
      renderItem={({ item }) => (
        <View style={styles.todoContainer}>
          <Text style={styles.todoText}>{item.text}</Text>
          <View style={styles.buttonsContainer}>
            <TouchableOpacity onPress={() => handleEdit(item.id)}>
              <Text style={styles.editButton}>編集</Text>
            </TouchableOpacity>
            <TouchableOpacity onPress={() => handleRemove(item.id)}>
              <Text style={styles.removeButton}>削除</Text>
            </TouchableOpacity>
          </View>
        </View>
      )}
      keyExtractor={item => item.id}
    />
  </View>
);

1. View コンポーネント

役割: View はアプリのUIを構築するための基本的なコンポーネントで、他のコンポーネントを包み込む「容器」のようなものです。
用途: 複数のUI要素をグループ化し、レイアウトを調整するために使います。例えば、ボタンやテキストフィールドを並べたり、全体のレイアウトを管理したりします。

2. TextInput コンポーネント

役割: TextInput はユーザーがテキストを入力できるフィールドを提供します。
用途: ユーザーからデータを入力させるためのコンポーネントです。たとえば、名前やコメント、ToDoアイテムの内容を入力する場面で使用します。
主なプロパティ:
value: フィールドに表示するテキストの値。
onChangeText: テキストが変更されたときに呼ばれる関数。
placeholder: フィールドに何も入力されていないときに表示されるヒントテキスト。

3. Button コンポーネント

役割: Button はユーザーがタップできるボタンを表示します。
用途: 何かのアクションを実行するためのボタンで、ユーザーが押すと指定された関数が実行されます。ToDoリストでは、新しいToDoの追加や既存のToDoの更新などの操作を実行するために使います。
主なプロパティ:
title: ボタンに表示するテキスト。
onPress: ボタンが押されたときに呼ばれる関数。

4. FlatList コンポーネント

役割: FlatList は長いリストを効率的に表示するためのコンポーネントです。
用途: 大量のデータをリストとして表示するために使います。リストのスクロールやアイテムの表示を効率的に管理します。
主なプロパティ:
data: リストに表示するデータの配列。
renderItem: 各アイテムをどのように表示するかを定義する関数。アイテムごとにUIをカスタマイズできます。
keyExtractor: 各アイテムに一意のキーを提供するための関数。リストのパフォーマンスを向上させます。

5.TouchableOpacity コンポーネント

役割: TouchableOpacity はタップ可能なコンポーネントで、ユーザーがタップすると視覚的なフィードバックを提供します。
用途: ボタンやタップできるアイコンなど、ユーザーが押したときに何かを実行する要素を作るために使います。タップの際に透明度が変わることで、ユーザーにタップしていることを視覚的に示します。
主なプロパティ:
onPress: タップされたときに呼ばれる関数。

レイアウト

今回はあまり触れませんがCSSのような形式で記述します。
今後レイアウトについても書くので今回は省略します!

まとめ

このガイドでは、React NativeとExpoを使ってシンプルなToDoアプリを作成する方法を説明しました。このガイドを基に、自分なりにToDoアプリを拡張してみることをお勧めします。このアプリは実行が終了するとデータが消えてしまうので今後Firebaseなどと組み合わせる方法などについても触れたいと思います。

Discussion