[Dalendar DevLog 4] コア機能実装 2 (フロントエンド & UI)
React Nativeで直感的なカレンダーUIを実装する過程です。FlatListを活用した無限スクロールとコンポーネント再利用性最大化戦略を紹介します。
![[Dalendar DevLog 4] コア機能実装 2 (フロントエンド & UI)](/images/blog/dalendar_dev_4_ui_structure.png)
4編: コア機能実装 2 (フロントエンド & UI)
1. シリーズの始まり: 直感的なUIに向けた第一歩
こんにちは、React Nativeカレンダーアプリ作りシリーズ4編です。前回の編まではアプリの基本設定とデータ構造について調べました。今回の編ではユーザーが直接対面して相互作用するカレンダーのフロントエンド、つまりUI実装について集中的に扱います。すべての実装はReact NativeとExpo環境を基盤に進められます。
アプリ開発初期段階で最も重要に考えた目標は「直感的なユーザー体験」でした。このために**「一画面に一ヶ月が見えて、左右スクロールで前月と翌月に自由に移動」**する方式を核心UIとして決定しました。ユーザーが別途の学習なしでも自然にカレンダーを探索できるようにすることが目標でした。
2. カレンダーUIの核心構造: コンポーネント再利用性最大化
効率的なカレンダーUIのために、私たちはネストされたコンポーネント構造を採択しました。この方式は「コンポーネント再利用」を通じてシステム資源を効率的に使用して性能を最適化することが核心です。
React NativeのFlatListコンポーネントは画面に見えたり近くにあるアイテムだけレンダリングし、遠くスクロールされたアイテムはメモリから除去(unmount)する「仮想化(virtualization)」を通じてこれを実装します。これはネイティブのRecyclerViewが最小限のビューホルダー(枠)を作ってデータを変えながら再活用する方式と同じ原理です。
これをもとに私たちのアプリは次のような2段階コンポーネント構造を採択しました。
- 月(Month)スクロールビュー: 最上位レベルのコンポーネントで、左右にスクロールされます。一画面にはただ一つの「月」アイテムだけ見えるように構成してユーザーが月単位で明確に探索できるようにします。これは
<FlatList horizontal pagingEnabled ... />のようなコンポーネントを使用すれば効率的に実装できます。 - 日(Day)グリッドビュー: それぞれの「月」アイテム内部に位置するコンポーネントです。一ヶ月の日付を6行7列、計42個の格子形態で表示してカレンダーの視覚的形態を完成させます。
3. 「無限スクロール」月カレンダー実装する
ユーザーが過去と未来を制約なく探索できるように「無限スクロール」機能を実装しました。これは実際に無限のデータをローディングするのではなく、ユーザーがそう感じるように作る一種のトリックです。
核心原理はFlatListの開始位置を非常に大きな整数値の中間地点に設定することです。initialScrollIndex propを活用すればこのトリックを簡単に実装できます。
const INITIAL_INDEX = Math.floor(Number.MAX_SAFE_INTEGER / 2);
// ...
<FlatList
// ... other props
initialScrollIndex={INITIAL_INDEX}
/>;
こうすればユーザーは両方向に事実上無限大に近くスクロールできる経験をすることになります。
月スクロールビューの各アイテム(position)に該当する年度と月を計算するロジックは「中央からのオフセット」概念を使えば直感的に実装できます。
- INITIAL_INDEXを「基準点(center)」として設定し、この基準点が現在の日付の月に該当するようにマッピングします。
- FlatListによってレンダリングされる各アイテムのpositionに対して、基準点とのオフセットを計算します:
const monthOffset = position - center; - 現在の月にmonthOffset値を足したり引いたりして該当アイテムが表示すべき年度と月の日付データを動的に生成します。
4. 一ヶ月の日付グリッド生成ロジック
特定年度と月が与えられたとき、画面に表示される6x7(42個)グリッドを埋める日付データを生成するロジックはカレンダー実装の核心です。
この過程は次のように進行されます。
- まず42個の日付オブジェクトを入れる空配列を生成します。
- 最も重要な段階は与えられた「月の1日が何曜日なのか」把握することです。JavaScriptのDateオブジェクトで
getDay()メソッドは日曜日を0、月曜日を1、… 土曜日を6で返します。 - 1日が属した週の最初の日(日曜日)を計算します。これは「1日」から「1日の曜日インデックス」分日付を後ろに移動させて求めることができます。
- 計算された開始日付(日曜日)から一日ずつ足して6週間、つまり計42個の日付データを順次配列に埋め込みます。
例えば、2024年12月1日は日曜日(曜日インデックス0)です。したがってグリッドの開始日付は12月1日から0日を引いた12月1日になります。もし1日が水曜日(インデックス3)だったら、開始日付は1日から3日を引いた前月の28日になります。この簡単な例示はロジックの核心を明確に見せてくれます。
5. 仕上げに: 構造的完成
今回のポストを通じて私たちはカレンダーアプリの核心的なフロントエンド構造を完成させました。コンポーネント再利用性を考慮した二重構造を設計し、無限スクロールを通じて月を探索するロジックと特定月の日付グリッドを生成するデータロジックまで実装しました。
これで基本的な形態を備えたカレンダーが成功裏に画面に現れるようになりました。もちろんまだ骨組みだけ備えた状態です。次の段階ではこの構造の上に実際の日程データを入出力して管理する機能を実装してカレンダーの本質的な役割を埋めていく予定です。
![[Dalendar DevLog 1] プロジェクトの始まりと技術スタック選定](/images/blog/dalendar_dev_1_ideation.png)
![[Dalendar DevLog 2] 堅固なアプリの骨組み - アーキテクチャとデータ構造設計](/images/blog/dalendar_dev_2_architecture.png)
![[Dalendar DevLog 3] コア機能実装 1 (バックエンド & ロジック)](/images/blog/dalendar_dev_3_math_logic.png)