取り組みの背景 — なぜエラーハンドリングが重要なのか
アプリ開発を始めたばかりの頃は、新しい機能を追加することに集中しがちです。しかし、ユーザーにとって本当に大切なのは「アプリが安定して動く」こと。どんなに魅力的な機能があっても、頻繁にクラッシュするアプリは使い続けてもらえません。
Rork を使ったアプリ開発では、AI がコードを自動生成してくれるため、基本的なエラー処理が見落とされることがあります。ここではRork アプリの安定性を高めるためのエラーハンドリングとクラッシュ対策の基本を、初心者の方にもわかりやすく解説します。
この記事を読むことで、以下のスキルが身につきます。
- JavaScript / React Native の基本的なエラーハンドリング手法
- API通信時のエラー処理パターン
- クラッシュを未然に防ぐための設計の考え方
- 問題が起きたときのデバッグ手法
try-catch — エラーを安全にキャッチする基本構文
エラーハンドリングの最も基本的な手法が try-catch です。処理が失敗する可能性のあるコードを try ブロックで囲み、エラーが発生した場合に catch ブロックで安全に処理します。
// Rork アプリでの基本的な try-catch パターン
const fetchUserData = async (userId) => {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
// HTTP ステータスコードのチェック
if (!response.ok) {
throw new Error(`サーバーエラー: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
// エラーの種類に応じた処理
if (error.message.includes('Network request failed')) {
console.warn('ネットワーク接続を確認してください');
} else {
console.error('データ取得に失敗しました:', error.message);
}
return null; // フォールバック値を返す
}
};
// 使用例
// 期待する出力(正常時): ユーザーデータオブジェクト
// 期待する出力(エラー時): null(+ コンソールにエラーメッセージ)ここで大切なのは、catch ブロック内でただエラーを握りつぶすのではなく、ユーザーに適切なフィードバックを返すことです。「何かがうまくいかなかった」ことをユーザーに伝え、次に取るべきアクション(リトライ、ネットワーク確認など)を示しましょう。
Error Boundary — UIのクラッシュを防ぐ安全網
React Native アプリでは、レンダリング中にエラーが発生するとアプリ全体がクラッシュしてしまいます。これを防ぐのが Error Boundary(エラー境界)です。
Error Boundary は、子コンポーネントのレンダリングエラーをキャッチし、クラッシュの代わりにフォールバック UI を表示する仕組みです。
// ErrorBoundary コンポーネントの実装
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
// エラー発生時にフォールバック UI を表示するためステートを更新
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// エラーログをサービスに送信(本番環境で重要)
console.error('Error Boundary caught:', error, errorInfo);
}
handleRetry = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
<View style={styles.container}>
<Text style={styles.title}>問題が発生しました</Text>
<Text style={styles.message}>
申し訳ございません。画面の読み込み中にエラーが発生しました。
</Text>
<TouchableOpacity style={styles.button} onPress={this.handleRetry}>
<Text style={styles.buttonText}>もう一度試す</Text>
</TouchableOpacity>
</View>
);
}
return this.props.children;
}
}
// 期待する動作:
// 子コンポーネントでエラー → フォールバック UI 表示
// 「もう一度試す」タップ → 子コンポーネント再レンダリングError Boundary の配置は戦略的に行いましょう。アプリ全体を1つの Error Boundary で囲むだけでなく、画面ごとや重要なセクションごとに配置することで、エラーの影響範囲を最小限に抑えられます。
ネットワークエラーへの対応 — オフライン対策とリトライ
モバイルアプリでは、ネットワーク接続が不安定になることは日常的に起こります。電波の弱い場所、Wi-Fi の切り替え時、機内モードなど、さまざまな場面でネットワークエラーが発生します。
Rork アプリでネットワーク通信を行う際は、以下の3つのポイントを意識しましょう。
接続状態の確認
import NetInfo from '@react-native-community/netinfo';
// ネットワーク状態を監視するカスタムフック
const useNetworkStatus = () => {
const [isConnected, setIsConnected] = React.useState(true);
React.useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
setIsConnected(state.isConnected);
});
return () => unsubscribe();
}, []);
return isConnected;
};
// 使用例
const MyScreen = () => {
const isConnected = useNetworkStatus();
if (!isConnected) {
return <OfflineBanner message="インターネット接続がありません" />;
}
return <MainContent />;
};タイムアウトの設定
API リクエストにはタイムアウトを設定し、いつまでもレスポンスを待ち続けることがないようにします。
// タイムアウト付き fetch の実装
const fetchWithTimeout = async (url, options = {}, timeout = 10000) => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('リクエストがタイムアウトしました');
}
throw error;
}
};
// 期待する出力(タイムアウト時): Error: リクエストがタイムアウトしましたリトライ機構
一時的なエラーに対しては、自動リトライを実装することでユーザー体験を改善できます。
// 指数バックオフ付きリトライ関数
const fetchWithRetry = async (url, options = {}, maxRetries = 3) => {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetchWithTimeout(url, options);
if (response.ok) return response;
// 5xx エラーはリトライ対象
if (response.status >= 500) {
throw new Error(`Server error: ${response.status}`);
}
// 4xx エラーはリトライしない
return response;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
// 指数バックオフ: 1秒, 2秒, 4秒...
const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
};ネットワークエラー処理の実践的なパターンについては、Rork アプリの起動クラッシュ・白画面 完全解決ガイドでも詳しく解説しています。
入力バリデーション — ユーザー入力によるエラーを防ぐ
ユーザーからの入力は、常に「予期しない値が来る可能性がある」と考えて処理しましょう。フォーム入力、検索クエリ、設定値など、あらゆる入力をバリデーションすることでクラッシュを防げます。
// 入力バリデーションのユーティリティ関数
const validateInput = {
// メールアドレスの形式チェック
isValidEmail: (email) => {
if (!email || typeof email !== 'string') return false;
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return pattern.test(email.trim());
},
// 数値の範囲チェック
isInRange: (value, min, max) => {
const num = Number(value);
return !isNaN(num) && num >= min && num <= max;
},
// 文字列の長さチェック
isValidLength: (text, minLen, maxLen) => {
if (!text || typeof text !== 'string') return false;
const trimmed = text.trim();
return trimmed.length >= minLen && trimmed.length <= maxLen;
},
};
// フォーム送信時の使用例
const handleSubmit = (formData) => {
const errors = [];
if (!validateInput.isValidEmail(formData.email)) {
errors.push('正しいメールアドレスを入力してください');
}
if (!validateInput.isValidLength(formData.name, 1, 50)) {
errors.push('名前は1〜50文字で入力してください');
}
if (errors.length > 0) {
// エラーメッセージを表示
Alert.alert('入力エラー', errors.join('\n'));
return;
}
// バリデーション通過後の処理
submitData(formData);
};バリデーションは「クライアント側」と「サーバー側」の両方で実施するのが鉄則です。クライアント側のバリデーションはユーザー体験の向上のため、サーバー側はセキュリティのためと考えましょう。
デバッグの基本テクニック — 問題を素早く特定する方法
エラーが発生したとき、素早く原因を特定する力はアプリ開発において非常に重要です。Rork アプリでのデバッグに役立つ基本テクニックを紹介します。
console メソッドの使い分け
// console.log — 一般的な情報出力
console.log('ユーザーデータ:', userData);
// console.warn — 警告(黄色表示)
console.warn('APIレスポンスに予期しないフィールドがあります');
// console.error — エラー(赤色表示)
console.error('認証トークンの取得に失敗:', error);
// console.table — オブジェクト/配列を表形式で表示
console.table([
{ name: 'API呼び出し', duration: '120ms', status: 'success' },
{ name: 'DB書き込み', duration: '45ms', status: 'success' },
]);
// console.time / timeEnd — 処理時間の計測
console.time('データ取得');
const data = await fetchUserData();
console.timeEnd('データ取得');
// 期待する出力: データ取得: 235.42ms条件付きデバッグログ
本番環境ではデバッグログを出力しないよう、環境に応じた制御を入れましょう。
// デバッグユーティリティ
const DEBUG = __DEV__; // React Native の開発モードフラグ
const logger = {
debug: (...args) => {
if (DEBUG) console.log('[DEBUG]', ...args);
},
warn: (...args) => {
console.warn('[WARN]', ...args);
},
error: (...args) => {
console.error('[ERROR]', ...args);
// 本番環境ではエラーレポートサービスに送信
if (!DEBUG) {
// reportError(args);
}
},
};
// 使用例
logger.debug('現在のステート:', state); // 開発時のみ出力
logger.error('決済処理でエラー:', error); // 常に出力 + 本番ではレポート送信エラー解析と本番環境での監視をさらに深く学びたい方には、Rork Maxアプリのクラッシュ解析・監視完全ガイドで Sentry や Firebase Crashlytics を活用した実践的な監視手法を詳しく解説しています。
まとめ
エラーハンドリングとクラッシュ対策は、アプリの品質を左右する基盤となるスキルです。この記事で紹介した基本テクニックを実践するだけでも、アプリの安定性は大きく向上します。
まずは try-catch による基本的なエラーキャッチから始め、Error Boundary の配置、ネットワークエラーへの対応と段階的に取り組んでみてください。完璧を目指す必要はありません。「エラーが起きてもアプリがクラッシュしない」状態を作ることが最初のゴールです。
より高度なエラーハンドリングのパターンや本番監視体制について学びたい方には、Rork Max エラーハンドリング・デバッグ・本番監視の実践パターンがおすすめです。