今回新しくReact Nativeでアプリ開発をすることになったかExpoというフレームワークも使うことになったのでまとめておく
Expoとは:React Native開発を効率化するOSSプラットフォーム
Expoは、Expo Application Services(EAS)を中心とするツール群を提供する、React Nativeベースのモバイルアプリケーション開発フレームワーク。React Native自体はJavaScriptを使用してクロスプラットフォーム(iOS/Android)のアプリを開発するためのフレームワークだが、Expoはその上に構築され、以下の特徴を持つ
Expoの特徴
- 開発環境の簡素化
- ネイティブ開発環境(XcodeやAndroid Studio)のセットアップが不要。
- JavaScript/TypeScriptだけで開発可能。
- 豊富なライブラリとAPI
- Expo SDKには、カメラ、位置情報、プッシュ通知、センサーなど、モバイルアプリに必要な機能が統合されている。
- Expo Application Services(EAS)
- アプリのビルド、デプロイ、更新を簡略化。
- 実機プレビュー(Expo Go)
- 開発中のアプリを「Expo Go」というアプリを使って、iOSやAndroidの実機で即座にプレビュー可能。
- ホットリロード
Expoのディレクトリ構造
project-root/
│
├── app/ # ページやレイアウトを管理
│ ├── index.js # ホームページ(ルートページ)
│ ├── about.js # Aboutページ
│ ├── [userId].js # 動的ルーティング例
│ └── _layout.js # 共通レイアウト(例: ナビゲーションバーやフッター)
│
├── assets/ # 静的ファイル(画像やフォントなど)
│ ├── icon.png # アプリアイコン
│ ├── splash.png # スプラッシュスクリーン画像
│ └── fonts/ # カスタムフォント
│ ├── OpenSans-Regular.ttf
│ └── OpenSans-Bold.ttf
│
├── components/ # 再利用可能なUIコンポーネント
│ ├── Button.js # カスタムボタンコンポーネント
│ ├── Header.js # ヘッダーコンポーネント
│ └── Footer.js # フッターコンポーネント
│
├── hooks/ # カスタムフック(コンポーネントに特化したロジック)
│ ├── useFetch.js # データ取得用のカスタムフック
│ ├── useAuth.js # 認証状態の管理フック
│ └── useWindowSize.js # ウィンドウサイズ取得のフック
│
├── services/ # API通信やビジネスロジックを管理
│ ├── api.js # サーバーとの通信ロジック
│ ├── auth.js # 認証関連の処理
│ ├── user.js # ユーザー情報の取得・更新ロジック
│ └── utils.js # 汎用的なヘルパーロジック
│
├── scripts/ # シェルスクリプト(補助スクリプト)
│ ├── deploy.sh # デプロイ用スクリプト
│ └── setup.sh # プロジェクト初期設定スクリプト
│
├── node_modules/ # npmでインストールした依存パッケージ
│ └── ... # 自動生成されるため触る必要なし
│
├── .gitignore # Gitで追跡しないファイルやディレクトリを指定
├── app.json # Expoアプリの設定ファイル
├── babel.config.js # Babelの設定ファイル
├── package.json # プロジェクトの依存関係やスクリプトを管理
├── metro.config.js # Metroバンドラーの設定ファイル(必要に応じて作成)
└── README.md # プロジェクトの概要やセットアップ手順
app :アプリのルーティングを管理
ディレクトリの構造がそのままアプリのルーティング構造(ページ構造)になる。
app/
index.js // ホームページ
about.js // "about"ページ
[userId].js // 動的ルーティング(例: "/user/123")
共通のレイアウト(_layout.tsx): ページ間で使うナビゲーションバーやフッターなどを設定可能。
- アンダースコア付きでファイル名を記載すると、「ルート構造全体に影響を与える」ファイルとして認識される。
_layout.tsx
をlayout.tsx
に変更した場合はExpo Routerはこれを通常のページとして認識し、URLルート(/layout
)として動作する。逆に言えば、_layout.tsx
というファイル名にすると、通常のページ(URLルートとして)としては認識されない。
Expoはファイルベースルーティング(ディレクトリ構造でパスが決まるルーティング)を採用している。
- index.tsxは、そのディレクトリのルートパス(/)として認識される。
app/index.tsx
は URL/
に対応する - app/dashboard.tsx→/dashboard
- app/settings/index.tsx→
/settings
- app/(settings)/language.tsx→/language
パスに影響を与えずにフォルダで管理したい場合、フォルダ名を()で囲む。
画面遷移には、Linkコンポーネントを使用する方法と、router.navigateメソッドの2つがある
assets : 静的コンテンツ(画像、フォント、アイコンなど)を格納
Expo SDKのexpo-asset
ライブラリを使用して簡単に読み込むことができる。
import { Image } from 'react-native';
import logo from './assets/logo.png';
<Image source={logo} style={{ width: 100, height: 100 }} />;
components:ページのUI部品を格納
再利用可能なUIコンポーネントをまとめる。
components/
Button.js // 再利用可能なカスタムボタン
Header.js // ヘッダーコンポーネント
hooks :カスタムフック(コンポーネント内で使用するロジック)を格納
複数のコンポーネントで共通するロジック(APIデータのフェッチ処理・スクロール位置やウィンドウサイズの管理など)を分離して再利用
// hooks/useFetch.js
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then((res) => res.json())
.then((data) => {
setData(data);
setLoading(false);
});
}, [url]);
return { data, loading };
};
export default useFetch;
services :APIサービスやビジネスロジックを格納
API通信の管理
- サーバーとの通信処理を分離して管理(例: Axiosを使用してREST APIやGraphQLのエンドポイントに接続)
データ処理のロジック
- データの整形やキャッシュ処理を行う(例: サーバーから受け取ったデータをアプリで使いやすい形に加工)
// api.js:API通信を管理
import axios from 'axios';
// Axiosインスタンスを作成
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// GETリクエスト例
export const fetchData = async (endpoint) => {
try {
const response = await apiClient.get(endpoint);
return response.data;
} catch (error) {
console.error('API Error:', error);
throw error;
}
};
// POSTリクエスト例
export const postData = async (endpoint, data) => {
try {
const response = await apiClient.post(endpoint, data);
return response.data;
} catch (error) {
console.error('API Error:', error);
throw error;
}
};
services
とhooks
の両方でロジックを扱うが、その役割と用途が異なる
特徴 | services | hooks |
役割 | API通信やビジネスロジックを集約する | コンポーネントに特化したロジックを管理 |
主な用途 | サーバーとの通信、データ処理、汎用的な機能を提供 | 状態管理、イベント処理、UI関連のロジックを再利用可能に |
利用方法 | 他のhooks やcomponents から呼び出されることが多い | Reactコンポーネントで直接使用する |
スコープ | アプリケーション全体で再利用可能 | 特定のコンポーネントやページに特化 |
例 | – API通信関数(fetchData 、postData )- 認証関連処理- データ加工ロジック | – カスタムフック(useFetch 、useAuth )- ウィンドウサイズ取得- スクロール位置の監視 |
依存するもの | Axiosなどの通信ライブラリ、ユーティリティ関数など | Reactのライフサイクルフック(useState 、useEffect ) |
scripts :シェルコマンドやプロジェクトで使用するスクリプトを格納
開発段階ではあまり使われないが、ビルドやデプロイの自動化などで役立つ場合がある。
scripts/
deploy.sh // デプロイの自動化スクリプト
setup.sh // 初期セットアップスクリプト
app.json :Expoアプリの設定ファイル
アプリ名、バージョン、アイコン、スプラッシュスクリーンなどを定義。
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"platforms": ["ios", "android", "web"]
}
}
babel.config.js :JavaScriptのコンパイラであるBabelの設定
Babelとは、新しいJavaScript構文を古いブラウザや環境でも動作するコードに変換するツール。
React NativeやExpoでは、最新のJavaScript機能やReact独自の構文(JSXなど)を使うが、これらの構文がそのままでは、デバイスや古いブラウザやJavaScriptエンジン(例: Androidの古いバージョン)で動作しないことがあるため、互換性のある構文に変換する必要があり、その設定をbabel.config.jsで行っている
module.exports = function (api) {
api.cache(true); //Babelの設定をキャッシュして、ビルドの高速化を図る
return {
presets: ['babel-preset-expo'], //Babelの変換ルールをまとめたセット
};
};
例えば、下記のReactの独自構文(JSX)は、JavaScriptエンジンがそのまま理解できるコードではない。
const App = () => <Text>Hello, React Native!</Text>;
Babelを使うと、これが次のようなJavaScriptコードに変換される。
const App = () => React.createElement(Text, null, 'Hello, React Native!');
ただ、presets: [‘babel-preset-expo’]を記載するだけでOK。babel-preset-expo
は、Expoプロジェクトに必要なすべての変換ルールを含んでいるため。
内部的には下記をサポートしている。
- JSX構文の変換。
- 最新のJavaScript構文(ES6+)の変換。
- React Native向けのモジュール解決(パスの補完など)。
ちなみに、Babelは元々、「Babylon(バビロン)」という名前のJavaScriptパーサー(構文解析ツール)から発展したもので、「Babylonian confusion(バビロンの混乱)」をヒントに名付けられたと言われている。
正式な略語ではなく、名前自体が「多くの異なるJavaScript構文(言語)をサポートし、統一された形に変換する」という役割を象徴している。バビロンの塔の伝説(異なる言語が混ざり合った世界)を連想させる名前。
package.json:プロジェクトの依存パッケージやスクリプトを管理
アプリケーションで利用するライブラリやツール(React Native、Expo、Axiosなど)の情報を記録する。
dependencies
: 実際のアプリケーションで必要なパッケージ。devDependencies
: 開発時のみ必要なパッケージ(例: ESLint、Prettier)。
{
"dependencies": {
"react": "18.0.0",
"react-native": "0.72.0",
"expo": "^49.0.0",
"axios": "^1.3.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"eslint": "^8.40.0"
}
}
Expo環境をセットアップする手順
Node.jsのインストール
Expo CLI(Expoプロジェクトの作成、エミュレーターの起動などができるコマンドラインインターフェイス)は、Node.jsの環境で動作する。
公式サイトからNode.jsをインストールすると、npm(Node Packag Manager)も同時にインストールされる。インストール後に下記でバージョン確認
node -v
npm -v
Expo CLIのインストール
npmを使用してグローバルにインストール。
npm install -g expo-cli
インストールすると、システム全体でどこからでもexpo
コマンドを実行可能になる。
新しいプロジェクトの作成
プロジェクト作成は、create-expo-appコマンドで行う。プロジェクト作成時にしか使わないcreate-expo-app
をローカルやグローバルにインストールする必要はないため、その場合には、npmではなくnpxコマンドを使う
npx create-expo-app my-app
開発サーバーを起動
作成されたプロジェクトフォルダに移動し、Expo CLIを使って開発サーバーを起動する
cd my-app
npx expo start
エミュレーター・実機でのプレビュー(Expo Go)
エミュレーターと実機とでプレビューの仕方が異なる。エミュレーターの場合は、下記コマンドを実行。アプリがMacのiOSシミュレーターで起動する
npm run ios
npm run android
npm run
コマンドを実行すると、package.json
ファイルのscripts
セクションを参照して、指定されたスクリプトを実行する。今回の場合は"ios": "expo start --ios"
(プロジェクト作成した時点で自動的に作成される)が呼び出され、実際にはexpoコマンド(expo start --ios
)が実行されている(ビルド時にターミナルに表示される)
// package.json
{
"name": "my-app",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"test": "jest --watchAll",
"lint": "expo lint"
},
}
実機の場合は上記ではダメで、Expo Goという、Expoで開発中のアプリを実機(スマートフォン)でプレビューするためのモバイルアプリのダウンロードが必要。Expoが提供している公式アプリで、iOSとAndroid向けに公開されている。
Expo Goでは、QRコードをスキャンするだけで、ビルド無しで実機で動作確認ができる。
実機の場合は、npx expo startで開発サーバーを起動すると、QRコードがターミナルに表示されるので、スマホのカメラで読み取ることで、Expo Go経由で実機でアプリをプレビューできる。ホットリロードでコードの変更がすぐに反映される
ちなみにFlutterの場合は実機でのビルド確認は、有線接続が必要でPCとスマホをUSBケーブルで接続してビルドを実行するが、Expoの場合はQRコードを読み取る方式なのでExpo Goアプリがあれば有線でなくても実機確認できる。地味に便利
ちなみに、開発中に実機確認ではなく、実機向けの完成版アプリを作成する場合はEAS Buildを使ったビルドが一般的。詳細はこちらに記載されている
https://zenn.dev/kamo_tomoki/books/0158a7770edeea/viewer/2b22d2
実際にビルドすると下記のように表示された。次にやるチュートリアルも表示されている