😽

Claude Codeのlearningモードと一緒にミリしらReact Nativeでモバイルアプリ作ってみた

に公開

Claude Codeのlearningモード、知っていますか?
使ったことはありますか?

「AIに実装を任せるようになって、仕事の楽しさの大部分が失われてしまった……😢」

そんな悲しみを抱えながらITエンジニアとして今日も仕事をしている、そこのあなた。
今こそ個人開発しましょう、Claude Codeのlearningモードと一緒に。

はじめに

私は普段は主にFlutterでモバイルアプリの開発をしています。
技術選定を自分でしたわけではなく、既存のFlutterプロジェクトに途中参画するところから自分のモバイルアプリ開発のキャリアが始まったので、Flutter以外のモバイル用フレームワークは触ったことがありませんでした。

業務の中でReactで作られたWebアプリへ機能追加をすることもあり、モバイル用FWのReact Nativeの方も触ってみたいなーと思っていました。
そこで、同じく気になっていたClaude Codeのlearningモードを使って、どんなふうに学びながらアプリを作れるのかを今回試してみることにしました。

  • learningモードってどんな感じ?
  • Flutter開発者から見たReact Native

あたりを書いていきたいと思います。

前提:Claude Codeのlearningモードとは?

Claude Codeの出力スタイルの一つです。

Learning: 協調的な学習型モードです。Claude はコーディング中に「Insights」を共有するだけでなく、小さな戦略的なコードの一部を自分で実装するよう求めます。Claude Code はコード内に TODO(human) マーカーを追加して、実装するべき箇所を示します。

引用元:https://code.claude.com/docs/ja/output-styles

とあるように、Claudeがソースコードの実装を全て行うのではなく、ユーザーが自分で実装すべき部分を一部残す形で出力してくれます。

設定方法等は上記のドキュメントに記載されているので、気になる方はご覧ください。

Claudeの出力スタイルをlearningに変更し、いざ、初めてのReact Nativeアプリ開発に入ります。

Claudeと相談しながらのアプリ開発開始

まずはFlutterの話題でClaudeの知識レベルを測る

私はReact Nativeを全く触ったことがなく、状態管理やデータの管理もどのようにすればいいか分からない状態でした。
Claudeに聞いても回答が妥当なのかを判別できないと思ったので、まずは自分の知っている領域の質問をして、CLAUDE.md等の前提知識を何も渡していない状態のClaudeの知識レベルがどんな感じかを確認してみました。

自分
Flutterでモバイルアプリを開発する際、状態管理はどうするのが良いですか?
Claude
Flutterの状態管理は規模と複雑さによって選択肢が変わります。

(中略)

選び方の目安

┌────────────────────────┬──────────────────────┐
│          規模          │         推奨         │
├────────────────────────┼──────────────────────┤
│ 個人・小規模           │ Riverpod か Provider │
├────────────────────────┼──────────────────────┤
│ 中規模・チーム         │ Riverpod             │
├────────────────────────┼──────────────────────┤
│ 大規模・複雑なロジック │ Bloc                 │
└────────────────────────┴──────────────────────┘

現在のトレンド

Riverpod が最もバランスが良いため、迷ったらこれが無難です。flutter_riverpod + riverpod_annotation でコード生成を使うと記述量も減ります。

今開発しているアプリの規模感や要件があれば、より具体的にアドバイスできます。

概ね妥当なことを言っていそう。
これならReact Nativeの知識もある程度信頼できそうだと判断し、React Nativeについても相談しました。

React Nativeの状態管理を相談しながら決めていく

自分
React Nativeでモバイルアプリを開発する際、状態管理はどうするのが良いですか?
Claude
★ Insight ─────────────────────────────────────
React Nativeの状態管理は「状態の種類」で使い分けるのが鍵。すべてをグローバルに管理しようとするのが最大の失敗パターン。
─────────────────────────────────────────────────
(中略)

推奨構成(2025年時点)

ローカル状態: useState/useReducer
サーバー状態: TanStack Query
グローバル状態: Zustand

Reduxは大規模チームや既存資産がある場合以外は不要になりました。

このプロジェクト(nikki)の場合

日記アプリであれば:
- エントリー一覧/詳細 → TanStack Query(ローカルDB連携ならWatermelonDBと組み合わせ)
- 認証・ユーザー設定 → Zustand
- 入力フォーム → useState で十分

アプリの規模感や使う予定のバックエンド(Firebase、Supabaseなど)が分かれば、より具体的な構成を提案できます。

★ Insight が初登場。
learningモードだと、こんな感じで適宜開発のヒントをくれます。
今回はReact Nativeに限らず「せやな」という感じの内容です。

続けて相談し、今回作成したいアプリに適した状態管理について決めていきます。

自分
バックエンドを持たない、ローカルだけで動くアプリにする予定です。
日記エントリーはSQLiteを使ってローカルDBに保存します。ユーザー認証は持たない予定です。
Claude
★ Insight ─────────────────────────────────────
ローカルSQLiteアプリでは「サーバーレスのサーバー状態」という考え方がポイント。SQLiteへのクエリもネットワークAPIと同様に非同期で失敗しうるため、TanStack Queryの恩恵を受けられる。
─────────────────────────────────────────────────

このアプリに最適な構成

SQLite層:    expo-sqlite + Drizzle ORM(型安全なクエリ)
データ取得:  TanStack Query(ローカルSQLiteを"サーバー"扱い)
グローバル:  Zustand(現在の画面状態、フィルターなど)
ローカル:    useState(フォーム入力)

なぜこの組み合わせか

(後略)

ローカルSQLiteアプリでは「サーバーレスのサーバー状態」という考え方がポイント

このInsightはこれまで整理できていなかった視点だったので、勉強になりました。
データ取得元がローカルDBになろうとAPIになろうと問題ないよう疎結合にアプリケーションを構築する、というのは普段の開発時も意識していましたが、改めて考えると上記のような見方ができますね。

軽く調べてみて問題なさそうだったので、DB周りはClaudeが提案してくれた構成にすることにしました。

Claudeに自分の背景情報を共有してみる

セッションを続けたまま、アーキテクチャの検討に入ります。

Flutterではレイヤードアーキテクチャでの開発に慣れていたので、React Nativeでも可能であればそうしたい。
ということで、次からは自分の背景情報も渡しつつ相談していくことにしました。

自分
次はアーキテクチャを考えたいと思います。
私はFlutterエンジニアであり、使い慣れているのはレイヤードアーキテクチャです。
状態管理にはRiverpodを使用していました。

今回、React Nativeを全く知らない状態で、学習しながらこのアプリを作っていきたいと思っています。
React Nativeでもレイヤードアーキテクチャを実現することは可能でしょうか?
Flutterで開発していたときは、(以下略)
Claude
まずユーザー情報をメモリに保存します。

  Wrote 2 memories

★ Insight ─────────────────────────────────────
React Nativeはアーキテクチャを強制しない。Flutterはlib/の慣習がある程度共有されているが、RNは完全に自由なので、逆に「自分でルールを決める」必要がある。レイヤードアーキテクチャを持ち込むのは良い判断。
─────────────────────────────────────────────────

FlutterのレイヤーをReact Nativeに対応させる

(中略)

Riverpodとの対応関係

Riverpodで慣れた考え方がそのまま使えます:

(中略)

FutureProvider ≈ useQuery、ref.invalidate() ≈ queryClient.invalidateQueries() という対応です。

まずユーザー情報をメモリに保存します。

とあったとおり、ここで伝えた情報を記憶してくれて、次回以降のセッションでも引き継いでくれました。
以降、質問した際などにFlutterの話を出さなくてもFlutterと比較しながら回答してくれるようになりました。

メモリファイルは ~/.claude/projects/<project>/memory/ 配下に.mdファイルとして保存されます。

このあとアプリの画面仕様とアーキテクチャの注意点を伝え、その情報もメモリファイルに保存してもらいました。
「この情報をセッションを跨いでも引き継げるようにして」と指示することで、Claudeが.mdファイルを作って情報を保存してくれます。

画面仕様を伝える際には画面のスクリーンショットも渡しましたが、スクショのパスや画像ファイルは記憶されていないようでした。

個人で小さなアプリを作るレベルの規模感であれば、CLAUDE.mdや各種スキルの整備までは行わなくても、こういった基本情報を記憶してもらうだけで十分快適に開発ができました。

進捗管理もClaudeに任せてみた

その日の作業を終了する際、次に再開する際に進捗状況を忘れているだろうと思ったので、進捗状況もClaudeに記憶してもらうことにしました。
進捗状況を保存して、と毎回伝えるのも面倒だったので、進捗保存用コマンドを作ってセッション終了前に呼ぶようにしています。

今回の場合、下記のようなファイルでの進捗管理となりました。

PROGRESS.md
# 日記 - 設計・進捗メモ

(## アプリ概要、画面構成、アーキテクチャなど。略)

## タスク進捗

### 完了済み ✅
- [x] `src/infrastructure/db/schema.ts` — diariesテーブル定義
- [x] `src/infrastructure/db/client.ts` — expo-sqlite接続
- [x] `src/infrastructure/db/migrations.ts` — アプリ用マイグレーション設定
- [x] `src/domain/diary.ts` — Diary / NewDiary 型定義
- [x] `src/repositories/diaryRepository.ts` — Repositoryインターフェース
- [x] `src/repositories/sqliteDiaryRepository.ts` — Repository実装(findByDate / findByYearMonth)
- [x] `app/_layout.tsx` — useMigrations + QueryClientProvider + 全画面登録
- [x] `src/hooks/diary.ts` — useTodayDiary / useDiariesByMonth
- [x] `src/hooks/diary.ts` — useCreateDiary / useUpdateDiary の実装
- [x] `app/index.tsx` — 起動時フロー(useTodayDiaryでウェルカム or 一覧へ振り分け)
- [x] `src/presentation/styles/colors.ts` — カラー定数
- [x] `src/presentation/components/FilledButton.tsx` — 塗りつぶしボタン
- [x] `src/presentation/components/OutlineButton.tsx` — 枠線ボタン
- [x] `app/welcome.tsx` — ウェルカム画面(Ionicons + FilledButton + OutlineButton)
- [x] `app/entry.tsx` — 日記入力画面(新規・編集共用、バリデーション・エラートースト含む)
- [x] `src/hooks/diary.ts` — `diaryKeys.date()` 追加
- [x] `app/list.tsx` — 日記一覧画面(月切り替え・全日表示・プレビュー・鉛筆アイコン編集ボタン、未記入日も編集可)

### 残タスク 🔲

#### 動作確認(**今ここ**)
- [ ] 実機 or シミュレーターでアプリ起動して全画面の動作確認

タスク進捗のところもClaudeが[ ]から[x]に勝手に変更してくれるので、自分で進捗管理ファイルを編集する必要がなく、そういった細かい管理作業でMPを消費しがちな自分は非常に気楽に開発に当たることができました。

VS Codeを開いてClaudeに「次は何すればいいんだっけ?」と聞くだけで作業を再開できる。
そう思うだけで作業に取り掛かる際の億劫さが激減するのを感じました。

learningモードの良かったところ

ここからは、実際にlearningモードで開発をしていく中で、良いなと思ったところを書いていきます。

TODO部分を実装していくのが楽しい!!!!

Claudeに実装を指示すると、下記のように、一部をサンプル実装した上で、TODOとして人間が実装すべき部分を示してくれます。

OutlineButton.tsx
import { DimensionValue } from 'react-native';
import { colors } from '../styles/colors';

type Props = {
  label: string;
  onPress?: () => void;
  backgroundColor?: string;
  borderColor?: string;
  textColor?: string;
  width?: DimensionValue;
};

export function OutlineButton({
  label,
  onPress = () => {},
  backgroundColor = colors.base,
  borderColor = colors.primary,
  textColor = colors.primary,
  width = '100%',
}: Props) {

// TODO(human): FilledButton を参考に、枠線ボタンのコンポーネント本体を実装してください

}
他の実装と指示した内容
FilledButton.tsx
import { DimensionValue, Pressable, StyleSheet, Text } from 'react-native';
import { colors } from '../styles/colors';

type Props = {
  label: string;
  onPress?: () => void;
  backgroundColor?: string;
  textColor?: string;
  width?: DimensionValue;
};

export function FilledButton({
  label,
  onPress = () => {},
  backgroundColor = colors.primary,
  textColor = colors.onPrimary,
  width = '100%',
}: Props) {
  return (
    <Pressable
      onPress={onPress}
      style={[styles.button, { backgroundColor, width }]}
    >
      <Text style={[styles.label, { color: textColor }]}>{label}</Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  button: {
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
  },
  label: {
    fontSize: 16,
    fontWeight: '600',
  },
});
指示した内容
塗りつぶしボタンと枠線ボタンを、 @src/presentation/components に作ってください。
  - 塗りつぶしボタン
    - 外から渡せるもの
      - 背景色、文字色、横幅?、押下時のコールバック
    - デフォルト値
      - 背景色:colors.primary
      - 文字色:colors.onPrimary
      - 横幅:画面横幅いっぱい
      - コールバック:特に処理なし
  - 枠線ボタン
    - 外から渡せるもの
      - 背景色、枠色、文字色、横幅?、押下時のコールバック
    - デフォルト値
      - 背景色:colors.base
      - 枠色:colors.primary
      - 文字色:colors.onBase
      - 横幅:画面横幅いっぱい
      - コールバック:特に処理なし

このTODOを実装していくのがとっても楽しい!

アプリの全てを手動で実装していくと、どうしても「面倒くさいな……」と感じる部分が多々出てきてしまう。
しかし一方で全てをAIに任せると、それはそれでつまらない。

Claude Codeのlearningモードなら、適度に開発の導線を整えつつ、自分でコードを書いて動かす楽しさもしっかり用意してくれます。
"learning" モードなので、楽しさではなく学習機会を提供するための機能だとは思いますが、この用意されたTODO部分を実装していくのは明確に楽しかったです。

仕事では生産性のために切り捨て、これも時代の流れかと諦めていた『パズルを解くような実装の面白さ』を、learningモードでは適度に味わうことができる。
そのうえ手動では実現できないスピードでアプリが構築されていく……。

楽しい……すごく楽しい😭

他のコーディングエージェントでも設定次第で似たようなことができるかもしれませんが、外的な強制力が何もない趣味の開発においては、自分で何も調整しなくても自動で良きに計らってくれるlearningモードの手軽さはとてもありがたかったです。
進捗管理の楽さもあって、毎日退勤後にClaudeとアプリ開発するのが楽しみになっていました。

全部を自分で実装しなくても結構理解が深まる

TODO部分を実装するだけでもわからないところや気になるところがたくさん出てくるので、その都度Claudeに質問していきました。
ただAIの出力したソースコードを眺めるよりも、自分での実装のフェーズがあることで、より自分の知識・理解が深まると感じました。

TODO以上の工夫に気づいてくれるのが嬉しい

TODOで指示された内容を超えてエラーハンドリングなどを盛り込んで実装してみたところ、Claudeがそれを把握して褒めてくれたのも良かったです。

Claude
実装の内容をまとめると:

┌──────────────────────────────────────┬────────────────────────────────────────────────────┐
│               実装内容               │                        評価                        │
├──────────────────────────────────────┼────────────────────────────────────────────────────┤
│ バリデーション(!happiness1.trim()) │ ! が抜けていたが概念は正しかった                   │
├──────────────────────────────────────┼────────────────────────────────────────────────────┤
│ create/update の振り分け(if/else)  │ ガイダンスの三項演算子より明示的でむしろ読みやすい │
├──────────────────────────────────────┼────────────────────────────────────────────────────┤
│ mutateOptions に onError を追加      │ 指示になかった部分を自分で拡張できている           │
├──────────────────────────────────────┼────────────────────────────────────────────────────┤
│ エラートースト用の state とJSXを追加 │ 設計を自分で拡張できている                         │
└──────────────────────────────────────┴────────────────────────────────────────────────────┘

isAllValidLength は TextInput の maxLength={MAX_CHARS} が既に入力を制限しているので実際には300文字を超えることはありませんが、ロジックとして間違いではありません。

次は /list 画面に進みますか?

後輩の成果物のレビューをする際、仕事を楽しいと感じて主体性を持てるようになったらいいなと思いながら、良かったところや以前と比べて成長したところを伝えるようにしていたな、というのを思い出しました。
やっぱりこうやって気づいて褒めてもらえるのは嬉しいですね。

ただ、三項演算子云々の部分はサンプル実装より自分の実装の方が無駄が多いなと感じたので、気持ちよさは受け取りつつ、言われた内容を過信しすぎずに自分でちゃんと見直すのは大事だなと思いました。

Insightも結構役立つ

これまで提示してきた出力例のほか、FlutterではこうするがReact Nativeではこうする、というような比較もInsightでくれることが多く、React Nativeでの実装の理解に役立ちました。
基本自分の知らないことをClaudeに聞いて、その回答の際にInsightをくれることが多かったので、「そんなこと当然知ってるよ」という内容が出力されることはほとんどなかったです。

Flutter開発者から見たReact Nativeの印象

ここからは、Flutterだけでモバイルアプリ開発を続けてきた立場から見たReact Nativeの印象を書いていきたいと思います。

フロントエンド開発者が0からモバイルアプリ開発に入るなら、Flutterよりも学習コストは低そう

下記の点から、React等の経験がある人にはFlutterよりもReact Nativeの方がとっつきやすいだろうなと感じました。

  • TypeScriptで実装できる
  • UIの構築もReact等と同様にHTMLタグのような形で行える
  • Reactで使用していたライブラリが使える場合がある(状態管理やORMなど)

Flutterになると、まずDart/Flutterの記法に慣れないといけないし、ウィジェットという独自概念でUIが構築されているし、ライブラリもTS系のFWとは異なるものが多いしで、越えないといけないハードルが結構あるだろうな、という印象です。

モバイルアプリ開発者がフロントエンドに領域を広げる際の入り口にもなりそう

同様に、モバイル→フロントの入り口としても良いなと感じました。
Expo Routerを使えばNext.js等と同様にファイルシステムがそのままルーティングとなるので、フロントエンドのソースコードを読む際にもディレクトリ構造を把握しやすくなると思います。

自分がフロントエンドのコードを読む際に一番やりづらいなと思うのが「どこに何があるのかよくわからん」という状況なので、React Nativeで同じようなディレクトリ構成に慣れることができるのはメリットだと感じました。

Expo Goは上手く使えなかった

Expo Goを使って実機確認が簡単にできるらしい、という前情報を持っていたので、出先でも簡単に実機デバッグができるのかな、と開発に入る前は期待していました。

ただ、調べてみるとExpo Goはプロダクトレベルのものには使うべきではなく、プッシュ通知などの一部のネイティブ機能を扱うこともできないということがわかりました。

Expo Goの代わりにExpo Dev Clientという方式を使えば、Expo Goと同様にスマホでQRコードを読み込んで実機確認ができるということだったので、私は仕事で使う場合を想定して、Expo Dev Clientの方を試してみることにしました。

しかし、自分の環境ではQRコードを読み込んでも接続タイムアウトになってしまい、アプリの正常な画面を表示することは叶いませんでした。
Claudeに聞いたところ、Wi-Fiルーターか何かの設定の影響で、同じネットワーク内の端末同士で通信をすることが許可されていないことが原因のようだったのですが、解決方法がぱっとわからなかったため、結局実機デバッグは有線接続して行うことにしました。

自分のメイン端末はAndroidなので実機デバッグも簡単にできましたが、iOSだとプロビジョニングプロファイル等の用意をしなければならないので、その分手間が増えてしまいます。
Expo Dev Clientを使いこなしてiOSでも簡単に実機確認できるよう、機会があればもう少し調査したいと思います。

まとめ

learningモード、すっごく楽しいよ!!!!

学習をしたい新人エンジニアだけでなく、自分は実装を楽しめるからIT業界向いてるな、と考えてITエンジニアを続けてきたような方にもぜひお勧めしたいです。
個人開発もAIのおかげで以前と比べて取り組むハードルがかなり下がったので、今までいろいろ面倒で食指が動かなかった方も、個人開発でClaude Code learningモードと一緒に楽しく実装しよう!

(あとはアプリストアでの個人開発者の住所丸出し問題がどうにかできればもっと良いのですが、ストア公開せず自分のスマホに入れて使うだけでも十分楽しめると思います!)

レバテック開発部

Discussion