GoF以外のプログラミング・デザインパターン #04 Flux / Redux

2025/11/25

プログラミング 学習

t f B! P L
eyecatch Flux/Reduxは、Facebookが提唱したアーキテクチャパターンです。 MVVMの派生で、Reactの基礎になっています。 この2つの違いも含めて解説してみたいと思います。

Fluxとは

MVCのような双方向データバインディングではなく、データの一方向フロー(unidirectional data flow)を重視しているパターンになっています。 アプリの状態管理をシンプルで予測可能にすることを目的にして、データの流れを一方向にすることで、デバッグしやすくするのが特徴です。

構成

[View] → [Action] → [Dispatcher] → [Store] → [View]
View UI部分(Reactコンポーネントなど) ユーザー操作によって Action を発行する Action 何が起きたかを表す「イベントオブジェクト」 例: { type: "ADD_TODO", text: "牛乳を買う" } Dispatcher すべてのActionを一元管理する「ハブ」 各Storeに対してActionを配信する Store アプリの状態(State)を保持 DispatcherからActionを受け取り、状態を更新 状態が変わるとViewに通知

データの流れ(Fluxサイクル)

1. ユーザー操作 → ViewがActionを発行  ↓ 2. Dispatcher がActionを各Storeへ配信  ↓ 3. Store が状態を更新  ↓ 4. Store → View に変更通知  ↓ 5. View が再描画

サンプルコード

// === Dispatcher === class Dispatcher { constructor() { this.listeners = []; } register(fn) { this.listeners.push(fn); } dispatch(action) { this.listeners.forEach(fn => fn(action)); } } // === Action === const Actions = { increment(dispatcher) { dispatcher.dispatch({ type: 'INCREMENT' }); } }; // === Store === class CounterStore { constructor(dispatcher) { this.count = 0; this.listeners = []; dispatcher.register((action) => { switch (action.type) { case 'INCREMENT': this.count++; this.emitChange(); break; } }); } getState() { return this.count; } subscribe(listener) { this.listeners.push(listener); } emitChange() { this.listeners.forEach(fn => fn()); } } // === View === const dispatcher = new Dispatcher(); const store = new CounterStore(dispatcher); store.subscribe(() => { document.getElementById('count').textContent = store.getState(); }); document.body.innerHTML = ` <h2>Flux Counter</h2> <div>Count: <span id="count">0</span></div> <button id="btn">+1</button> `; document.getElementById('btn').addEventListener('click', () => { Actions.increment(dispatcher); });

Reduxとは

Fluxをベースにした、よりシンプルで汎用的な状態管理ライブラリです。 Reactでよく使われますが、React専用ではありません。

Reduxの3原則

1. 単一のストア アプリ全体の状態は1つのオブジェクトツリーで管理
例: { todos: [...], user: {...} }
2. 状態は読み取り専用 直接変更せず、Actionを介してのみ更新 3. 変更は純粋関数(Reducer)で行う StateとActionを受け取り、新しいStateを返す 副作用を持たない

構成

[View] → [Action] → [Reducer] → [Store] → [View]
Action 状態変更の意図を表すオブジェクト Reducer 現在のStateとActionから新しいStateを計算する純粋関数 Store 状態(State)を保持 ActionをdispatchしてReducerに渡す View 状態を購読し、変更に応じて再レンダリング

Fluxとの違い

項目 Flux Redux
Store数 複数可 単一
状態管理 Storeが内部で管理 ReducerがStateを生成
Dispatcher あり 不要(dispatch関数で代替)
状態変更 Store内部で更新 不変のStateを新しく生成
概念 設計思想(アーキテクチャ) 実装ライブラリ

サンプルコード

// === Reducer === function counter(state = { count: 0 }, action) { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; default: return state; } } // === Store === const store = Redux.createStore(counter); // === View === document.body.innerHTML = ` <h2>Redux Counter</h2> <div>Count: <span id="count">0</span></div> <button id="btn">+1</button> `; // 状態が変わったらUI更新 store.subscribe(() => { document.getElementById('count').textContent = store.getState().count; }); // ボタン押下 → dispatch document.getElementById('btn').addEventListener('click', () => { store.dispatch({ type: 'INCREMENT' }); });

Canvas実装デモ

FluxとReduxのデータフロー構造の違いを、Canvasで可視化するデモです。 <canvas id="flowCanvas" width="800" height="400"></canvas> <script> const canvas = document.getElementById("flowCanvas"); const ctx = canvas.getContext("2d"); function drawBox(x, y, w, h, label, color) { ctx.fillStyle = color; ctx.strokeStyle = "#333"; ctx.lineWidth = 2; ctx.fillRect(x, y, w, h); ctx.strokeRect(x, y, w, h); ctx.fillStyle = "#fff"; ctx.font = "16px sans-serif"; ctx.textAlign = "center"; ctx.fillText(label, x + w / 2, y + h / 2 + 5); } function drawArrow(x1, y1, x2, y2, text) { const dx = x2 - x1, dy = y2 - y1; const angle = Math.atan2(dy, dx); const len = Math.sqrt(dx * dx + dy * dy); ctx.save(); ctx.translate(x1, y1); ctx.rotate(angle); ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(len - 10, 0); ctx.strokeStyle = "#000"; ctx.lineWidth = 2; ctx.stroke(); // 矢印先端 ctx.beginPath(); ctx.moveTo(len - 10, 0); ctx.lineTo(len - 20, -5); ctx.lineTo(len - 20, 5); ctx.closePath(); ctx.fill(); // ラベル ctx.fillStyle = "#000"; ctx.font = "12px sans-serif"; ctx.fillText(text, len / 2 - 15, -10); ctx.restore(); } // 描画クリア ctx.clearRect(0, 0, canvas.width, canvas.height); // === Flux側 === ctx.fillStyle = "#333"; ctx.font = "20px sans-serif"; ctx.fillText("🌀 Flux", 120, 40); drawBox(50, 80, 100, 60, "View", "#3b82f6"); drawBox(200, 80, 120, 60, "Action", "#f97316"); drawBox(380, 80, 140, 60, "Dispatcher", "#22c55e"); drawBox(580, 80, 120, 60, "Store", "#a855f7"); drawArrow(150, 110, 200, 110, "dispatch()"); drawArrow(320, 110, 380, 110, "dispatch"); drawArrow(520, 110, 580, 110, "update()"); drawArrow(580, 140, 100, 140, "notify → View"); // === Redux側 === ctx.fillStyle = "#333"; ctx.font = "20px sans-serif"; ctx.fillText("⚡ Redux", 120, 250); drawBox(50, 280, 100, 60, "View", "#3b82f6"); drawBox(200, 280, 120, 60, "Action", "#f97316"); drawBox(380, 280, 140, 60, "Reducer", "#22c55e"); drawBox(580, 280, 120, 60, "Store", "#a855f7"); drawArrow(150, 310, 200, 310, "dispatch()"); drawArrow(320, 310, 380, 310, "reduce()"); drawArrow(520, 310, 580, 310, "setState()"); drawArrow(580, 340, 100, 340, "subscribe → View"); </script>

表現しているポイント

パターン 特徴
Flux Dispatcherを中心に「複数のStore」へ流す。一方向のイベントループ構造。
Redux Dispatcherがなく、「1つのStore+Reducer」で管理。状態はImmutableで更新。

補足

Flux:データの流れが“イベント中心”で、複数のStoreが独立して存在できる。 Redux:状態の流れが“関数型思考”で、全状態をReducerが一括管理。

あとがき

MVVMをさらに使いやすくしたイメージのFlux/Reduxパターンでしたね。 Javascript特有のDOM各種のイベント設置とそれらを管理するのを効率的にするのがわかります。 実際にプログラミングをしていても、Webイベント管理は、よく迷子になりがちなので、こうしたデザインパターンを熟知して設計対応するのが良いですね。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ