RORK LABEN
TOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しましたTOOLING — Rorkの開発者向けリポジトリが動き続けています。rork-xcodeが7月16日、rork-deviceが7月15日、rork-plistが7月13日に更新されましたOPUS46 — RorkでClaude Opus 4.6が稼働しています。Rork MaxはClaude Codeを土台にアプリを組み立てる設計ですSIM — ブラウザ上で動くクラウドのiOSシミュレータを備え、実機へのインストールは1クリック、App Storeへの提出は2クリックと案内されていますMAX — Rork MaxはReact Nativeではなく純粋なSwiftを出力します。iPhone・iPad・Apple Watch・Apple TV・Vision Pro、そしてiMessageまでが射程ですNATIVE — HealthKit、ARKitとLiDAR、NFC、Dynamic Island、Live Activities、Metalによる3D、Core MLのオンデバイス推論まで扱えますSEED — RorkはLeft Lane Capitalが主導する1,500万ドルのシードラウンドを実施し、Peak XVとa16z Speedrunが参加しました
記事一覧/開発ツール
開発ツール/2026-05-13中級

RorkアプリでZustand v5を導入したら、Context APIで詰まっていた再レンダリング問題が解消した話

RorkアプリでContext APIを使い続けたら再レンダリングが多発。Zustand v5への移行で解決した経緯と、v4からの変更点・実践的な実装パターンを紹介します。

Rork515Zustand4状態管理11React Native209パフォーマンス30個人開発186

Rorkでアプリを作り始めたころ、状態管理には React の useContextuseState を組み合わせたシンプルな方法を使っていました。最初はそれで十分でした。画面が2〜3枚なら、Context APIで問題は起きません。

ところがスクリーン数が8枚を超えたあたりから、妙なことが起き始めました。ある画面でユーザーのプロフィール情報を更新すると、直接関係のない別の画面でも不要な再レンダリングが走る。デバッグしていくと、Context Providerの子孫コンポーネントが丸ごと再レンダリングされていることが分かりました。

そのとき、累計500万ダウンロードを超えたアプリ群を個人で開発・運営してきた経験から、「これは後で大きくなるほど辛くなるパターンだ」と直感的に分かりました。2014年から個人開発を続けてきた中で、初期の設計の甘さが後から雪だるま式に問題になっていく経験を何度もしてきたので。

Zustand v5を導入して1週間後、再レンダリングの問題はほぼ解消しました。その移行の経緯とZustand v5の実践的な使い方を実例とともに整理しました。

Context APIのどこが問題だったか

Context APIを使った状態管理の典型的なパターンはこんな形です。

// ❌ よくある問題パターン
const UserContext = createContext(null);
 
export function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  const [settings, setSettings] = useState({});
  const [notifications, setNotifications] = useState([]);
 
  return (
    <UserContext.Provider value={{ user, setUser, settings, setSettings, notifications, setNotifications }}>
      {children}
    </UserContext.Provider>
  );
}

このパターンの問題は、usersettingsnotifications のいずれかが変わるたびに、UserContext.Provider全ての子孫コンポーネント が再レンダリングされる点です。通知の件数が変わっただけで、ユーザープロフィールを表示しているコンポーネントも再描画されてしまう。

小さなアプリなら気にならないですが、画面数が増えると、フリーズとまではいかなくても「なんかもっさりするな」という体験劣化につながります。

Zustand v5とは、なぜRorkアプリに向いているか

Zustand(ドイツ語で「状態」)は、Reactのグローバル状態管理ライブラリの中でも軽量でシンプルな設計が特徴です。v5(2024年10月リリース)では、useSyncExternalStore を内部で使うようになり、React 18以降の並行モード(Concurrent Mode)と適切に動作するようになりました。

React Native / Rorkアプリに向いている理由はシンプルです。

  • 選択的な購読(selective subscription): 必要な状態だけを購読できるため、無関係な更新で再レンダリングされない
  • Provider不要: コンポーネントツリーをProviderで囲む必要がなく、どこからでも状態にアクセスできる
  • 非同期処理がシンプル: Thunkやmiddlewareなしに非同期ロジックをstoreの中に書ける
  • TypeScriptとの相性が良い: 型推論が自然に効く

インストールと基本的な使い方

Rorkプロジェクトへのインストールはシンプルです。

npx expo install zustand

v5の基本的なstoreの書き方はこうなります。

// store/useUserStore.ts
import { create } from 'zustand';
 
type User = {
  id: string;
  name: string;
  email: string;
};
 
type UserStore = {
  user: User | null;
  isLoading: boolean;
  setUser: (user: User | null) => void;
  fetchUser: (userId: string) => Promise<void>;
};
 
export const useUserStore = create<UserStore>((set) => ({
  user: null,
  isLoading: false,
 
  setUser: (user) => set({ user }),
 
  fetchUser: async (userId) => {
    set({ isLoading: true });
    try {
      const response = await fetch(`https://api.example.com/users/${userId}`);
      const data = await response.json();
      set({ user: data, isLoading: false });
    } catch (error) {
      set({ isLoading: false });
      console.error('fetchUser failed:', error);
    }
  },
}));

コンポーネントからの使い方は直感的です。

// screens/ProfileScreen.tsx
import { useUserStore } from '../store/useUserStore';
 
export default function ProfileScreen() {
  // ⭐ userだけを選択購読 — isLoadingが変わっても再レンダリングされない
  const user = useUserStore((state) => state.user);
  const fetchUser = useUserStore((state) => state.fetchUser);
 
  // ...
}

(state) => state.user という セレクタ関数 がポイントです。このコンポーネントは user が変わったときだけ再レンダリングされ、isLoading の変化では再レンダリングされません。Context APIとの決定的な違いはここにあります。

v4からv5への主な変更点

Zustand v4を使ったことがある方向けに、v5で変わった部分をまとめます。

create の挙動が変わった

v4では set の第二引数に true を渡すと state を完全に置き換えできました。v5では明示的に replace: true と書く必要があります。

// v4の書き方(v5でも動くが非推奨)
set({ user: null }, true);
 
// v5の推奨
set({ user: null }, { replace: true });

subscribeWithSelector がコア統合

v4では subscribeWithSelector をmiddlewareとして追加する必要がありましたが、v5では subscribe が直接セレクタをサポートします。

// v5: セレクタを直接指定して購読
const unsubscribe = useUserStore.subscribe(
  (state) => state.user?.id,
  (userId) => {
    console.log('ユーザーIDが変わりました:', userId);
  }
);

③ TypeScriptの型推論が改善

v5では create<UserStore>()() の二重括弧が不要になり、型推論がよりスマートになりました。TypeScriptで書く場合の記述量が少し減っています。

実践パターン:認証状態の管理

RorkアプリでZustandを使う典型的なパターンとして、認証(Auth)状態の管理を紹介します。

// store/useAuthStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
 
type AuthState = {
  token: string | null;
  isAuthenticated: boolean;
  signIn: (token: string) => void;
  signOut: () => void;
};
 
// persist middlewareでAsyncStorageに永続化
export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      isAuthenticated: false,
 
      signIn: (token) => set({ token, isAuthenticated: true }),
 
      signOut: () => set({ token: null, isAuthenticated: false }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

この実装では、persist middlewareを使うことでアプリを再起動してもログイン状態が保持されます。AsyncStorage をストレージとして指定しているので、React Nativeの環境でそのまま動きます。

アプリのルートでの使い方はこうなります。

// app/_layout.tsx(Expo Router)
import { useAuthStore } from '../store/useAuthStore';
import { Redirect } from 'expo-router';
 
export default function RootLayout() {
  const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
 
  if (!isAuthenticated) {
    return <Redirect href="/login" />;
  }
 
  return <Slot />;
}

Provider不要でルートのレイアウトから状態を読み取れるのは、Rorkで Expo Router を使ったナビゲーション設計 を構築するうえで特に便利です。

Context APIとZustand、どちらを選ぶべきか

「じゃあもうContextは使わなくていい?」という疑問への答えは「ケース・バイ・ケース」です。

Contextが適しているケース:

  • テーマ(ダークモード/ライトモード)のような静的に近い設定値
  • コンポーネントツリーの一部分だけで使うローカルな状態
  • サードパーティライブラリが提供するProviderパターン

Zustandが適しているケース:

  • ユーザー認証状態、カート内容のようなグローバルかつ頻繁に変わる状態
  • 複数の画面から参照・更新される状態
  • 非同期API呼び出しを含む状態ロジック

私の場合、現在は「Zustand for グローバル状態、Context for テーマ・i18n・ライブラリProvider」という分け方に落ち着いています。TanStack QueryとZustandを組み合わせる パターンも実用的で、サーバーデータはTanStack Query、クライアントのUI状態はZustandという役割分担がきれいに機能します。

移行するなら少しずつが安全

既存のContext APIをいきなり全部Zustandに書き換えるのはリスクがあります。私がお勧めするのは、「最も再レンダリングが多発している状態から1つずつ移行する」方法です。

  1. React DevTools(またはRork CompanionのパフォーマンスパネルでのFlipperを活用)で不要な再レンダリングを特定
  2. 原因となっているContextを1つ選んでZustandに移行
  3. 移行後の動作を確認してから次へ

Rorkアプリの規模が大きくなってきたら、状態管理の見直しは避けて通れません。Zustand v5は、個人開発者が一人で管理できる軽さと、スケールしたときに耐えられる設計のバランスが取れたライブラリだと感じています。

まずは認証状態の管理だけZustandに移行してみると、効果を実感しやすいと思います。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

開発ツール2026-07-14
AI再生成に壊されない設計 — Rork の生成コードと手書きロジックを分ける境界の引き方
Rork へ追加指示を出すたびに手書きのロジックが消える。プロンプトで守るのは対症療法です。生成コードと自分の所有コードを層で分け、再生成が届かない境界を設計で引く方法を、Zustand と service 層の実装例つきでお伝えします。
開発ツール2026-07-10
React Compiler を Expo に入れて、手書き memo を 41 箇所消しました
Rork が生成した React Native 画面に React Compiler を導入し、再レンダリング回数を Profiler で実測しました。手書き memo/useCallback の削除判断、効かない箇所の見分け方、CI での回帰検知までをまとめています。
開発ツール2026-07-04
「続きを読む」を出すべきか、テキストの実測で決める — Rork(Expo)の折りたたみ表示設計
商品説明やレビュー本文を3行で畳み「続きを読む」を出す実装で、短い文にまでトグルが付いてしまう問題を解決します。onTextLayout で実際の行数を測り、はみ出す時だけトグルを出す設計を、iOS と Android の差、展開アニメーション、文字サイズ拡大への配慮まで動くコードでまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →