React Native - 第3章:アプリ開発のプロセス

React Native - アプリ開発のプロセス

設計から実装まで、実際のアプリ開発の流れを学ぼう

実践:Todoリストアプリを作ろう

これまで学んだ知識を使って、実際にアプリを作る流れを学びます。

第2章では「各機能の使い方」を学びました。この章では「実際の作り方」を学びます。


アプリ開発の基本フロー

いきなりコードを書き始めてはいけません。プロの開発者は以下の順序で進めます。

1. 📋 要件定義  →  何を作るか決める
2. 🎨 設計      →  どう作るか考える
3. 💻 実装      →  コードを書く
4. 🧪 テスト    →  動作確認
5. 🔧 改善      →  より良くする

なぜこの順序なのか?

❌ いきなりコードを書くと:

  • 途中で「これどうするんだっけ?」となる
  • 後から大幅な修正が必要になる
  • バグが多くなる
  • 完成まで時間がかかる

✅ 順序を守ると:

  • 迷わず進められる
  • 最小限の修正で済む
  • バグが少ない
  • 効率的に開発できる

ステップ1: 要件定義

「何を作るか」を明確にします。

作るもの:シンプルなTodoリストアプリ

必須機能(Must Have)

まず最低限必要な機能を決めます。

  • ✏️ Todoを入力できる - ユーザーがテキストを入力
  • Todoを追加できる - 入力したTodoをリストに追加、追加後、入力欄はクリア
  • 📋 Todoの一覧を見られる - すべてのTodoを表示、見やすいレイアウト
  • 🗑️ Todoを削除できる - 不要なTodoを削除

開発の鉄則

最初から完璧を目指さない!

まず「動くもの」を作る
 ↓
少しずつ改善する

ステップ2: 状態設計

「どんなデータが必要か」を考えます。

必要な状態を洗い出す

Todoアプリに必要な状態:

  • 現在入力中のテキスト - 入力欄の値を保持、型: string
  • Todoのリスト - すべてのTodoを保持、型: string[]

最終的な状態設計

// 状態1: 入力中のテキスト
const [todo, setTodo] = useState<string>('');

// 状態2: Todoのリスト
const [todos, setTodos] = useState<string[]>([]);

たった2つの状態でTodoアプリができます!


ステップ3: UI設計

「画面のレイアウト」を考えます。

画面構成

┌─────────────────────┐
│  Todoリスト              │ ← タイトル
├─────────────────────┤
│ [入力欄]  [追加ボタン]   │ ← 入力エリア
├─────────────────────┤
│ ┌─────────────────┐ │
│ │ 牛乳を買う    [削除] │ │
│ └─────────────────┘ │
│ ┌─────────────────┐ │ ← Todoリスト
│ │ レポート書く  [削除] │ │
│ └─────────────────┘ │
│ ┌─────────────────┐ │
│ │ 運動する      [削除] │ │
│ └─────────────────┘ │
└─────────────────────┘

使用するコンポーネント

  • タイトル → Text
  • 全体の容器 → View
  • 入力欄 → TextInput
  • 追加ボタン → Button
  • Todoリスト → FlatList
  • 削除ボタン → TouchableOpacity

ステップ4: 実装の順序を決める

「どの順番で作るか」を計画します。

実装ステップ(全5段階)

  • 基本構造を作る - タイトルを表示
  • 入力UIを追加 - 入力欄とボタンを配置
  • 追加機能を実装 - Todoを追加できるようにする
  • リスト表示を実装 - Todoを画面に表示
  • 削除機能を実装 - Todoを削除できるようにする

なぜこの順番?

簡単なものから順に作る
 ↓
各ステップで動作確認
 ↓
問題を早期発見
 ↓
効率的に開発

ステップ5: 段階的に実装する

それでは、実際にコードを書いていきましょう。各ステップで少しずつ機能を追加します。

実装ステップ1: 基本構造を作る

目標: タイトルを表示する

import { View, Text, StyleSheet } from 'react-native';

export default function TodoApp() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Todoリスト</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
  },
});

確認: タイトルが表示されているか?

実装ステップ2: 入力UIを追加

目標: 入力欄とボタンを配置する

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

export default function TodoApp() {
  // 状態1: 入力中のテキスト
  const [todo, setTodo] = useState('');

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Todoリスト</Text>

      {/* 入力エリア */}
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={todo}
          onChangeText={setTodo}
          placeholder="Todoを入力してください"
        />
        <Button title="追加" onPress={() => {}} />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
  },
  inputContainer: {
    flexDirection: 'row',
    marginBottom: 20,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    marginRight: 10,
    borderRadius: 5,
    backgroundColor: 'white',
  },
});

確認: 入力欄に文字を入力できるか?

ポイント:

  • flexDirection: 'row' で入力欄とボタンを横並びに
  • flex: 1 で入力欄が残りのスペースを埋める

実装ステップ3: 追加機能を実装

目標: ボタンを押したらTodoが追加される

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

export default function TodoApp() {
  const [todo, setTodo] = useState('');
  // 状態2: Todoのリスト
  const [todos, setTodos] = useState<string[]>([]);

  // Todoを追加する関数
  const addTodo = () => {
    if (todo.trim()) {  // 空白でないかチェック
      setTodos([...todos, todo]);  // 新しい配列を作る
      setTodo('');  // 入力欄をクリア
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Todoリスト</Text>

      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={todo}
          onChangeText={setTodo}
          placeholder="Todoを入力してください"
        />
        <Button title="追加" onPress={addTodo} />
      </View>

      {/* デバッグ用:todosの中身を表示 */}
      <Text>Todoの数: {todos.length}</Text>
    </View>
  );
}

// styles は省略(変更なし)

確認: ボタンを押すと「Todoの数」が増えるか?

重要なポイント:

  • todo.trim() で前後の空白を削除してチェック
  • [...todos, todo] で新しい配列を作る(直接変更しない)
  • 追加後に setTodo('') で入力欄をクリア

実装ステップ4: リスト表示を実装

目標: Todoを画面に表示する

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

export default function TodoApp() {
  const [todo, setTodo] = useState('');
  const [todos, setTodos] = useState<string[]>([]);

  const addTodo = () => {
    if (todo.trim()) {
      setTodos([...todos, todo]);
      setTodo('');
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Todoリスト</Text>

      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={todo}
          onChangeText={setTodo}
          placeholder="Todoを入力してください"
        />
        <Button title="追加" onPress={addTodo} />
      </View>

      {/* Todoリスト */}
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <View style={styles.todoItem}>
            <Text style={styles.todoText}>{item}</Text>
          </View>
        )}
        keyExtractor={(item, index) => index.toString()}
        ListEmptyComponent={() => (
          <Text style={styles.emptyText}>Todoを追加してください</Text>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
  },
  inputContainer: {
    flexDirection: 'row',
    marginBottom: 20,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    marginRight: 10,
    borderRadius: 5,
    backgroundColor: 'white',
  },
  todoItem: {
    backgroundColor: 'white',
    padding: 15,
    borderRadius: 5,
    marginBottom: 10,
  },
  todoText: {
    fontSize: 16,
  },
  emptyText: {
    textAlign: 'center',
    color: '#999',
    marginTop: 20,
  },
});

確認: Todoを追加すると、リストに表示されるか?

ポイント:

  • ListEmptyComponent でリストが空の時のメッセージを表示
  • keyExtractor で各アイテムにユニークなキーを設定

実装ステップ5: 削除機能を実装

目標: Todoを削除できるようにする(完成!)

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

export default function TodoApp() {
  const [todo, setTodo] = useState('');
  const [todos, setTodos] = useState<string[]>([]);

  const addTodo = () => {
    if (todo.trim()) {
      setTodos([...todos, todo]);
      setTodo('');
    }
  };

  // Todoを削除する関数
  const removeTodo = (index: number) => {
    setTodos(todos.filter((_, i) => i !== index));
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Todoリスト</Text>

      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={todo}
          onChangeText={setTodo}
          placeholder="Todoを入力してください"
        />
        <Button title="追加" onPress={addTodo} />
      </View>

      <FlatList
        data={todos}
        renderItem={({ item, index }) => (
          <View style={styles.todoItem}>
            <Text style={styles.todoText}>{item}</Text>
            <TouchableOpacity 
              onPress={() => removeTodo(index)}
              style={styles.deleteButton}
            >
              <Text style={styles.deleteButtonText}>削除</Text>
            </TouchableOpacity>
          </View>
        )}
        keyExtractor={(item, index) => index.toString()}
        ListEmptyComponent={() => (
          <Text style={styles.emptyText}>Todoを追加してください</Text>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
  },
  inputContainer: {
    flexDirection: 'row',
    marginBottom: 20,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    marginRight: 10,
    borderRadius: 5,
    backgroundColor: 'white',
  },
  todoItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    backgroundColor: 'white',
    padding: 15,
    borderRadius: 5,
    marginBottom: 10,
  },
  todoText: {
    fontSize: 16,
    flex: 1,
  },
  deleteButton: {
    backgroundColor: '#ff5252',
    paddingHorizontal: 15,
    paddingVertical: 8,
    borderRadius: 5,
  },
  deleteButtonText: {
    color: 'white',
    fontWeight: 'bold',
  },
  emptyText: {
    textAlign: 'center',
    color: '#999',
    marginTop: 20,
  },
});

確認: 削除ボタンを押すと、そのTodoが消えるか?

重要なポイント:

  • filter((_, i) => i !== index) で指定したインデックス以外を残す
  • () => removeTodo(index) でアロー関数を使って引数を渡す
  • flexDirection: 'row' でテキストと削除ボタンを横並びに

完成版コード(全体)

最終的な完成版コードです。このコードをそのまま使えば、Todoアプリが動きます。

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

export default function TodoApp() {
  // 状態管理
  const [todo, setTodo] = useState('');
  const [todos, setTodos] = useState<string[]>([]);

  // Todoを追加
  const addTodo = () => {
    if (todo.trim()) {
      setTodos([...todos, todo]);
      setTodo('');
    }
  };

  // Todoを削除
  const removeTodo = (index: number) => {
    setTodos(todos.filter((_, i) => i !== index));
  };

  return (
    <View style={styles.container}>
      {/* タイトル */}
      <Text style={styles.title}>Todoリスト</Text>

      {/* 入力エリア */}
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={todo}
          onChangeText={setTodo}
          placeholder="Todoを入力してください"
        />
        <Button title="追加" onPress={addTodo} />
      </View>

      {/* Todoリスト */}
      <FlatList
        data={todos}
        renderItem={({ item, index }) => (
          <View style={styles.todoItem}>
            <Text style={styles.todoText}>{item}</Text>
            <TouchableOpacity 
              onPress={() => removeTodo(index)}
              style={styles.deleteButton}
            >
              <Text style={styles.deleteButtonText}>削除</Text>
            </TouchableOpacity>
          </View>
        )}
        keyExtractor={(item, index) => index.toString()}
        ListEmptyComponent={() => (
          <Text style={styles.emptyText}>Todoを追加してください</Text>
        )}
      />
    </View>
  );
}

// スタイル定義
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    color: '#333',
  },
  inputContainer: {
    flexDirection: 'row',
    marginBottom: 20,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    marginRight: 10,
    borderRadius: 5,
    backgroundColor: 'white',
  },
  todoItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    backgroundColor: 'white',
    padding: 15,
    borderRadius: 5,
    marginBottom: 10,
  },
  todoText: {
    fontSize: 16,
    flex: 1,
  },
  deleteButton: {
    backgroundColor: '#ff5252',
    paddingHorizontal: 15,
    paddingVertical: 8,
    borderRadius: 5,
  },
  deleteButtonText: {
    color: 'white',
    fontWeight: 'bold',
  },
  emptyText: {
    textAlign: 'center',
    color: '#999',
    marginTop: 20,
  },
});

コードの構成:

  • インポート(必要なコンポーネントを読み込む)
  • 状態定義(useState × 2)
  • 関数定義(addTodo、removeTodo)
  • UI部分(JSX)
  • スタイル定義(StyleSheet.create)

振り返り - 学んだポイント

1. 開発プロセスの重要性

いきなりコードを書かない:

要件定義 → 設計 → 実装 → テスト → 改善

このプロセスを守ることで:
✅ 効率的に開発できる
✅ バグが少なくなる
✅ 後から修正しやすい

2. 状態設計の考え方

必要最小限から始める:

  • Todoアプリは、たった2つの状態で実現できた
  • 複雑に考えすぎず、シンプルに設計する
  • 必要になったら追加すればいい

3. 段階的な実装

小さく作って確認:

ステップ1: タイトル表示 → 動作確認
ステップ2: 入力UI追加 → 動作確認
ステップ3: 追加機能 → 動作確認
ステップ4: リスト表示 → 動作確認
ステップ5: 削除機能 → 動作確認

各ステップで確認することで、問題を早期発見

4. Reactの重要なルール

配列や状態は直接変更しない:

// ❌ ダメ
todos.push(todo);

// ✅ OK
setTodos([...todos, todo]);

新しい配列・オブジェクトを作る!

5. よく使う技術パターン

配列操作:

// 追加
[...todos, newTodo]

// 削除
todos.filter((_, i) => i !== index)

// 更新
todos.map((item, i) => i === index ? newItem : item)

条件チェック:

// 空白チェック
if (todo.trim()) { }

// 存在チェック
if (todos.length > 0) { }

イベントハンドラ:

// 引数なし
onPress={addTodo}

// 引数あり
onPress={() => removeTodo(index)}

6. レイアウトテクニック

Flexboxの活用:

// 横並び
flexDirection: 'row'

// スペースを埋める
flex: 1

// 両端に配置
justifyContent: 'space-between'

// 中央揃え
alignItems: 'center'

よくある失敗例

失敗例1: 配列を直接変更してしまう

❌ 間違ったコード

const addTodo = () => {
  todos.push(todo);  // ダメ!
  setTodo('');
};

何が起きるか: ボタンを押しても画面が更新されない

✅ 正しいコード

const addTodo = () => {
  setTodos([...todos, todo]);  // 新しい配列を作る
  setTodo('');
};

覚え方:

Reactの状態は「直接変更しない」新しく作る

失敗例2: keyExtractorを忘れる

❌ 間違ったコード

<FlatList
  data={todos}
  renderItem={({ item }) => <Text>{item}</Text>}
  // keyExtractor がない!
/>

何が起きるか: 黄色い警告が出る

✅ 正しいコード

<FlatList
  data={todos}
  renderItem={({ item }) => <Text>{item}</Text>}
  keyExtractor={(item, index) => index.toString()}
/>

失敗例3: イベントハンドラをすぐ実行してしまう

❌ 間違ったコード

<TouchableOpacity onPress={removeTodo(index)}>
  <Text>削除</Text>
</TouchableOpacity>
// レンダリング時に実行されてしまう!

✅ 正しいコード

<TouchableOpacity onPress={() => removeTodo(index)}>
  <Text>削除</Text>
</TouchableOpacity>
// アロー関数で包む

この章で学んだこと

アプリ開発のプロセス

  • 要件定義 → 何を作るか
  • 設計 → どう作るか
  • 実装 → コードを書く
  • テスト → 確認する
  • 改善 → より良くする

設計のポイント

  • 状態設計: 必要最小限から始める
  • UI設計: 使うコンポーネントを決める
  • 実装計画: 小さなステップに分ける

実装で学んだ技術

  • 配列操作: スプレッド構文、filter
  • 条件チェック: trim()、存在確認
  • イベントハンドラ: アロー関数での引数渡し
  • レイアウト: Flexboxの活用

開発の鉄則

  • ✅ いきなりコードを書かない
  • ✅ 小さく作って確認
  • ✅ 最初から完璧を目指さない
  • ✅ 必須機能から作る
  • ✅ 状態は直接変更しない

この考え方は、React Nativeだけでなく、あらゆる開発に共通します。段階的に作ることで、確実に動くアプリが完成します!