Expo + React Native完全ガイド - モバイルアプリ開発を最速で始める
ExpoとReact Nativeを使えば、JavaScriptとReactの知識だけで、iOS/Androidの両方に対応したネイティブアプリを開発できます。この記事では、Expoを使った最新のモバイルアプリ開発手法を解説します。
ExpoとReact Nativeの違い
React Native
- Metaが開発したモバイルアプリフレームワーク
- JavaScriptでネイティブアプリを構築
- ネイティブコード(Objective-C、Swift、Java、Kotlin)との統合が可能
Expo
- React Nativeのラッパーフレームワーク
- 開発環境の構築を簡素化
- ビルド、デプロイツールが統合済み
- 多数のネイティブ機能をすぐに使用可能
選択基準:
- Expo推奨 - 多くのアプリ、特にスタートアップや個人開発
- Bare React Native推奨 - 特殊なネイティブモジュールが必要な場合
環境構築
必要なもの
# Node.js 18以上が必要
node --version # v18.0.0以上であることを確認
Expoプロジェクトの作成
# Expoプロジェクトを作成
npx create-expo-app my-app --template
# プロジェクトディレクトリに移動
cd my-app
# 開発サーバーを起動
npx expo start
テンプレート選択肢:
- Blank - 最小構成
- Blank (TypeScript) - TypeScript対応の最小構成
- Tabs (TypeScript) - タブナビゲーション付き
Expo Goアプリで実機テスト
-
スマートフォンにExpo Goアプリをインストール
- iOS: App Store
- Android: Google Play
-
npx expo startでQRコードが表示される -
Expo GoアプリでQRコードをスキャン
これだけで実機でアプリが動作します。ビルド不要で即座に確認できるのがExpoの最大の利点です。
基本的なアプリ構造
App.tsx(エントリーポイント)
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello, Expo!</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
});
React Nativeの基本コンポーネント
View(コンテナ)
HTMLのdivに相当します。
import { View } from 'react-native';
<View style={{ padding: 20, backgroundColor: '#f0f0f0' }}>
{/* 子要素 */}
</View>
Text(テキスト)
すべてのテキストはTextコンポーネント内に配置する必要があります。
import { Text } from 'react-native';
<Text style={{ fontSize: 16, color: '#333' }}>
こんにちは
</Text>
Button(ボタン)
import { Button, Alert } from 'react-native';
<Button
title="クリック"
onPress={() => Alert.alert('ボタンが押されました')}
color="#007AFF"
/>
TextInput(入力フィールド)
import { TextInput, useState } from 'react-native';
const [text, setText] = useState('');
<TextInput
style={{
height: 40,
borderColor: 'gray',
borderWidth: 1,
paddingHorizontal: 10,
}}
value={text}
onChangeText={setText}
placeholder="入力してください"
/>
ScrollView(スクロール可能な領域)
import { ScrollView, Text } from 'react-native';
<ScrollView>
{Array.from({ length: 50 }, (_, i) => (
<Text key={i}>アイテム {i + 1}</Text>
))}
</ScrollView>
FlatList(効率的なリスト表示)
大量のデータを効率的に表示します。
import { FlatList, Text, View } from 'react-native';
const data = [
{ id: '1', title: 'アイテム 1' },
{ id: '2', title: 'アイテム 2' },
{ id: '3', title: 'アイテム 3' },
];
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={{ padding: 16 }}>
<Text>{item.title}</Text>
</View>
)}
/>
ナビゲーション(React Navigation)
画面遷移にはReact Navigationを使用します。
インストール
npx expo install @react-navigation/native @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context
基本的な使い方
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button } from 'react-native';
const Stack = createNativeStackNavigator();
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text style={{ fontSize: 24 }}>ホーム画面</Text>
<Button
title="詳細へ"
onPress={() => navigation.navigate('Details', { itemId: 42 })}
/>
</View>
);
}
function DetailsScreen({ route }) {
const { itemId } = route.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>詳細画面</Text>
<Text>アイテムID: {itemId}</Text>
</View>
);
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'ホーム' }} />
<Stack.Screen name="Details" component={DetailsScreen} options={{ title: '詳細' }} />
</Stack.Navigator>
</NavigationContainer>
);
}
Expoの便利な機能
カメラ
npx expo install expo-camera
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useState } from 'react';
import { Button, StyleSheet, View } from 'react-native';
export default function CameraScreen() {
const [permission, requestPermission] = useCameraPermissions();
const [cameraRef, setCameraRef] = useState(null);
if (!permission?.granted) {
return (
<View style={styles.container}>
<Button onPress={requestPermission} title="カメラ権限を許可" />
</View>
);
}
const takePicture = async () => {
if (cameraRef) {
const photo = await cameraRef.takePictureAsync();
console.log(photo.uri);
}
};
return (
<View style={styles.container}>
<CameraView style={styles.camera} ref={setCameraRef}>
<View style={styles.buttonContainer}>
<Button title="撮影" onPress={takePicture} />
</View>
</CameraView>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
camera: { flex: 1 },
buttonContainer: { position: 'absolute', bottom: 20, alignSelf: 'center' },
});
位置情報
npx expo install expo-location
import * as Location from 'expo-location';
import { useEffect, useState } from 'react';
import { Text, View } from 'react-native';
export default function LocationScreen() {
const [location, setLocation] = useState(null);
useEffect(() => {
(async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
console.log('権限が拒否されました');
return;
}
const loc = await Location.getCurrentPositionAsync({});
setLocation(loc.coords);
})();
}, []);
return (
<View>
{location && (
<>
<Text>緯度: {location.latitude}</Text>
<Text>経度: {location.longitude}</Text>
</>
)}
</View>
);
}
ローカルストレージ(AsyncStorage)
npx expo install @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
// 保存
await AsyncStorage.setItem('user_name', 'Taro');
// 取得
const name = await AsyncStorage.getItem('user_name');
// 削除
await AsyncStorage.removeItem('user_name');
スタイリング
StyleSheetによるスタイリング
import { StyleSheet, View, Text } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#f5f5f5',
},
card: {
backgroundColor: 'white',
borderRadius: 8,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3, // Android用の影
},
title: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 8,
},
});
<View style={styles.container}>
<View style={styles.card}>
<Text style={styles.title}>カードタイトル</Text>
</View>
</View>
NativeWindでTailwind CSS
npx expo install nativewind tailwindcss
これで、Tailwind CSSライクなスタイリングが可能になります。
ビルドとデプロイ
EAS Buildでアプリをビルド
# EAS CLIをインストール
npm install -g eas-cli
# ログイン
eas login
# ビルド設定
eas build:configure
# iOSビルド
eas build --platform ios
# Androidビルド
eas build --platform android
App StoreとGoogle Playへの公開
-
iOS(App Store)
- Apple Developer Program(年間$99)に登録
- App Store Connectでアプリを登録
- EASでビルドしたIPAをアップロード
-
Android(Google Play)
- Google Play Developer($25の一回払い)に登録
- Play Consoleでアプリを登録
- EASでビルドしたAAB/APKをアップロード
Expo Goでのプレビュー公開
npx expo publish
これでExpo Go経由でアプリをシェアできます(開発中のプレビューに最適)。
まとめ
Expo + React Nativeは、Webエンジニアがモバイルアプリ開発に参入する最速の手段です。
主要な利点:
- 一つのコードベースでiOS/Android対応
- React/TypeScriptの知識がそのまま活用可能
- 豊富なネイティブ機能(カメラ、位置情報、通知等)
- ホットリロードで高速な開発体験
- EAS Buildでクラウドビルド
公式ドキュメント: https://docs.expo.dev/
モバイルアプリ開発の敷居は、Expoによって劇的に下がりました。今日からあなたのアイデアをアプリにしましょう。