方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

Expo入門:ディレクトリ構造・Expo Router・セットアップ手順

expo-react-native-development-guide
この記事は約18分で読めます。

今回新しくReact Nativeでアプリ開発をすることになったかExpoというフレームワークも使うことになったのでまとめておく

Expoとは:React Native開発を効率化するOSSプラットフォーム

Expoは、Expo Application Services(EAS)を中心とするツール群を提供する、React Nativeベースのモバイルアプリケーション開発フレームワーク。React Native自体はJavaScriptを使用してクロスプラットフォーム(iOS/Android)のアプリを開発するためのフレームワークだが、Expoはその上に構築され、以下の特徴を持つ

 

Expoの特徴

  1. 開発環境の簡素化
    • ネイティブ開発環境(XcodeやAndroid Studio)のセットアップが不要。
    • JavaScript/TypeScriptだけで開発可能。
  2. 豊富なライブラリとAPI
    • Expo SDKには、カメラ、位置情報、プッシュ通知、センサーなど、モバイルアプリに必要な機能が統合されている。
  3. Expo Application Services(EAS)
    • アプリのビルド、デプロイ、更新を簡略化。
  4. 実機プレビュー(Expo Go)
    • 開発中のアプリを「Expo Go」というアプリを使って、iOSやAndroidの実機で即座にプレビュー可能。
  5. ホットリロード

 

Expoのディレクトリ構造

TypeScript
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 :アプリのルーティングを管理

ディレクトリの構造がそのままアプリのルーティング構造(ページ構造)になる。

TypeScript
app/
  index.js         // ホームページ
  about.js         // "about"ページ
  [userId].js      // 動的ルーティング(例: "/user/123")

 共通のレイアウト(_layout.tsx): ページ間で使うナビゲーションバーやフッターなどを設定可能。

  • アンダースコア付きでファイル名を記載すると、「ルート構造全体に影響を与える」ファイルとして認識される。_layout.tsxlayout.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ライブラリを使用して簡単に読み込むことができる。

TypeScript
import { Image } from 'react-native';
import logo from './assets/logo.png';

<Image source={logo} style={{ width: 100, height: 100 }} />;

components:ページのUI部品を格納

再利用可能なUIコンポーネントをまとめる。

TypeScript
components/
  Button.js       // 再利用可能なカスタムボタン
  Header.js       // ヘッダーコンポーネント

hooks :カスタムフック(コンポーネント内で使用するロジック)を格納

複数のコンポーネントで共通するロジック(APIデータのフェッチ処理・スクロール位置やウィンドウサイズの管理など)を分離して再利用

TypeScript
// 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のエンドポイントに接続)

データ処理のロジック

  • データの整形やキャッシュ処理を行う(例: サーバーから受け取ったデータをアプリで使いやすい形に加工)
TypeScript
// 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;
  }
};

serviceshooksの両方でロジックを扱うが、その役割と用途が異なる

特徴serviceshooks
役割API通信やビジネスロジックを集約するコンポーネントに特化したロジックを管理
主な用途サーバーとの通信、データ処理、汎用的な機能を提供状態管理、イベント処理、UI関連のロジックを再利用可能に
利用方法他のhookscomponentsから呼び出されることが多いReactコンポーネントで直接使用する
スコープアプリケーション全体で再利用可能特定のコンポーネントやページに特化
– API通信関数(fetchDatapostData)- 認証関連処理- データ加工ロジック– カスタムフック(useFetchuseAuth)- ウィンドウサイズ取得- スクロール位置の監視
依存するものAxiosなどの通信ライブラリ、ユーティリティ関数などReactのライフサイクルフック(useStateuseEffect

 

scripts :シェルコマンドやプロジェクトで使用するスクリプトを格納

開発段階ではあまり使われないが、ビルドやデプロイの自動化などで役立つ場合がある。

TypeScript
scripts/
  deploy.sh       // デプロイの自動化スクリプト
  setup.sh        // 初期セットアップスクリプト

 

app.json :Expoアプリの設定ファイル

アプリ名、バージョン、アイコン、スプラッシュスクリーンなどを定義。

TypeScript
{
  "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で行っている

TypeScript
module.exports = function (api) {
  api.cache(true); //Babelの設定をキャッシュして、ビルドの高速化を図る
  return {
    presets: ['babel-preset-expo'], //Babelの変換ルールをまとめたセット
  };
};

例えば、下記のReactの独自構文(JSX)は、JavaScriptエンジンがそのまま理解できるコードではない。

TypeScript
const App = () => <Text>Hello, React Native!</Text>;

Babelを使うと、これが次のようなJavaScriptコードに変換される。

TypeScript
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)。
TypeScript
{
  "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)も同時にインストールされる。インストール後に下記でバージョン確認

ShellScript
node -v
npm -v

 

Expo CLIのインストール

npmを使用してグローバルにインストール。

ShellScript
npm install -g expo-cli

 インストールすると、システム全体でどこからでもexpoコマンドを実行可能になる。

新しいプロジェクトの作成

プロジェクト作成は、create-expo-appコマンドで行う。プロジェクト作成時にしか使わないcreate-expo-appをローカルやグローバルにインストールする必要はないため、その場合には、npmではなくnpxコマンドを使う

ShellScript
npx create-expo-app my-app

開発サーバーを起動

作成されたプロジェクトフォルダに移動し、Expo CLIを使って開発サーバーを起動する

ShellScript
cd my-app
npx expo start

エミュレーター・実機でのプレビュー(Expo Go)

エミュレーターと実機とでプレビューの仕方が異なる。エミュレーターの場合は、下記コマンドを実行。アプリがMacのiOSシミュレーターで起動する

ShellScript
npm run ios
npm run android

 npm run コマンドを実行すると、package.jsonファイルのscriptsセクションを参照して、指定されたスクリプトを実行する。今回の場合は"ios": "expo start --ios" (プロジェクト作成した時点で自動的に作成される)が呼び出され、実際にはexpoコマンド(expo start --ios)が実行されている(ビルド時にターミナルに表示される)

TypeScript
// 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

 

実際にビルドすると下記のように表示された。次にやるチュートリアルも表示されている

 

 

タイトルとURLをコピーしました