先月、目覚ましアプリの設定を忘れて久しぶりに盛大な寝坊をしてしまったため、「これはさすがに改善しないといけない」と思い、冬休みを利用してアラームアプリを作成してみました。少し前にアプリ開発を普段していない同僚が生成 AI を使って Web アプリを作成しているところを見て、慣れない技術でも AI を活用すれば開発に挑戦できるのではないかと感じ、以前から気になっていたReact Native でのネイティブアプリ開発に挑戦することにしました。
この記事では、React Native や Expo といった技術の概要について、今回作成したアラームアプリを例にして紹介します。(アラームアプリの画面構成やソースコードは下の方に掲載しています。)React Native をはじめとするフロントエンド開発に興味を持ってもらうことを目指して記載します。
前提知識
SPA(Single Page Application)とは
React や React Native を理解するためには、まず 「SPA(Single Page Application)」 という概念を押さえておく必要があります。SPA は、従来の Web アプリのようにページ遷移のたびにサーバーから HTML を受け取るのではなく、最初にアプリ全体を読み込み、その後は JavaScript が画面の更新を担当する方式です。
画面の状態は JavaScript が保持し、必要な部分だけを差分更新するため、ページ遷移が高速で、アプリのような滑らかな操作感を実現できます。React はこの SPA の思想を前提に設計されており、画面を 「状態(state)」 と 「コンポーネント」 で管理する仕組みが非常に強力です。
React Native もこの React の思想をそのまま引き継いでおり、ネイティブアプリでありながら SPA 的な構造で動作します。つまり、React Native の UI は「状態が変わると UI が変わる」という SPA の基本原則の上に成り立っています。
SPA には、React の他に Angular や Vue.js 等のフレームワークがあります。
React とは
React は Meta(旧 Facebook)が開発した UI ライブラリで、現在の Web フロントエンド開発ではほぼ標準と言える存在です。React の特徴は、UI を 「コンポーネント」 という小さな部品の集合として捉える点にあります。画面を構成する要素を細かく分割し、それらを組み合わせて UI を構築するため、再利用性が高く、複雑な画面でも管理しやすくなります。
React のもう一つの重要な特徴は 「宣言的 UI」 という考え方です。従来の命令的 UI では開発者が DOM(画面を構成する部品)を直接操作し、要素の追加・削除・更新など、UI の変更方法を一つ一つ指示する必要がありました。これと対照的に宣言的 UI では、「状態がこうなったら、画面はこうあるべき」という宣言をコードで書いておき、状態が変更された際は React が内部で変化を検知し、必要な部分だけを再描画してくれます。Struts のようにサーバー側で HTML を生成して返す方式とは異なり、フロントエンド側で UI を完結させるのが React の世界観です。
React を理解する上で欠かせないのが「state(状態)」と「props(外部から渡される値)」です。React の UI は state を中心に動き、state が変わると UI が自動的に更新されます。この仕組みが React Native にもそのまま引き継がれています。
React Native とは
React Native は、React の書き方で iOS/Android のネイティブアプリを作れるフレームワークです。JavaScript で書いたコードがネイティブ UI に変換されるため、Web エンジニアがそのままネイティブアプリ開発に参入できるという大きなメリットがあります。
React Native の UI は WebView ではなく、実際にネイティブコンポーネントを描画します。たとえば、React Native の <View> は iOS では UIView、Android では View に変換されます。つまり、JavaScript で書いているにもかかわらず、アプリは本物のネイティブアプリとして動作します。
React Native の UI 実装は Flexbox を使ってレイアウトするため、Web の CSS と似ています。画面の構成要素は View や Text といったコンポーネントで表現され、これらを組み合わせて UI を作ります。スマホ特有の UI コンポーネントとして Switch や Pickerなどがあり、これらを使用したい時に適宜インポートすることでネイティブアプリらしい操作感を実現できます。
Expo とは
Expo は React Native の開発体験を大幅に向上させるプラットフォームです。Expo を使うと、Xcode や Android Studio をインストールしなくても開発を始められ、スマホにインストールした Expo Go アプリで即座に実機テストができます。
ソースを記述してビルドをすると PC に QR コードが表示され、それを iPhone で読み取るとアプリがロードされて動作確認ができるようになります。

React Native の基本機能の紹介
React アプリの読み込み(起動時の流れ)
React Native アプリは、起動するとまず JavaScript コード一式がまとめて読み込まれ、最初の画面(コンポーネント)がレンダリングされます。Expo を使っている場合は、Expo が内部でスマホの環境準備を行い、その後に JavaScript の実行環境が立ち上がり、React Native が実行されます。
React Native のアプリは、Web の SPA と同じように「最初にアプリ全体を読み込み、その後は状態に応じて画面を切り替える」という構造になっています。つまり、アプリの起動時に読み込まれるのは「最初の画面」ではなく「アプリ全体のルートコンポーネント」です。
今回のアプリでは、App.js がルートコンポーネントにあたり、アプリ全体のナビゲーションや状態管理の初期化を行っています。React Native のアプリは、起動時にこのルートコンポーネントが読み込まれ、そこから各画面へ遷移していく仕組みになっています。
// App.js(ルートコンポーネント)
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Main" component={MainScreen} />
<Stack.Screen name="EditGroup" component={EditGroupScreen} />
<Stack.Screen name="EditAlarm" component={EditAlarmScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
React Navigation による画面遷移
React Native で複数画面を扱う場合、ほぼ必ず使うのが React Navigation です。これは React Native のデファクトスタンダードのナビゲーションライブラリで、画面遷移、スタック管理、タブ、モーダルなど、ネイティブアプリに必要な UI 遷移をすべて提供してくれます。
React Navigation の基本は「スタックナビゲーション」です。これは、スマホアプリでよくある「画面を押すと次の画面が上に積み重なる」動作を実現する仕組みです。
今回のアプリでも、以下のような構成で画面遷移を実装しています。
- メイン画面(アラーム一覧)
- グループ編集画面
- アラーム編集画面
これらはすべてスタックナビゲーションで管理されており、ユーザーが画面を開くたびにスタックに積まれ、戻るとスタックから取り除かれるという動作をしています。
実際のナビゲーションコード(抜粋)
以下は、今回のアプリで使用していたナビゲーション設定の一部です。
アプリを起動するとルートコンポーネントが読み込まれた後、ナビゲーションの一番上にある MainScreen コンポーネントが読み込まれて表示されます。
// App.js(ルートコンポーネント)
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Main" component={MainScreen} /> // 一番上が読み込まれる
<Stack.Screen name="EditGroup" component={EditGroupScreen} />
<Stack.Screen name="EditAlarm" component={EditAlarmScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
このコードでは、NavigationContainer がアプリ全体のナビゲーションコンテキストを提供し、Stack.Navigator が画面遷移のスタックを管理しています。Stack.Screen で画面を登録し、name が画面の識別子、component が実際の画面コンポーネントです。
画面遷移は以下のように行います。
navigation.navigate("EditAlarm", { groupId, alarmId });
このように、React Navigation は画面遷移とパラメータの受け渡しを非常にシンプルに扱うことができます。
React のステート管理
React Native の UI は state を中心に動くため、状態管理はアプリ開発の中核になります。今回のアプリでは、アラームグループやアラームの ON/OFF 状態、通知 ID を state として管理しました。
以下は実際に使用したコードの一部です。
// MainScreen.js
const [groups, setGroups] = useState([]);
const handleToggleAlarmToday = (groupId, alarmId) => {
setGroups((prev) => {
const updated = [...prev];
const groupIndex = updated.findIndex((g) => g.id === groupId);
// 省略
updated[groupIndex] = { ...group, alarms };
return updated;
});
};
今回のアプリではアラームをグループとして登録できるようにしています。このコードでは、groups という state にアラームグループの一覧を保持し、アラームの ON/OFF を切り替えるたびに setGroups を呼び出して state を更新しています。React は state の変化を setGroups 検知して該当する UI を自動的に再描画してくれるため、スイッチの ON/OFF が即座に画面に反映されます。
React のステート管理では「state を直接書き換えず、必ず setState 系の関数を使う」というルールが重要です。今回のコードでも、setGroups の中で updated という新しい配列を作り直し、その中で値を更新しています。React は「参照の変化」を検知して UI を更新するため、この書き方が必須になります。
スマホ特有の UI コンポーネント・機能
React Native では、ライブラリをインポートすることでスマホアプリ特有の UI コンポーネントやストレージ・カメラ等デバイス操作の API を使用できます。今回のアプリでも、アラームの ON/OFF や時刻選択、データの保存、あらーむ等でこれらを活用しました。ここでは、今回特に使用した Switch、AsyncStorage の 2 つについて紹介します。
Switch
Switch は、スマホアプリでよく見かける「トグルスイッチ」です。iOS では丸いスライダーが左右に動く UI です。
Web のチェックボックスとは異なり、スマホの標準 UI に近い見た目と操作感を提供してくれます。

今回のアプリでの用途
- アラームの ON/OFF
- アラームグループ全体の ON/OFF
実際のコード例
// AlarmItem.js
<View style={styles.item}>
<Text style={styles.time}>{alarm.time}</Text>
<Switch
value={alarm.isTodayEnabled}
onValueChange={onToggleToday}
/>
</View>
このコードでは、value に現在の状態(true/false)を渡し、
onValueChange でスイッチが切り替わった時にonToggleToday を呼び出して state を更新しています。
React Native の Switch は、状態が変わると自動的にON/OFFが切り替わるため、宣言的 UI を視覚的に捉えやすいコンポーネントです。
AsyncStorage
アラームアプリでは、アプリを閉じても設定が消えないようにする必要があります。
React Native では、Web の localStorage に近い役割を持つ AsyncStorage を使うことで、データをスマホ内部に保存できます。
今回のアプリでの用途
以下の設定をAsyncStorage に保存し、アプリ起動時に読み込むようにしていました。
- アラームグループ一覧
- アラームの設定
- 通知 ID
- 最終アプリ起動日
実際のコード例(groupStorage.js)
import AsyncStorage from "@react-native-async-storage/async-storage";
const KEY = "groups";
export async function loadGroups() {
try {
const json = await AsyncStorage.getItem(KEY);
return json ? JSON.parse(json) : [];
} catch (e) {
console.error("loadGroups error:", e);
return [];
}
}
export async function saveGroups(groups) {
try {
await AsyncStorage.setItem(KEY, JSON.stringify(groups));
} catch (e) {
console.error("saveGroups error:", e);
}
}
React Native の AsyncStorage は非同期 API なので、関数内で使用する際は先頭にasyncを付けて非同期処理があることを宣言しておき、await を使ってストレージ保存をした後に後続処理が動くようにします。ちなみに、async、awaitは ECMAScript6(2015 年版 JavaScript)以上の機能なので、気になる方は別途調べてみてください。
まとめ
今回、寝坊という個人的な反省から始まったアラームアプリの開発でしたが、結果的に React Native や Expo の仕組みを深く理解する良いきっかけになりました。実際に手を動かしてみると、過去に使用したことのある Angular と共通する点もあり、JavaScript の知識があれば想像よりも手軽にネイティブアプリ開発ができることに驚きました。
おまけ:今回作成したアプリの構成
ソースコード(Github)
フォルダ構成(主要部分)
project-root/
├── App.js
├── screens/
│ ├── MainScreen.js
│ ├── EditGroupScreen.js
│ └── EditAlarmScreen.js
├── components/
│ ├── AlarmItem.js
│ └── GroupItem.js
├── utils/
│ ├── notification.js
│ └── storage.js
└── assets/
App.js(アプリ全体のエントリーポイント)
アプリ起動時に最初に読み込まれるファイルで、React Navigation の設定を行い、画面遷移のルートを定義しています。
Expo アプリでは、このファイルが「アプリ全体の初期化処理」と「ナビゲーションの起点」を担います。
screens/(画面コンポーネント)
アプリの主要な画面はすべてこのフォルダにまとめています。
MainScreen.js
アプリの中心となる画面で、アラームグループの一覧と各アラームの状態を表示します。
アプリ全体の state(グループ一覧、アラーム一覧、通知 ID など)をここで管理しており、編集画面からの変更もすべてこの画面に反映されます。

EditGroupScreen.js
アラームグループの編集画面で、グループ名の変更やアラームの追加・削除を行います。
MainScreen から渡されたコールバック関数を使って state を更新します。

EditAlarmScreen.js
アラームの詳細設定画面で、時刻、曜日、当日 ON/OFF などの設定を行います。
通知の登録やキャンセル処理もこの画面から呼び出されます。

components/(UI 部品)
画面内で繰り返し使う UI コンポーネントをまとめています。
AlarmItem.js
アラーム 1 件分の UI を担当し、時刻表示や ON/OFF スイッチを持ちます。
GroupItem.js
アラームグループ 1 件分の UI を担当し、グループ名やグループ全体の ON/OFF を表示します。
これらのコンポーネントを分離することで、MainScreen のコードが読みやすくなり、UI の再利用性も高まります。
utils/(ロジックやユーティリティ)
アプリの裏側で動く処理をまとめています。
notification.js
Expo Notifications を使った通知登録・キャンセルの処理をまとめています。
通知 ID の管理や、曜日ごとの通知スケジュールなど、アプリの中でも最も複雑なロジックが含まれています。
storage.js
AsyncStorage を使ったデータ永続化処理をまとめています。
アラーム設定や通知 ID を保存し、アプリ再起動後も状態を復元できるようにしています。
assets/(画像・フォントなど)
アプリ内で使用する画像やフォントを格納するフォルダです。
今回のアプリでは大きな使用はありませんが、一般的な Expo アプリではここにアイコンや画像を置きます。
その他
今回のアプリは、React Native の基本構造である「画面(screens)」「UI 部品(components)」「ロジック(utils)」が明確に分かれており、状態管理は MainScreen に集約する形で実装しました。 アラームの編集をしたい場合は MainScreen から遷移先のコンポーネントへコールバック関数を引き渡すことで、遷移先で状態を変更できるようにしています。