RORK LABEN
MAX — Rork MaxはReact NativeではなくネイティブSwiftアプリを生成し、iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageに対応しますNATIVE — AR/LiDAR、Metalによる3D、ウィジェット、Dynamic Island、Live Activities、HealthKit、NFC、Core MLまで踏み込めますPUBLISH — App Storeへの公開を2クリックで実行でき、個人開発の申請作業を大きく短縮できますSWIFT — 自然文で指示するだけで動くコードが生成され、iOS・Android・Webのアプリを素早く形にできますGROWTH — Rorkは直近で1,500万ドルを調達。月間743,000訪問・成長率85%と伸びていますPRICING — 無料で開始でき、有料プランは月25ドルから。Rork Maxは月200ドルですMAX — Rork MaxはReact NativeではなくネイティブSwiftアプリを生成し、iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageに対応しますNATIVE — AR/LiDAR、Metalによる3D、ウィジェット、Dynamic Island、Live Activities、HealthKit、NFC、Core MLまで踏み込めますPUBLISH — App Storeへの公開を2クリックで実行でき、個人開発の申請作業を大きく短縮できますSWIFT — 自然文で指示するだけで動くコードが生成され、iOS・Android・Webのアプリを素早く形にできますGROWTH — Rorkは直近で1,500万ドルを調達。月間743,000訪問・成長率85%と伸びていますPRICING — 無料で開始でき、有料プランは月25ドルから。Rork Maxは月200ドルです
記事一覧/開発ツール
開発ツール/2026-07-12上級

取り消しを押しても戻らない——編集履歴を安全に巻き戻すUndo/Redoの設計

状態を丸ごと保存するUndoは、数十手でメモリを圧迫します。コマンド履歴とスナップショットを使い分け、微調整の合体・上限・セッション跨ぎの永続化まで備えたUndo/Redoを、動くTypeScriptで設計します。

undo-redoreact-native12expo11architecture6state-management

プレミアム記事

壁紙を合成するアプリで、ユーザーが背景の色を少しずつ調整し、ステッカーを10個ほど並べ、最後にうっかり全消去のボタンに触れてしまった——というレビューをいただいたことがあります。慌てて取り消しを押したものの、戻ってきたのは全消去の直前ではなく、なぜか5手前の中途半端な状態でした。「取り消しが効かないなら、もう触るのが怖い」。短い一文でしたが、胸の奥がひやりとしました。

私自身、個人開発で編集操作のあるアプリをいくつか運用しています。Undo/Redo は「あればいい機能」ではなく、ユーザーが安心して手を動かせるかどうかを決める土台だと考えるようになりました。そして実装してみると、素朴な作り方ほど後で落とし穴になることも、身をもって知りました。

React Native(Expo)で編集系アプリの Undo/Redo を、長く運用に耐える形で設計していきます。動く TypeScript を軸に、メモリの圧迫・微調整の扱い・アプリ再起動後の履歴保持という、本番運用で必ずぶつかる三つの壁を越えていきます。

なぜ「状態を丸ごと保存する」Undoは破綻するのか

最初に思いつくのは、操作のたびに状態全体をコピーして配列に積む方法です。取り消しは一つ前のコピーに戻すだけ。実装は数十行で済みます。

ところが編集系アプリでは、この素朴さがそのまま落とし穴になります。壁紙の合成状態が、画像のトリミング情報・数十個のレイヤー・フィルタのパラメータを含むとしましょう。一つの状態が仮に80KBだとして、ユーザーが100手編集すれば、履歴だけで8MBを抱えます。スライダーを動かすたびに1手と数えれば、数分の編集で軽く超えていきます。

もう一つの問題は「何が変わったのか」が履歴に残らないことです。取り消しボタンに「色の変更を取り消す」と出したくても、丸ごとのコピーからは差分が読み取れません。

方式メモリ差分の意味向く場面
スナップショット(状態全体)大きい(状態サイズ×手数)残らない状態が小さい・操作が少ない
コマンド(操作の差分)小さい(差分サイズ×手数)残る(ラベル化できる)編集が続く・履歴が深い

私は、編集が長く続くアプリではコマンド方式を基本に据えることを推奨します。ただしスナップショットも捨てません。後述するセッション跨ぎの永続化では、むしろスナップショットが効いてきます。両者は敵対しません。役割が違うだけです。

コマンドを最小単位にする

コマンドとは「実行する(do)」と「取り消す(undo)」の対を持つ、操作の一単位です。差分だけを抱えるので軽く、ラベルを添えられます。まずは型で輪郭を決めます。

// 一手ぶんの操作。do と undo は対称であること。
export interface Command {
  readonly label: string;          // 「色の変更」など、UIに出せる名前
  do(): void;                      // 前へ進める
  undo(): void;                    // 一手戻す
  // 直前の同種コマンドと合体できるなら、合体後のコマンドを返す
  coalesceWith?(prev: Command): Command | null;
}

具体例として、レイヤーの位置を動かすコマンドを書きます。差分(移動前と移動後の座標)だけを保持している点に注目してください。

export function moveLayerCommand(
  store: EditorStore,
  layerId: string,
  from: Point,
  to: Point,
): Command {
  return {
    label: "レイヤーの移動",
    do: () => store.setLayerPosition(layerId, to),
    undo: () => store.setLayerPosition(layerId, from),
    coalesceWith: (prev) => {
      // 同じレイヤーを連続で動かしているなら、始点を引き継いで一手に束ねる
      const p = prev as ReturnType<typeof moveLayerCommand>;
      if (p.label !== "レイヤーの移動") return null;
      return moveLayerCommand(store, layerId, (p as any)._from ?? from, to);
    },
  };
}

ここで大切なのは、コマンドが状態の「置き場所」を知っているものの、状態そのものを丸ごと抱えないことです。抱えるのは変化した値だけ。これがメモリを軽く保つ肝になります。

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

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
状態全体を積むUndoがメモリを食い潰す理由と、コマンド差分で軽くする実装
連続する微調整を一手にまとめる合体(coalescing)の判定と、境界の見極め
アプリがバックグラウンドで破棄されても履歴を失わない、上限付き永続化の設計
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

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

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

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

関連記事

開発ツール2026-06-23
オフラインで書いた投稿が、復帰した瞬間にもう一つ増える——再送に強い送信アウトボックスの設計
永続キューで再送するだけでは、失われた応答が二重投稿を生みます。冪等キー・一時IDの付け替え・毒メッセージの隔離を備えた送信アウトボックスを、動くTypeScriptで設計します。
開発ツール2026-05-23
Rork 生成プロジェクト特有の expo start --offline forbidden — テンプレ構成が原因の4パターン
Rork が生成するテンプレート構成が原因で expo start --offline が forbidden になる4つのパターン(tsconfigPaths・expo-router キャッシュ・ネイティブプリビルド・lock 不一致)を、Rork プロジェクト固有の観点で切り分けます。汎用的な Expo 側の対処(プロキシ403・依存検証)は別記事にまとめています。
開発ツール2026-05-20
iOS の 0x8badf00d ウォッチドッグ強制終了に Rork アプリが落ちるときの対処
Rork で作った iOS アプリが起動直後に消える。クラッシュログに 0x8badf00d。Apple のウォッチドッグが React Native の重い初期化を切ったときの正しい直し方を、私の運用中アプリで実際に効いた手順で説明します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →