実践: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だけでなく、あらゆる開発に共通します。段階的に作ることで、確実に動くアプリが完成します!