ほんとかどうかわかんない話(Redux, Flux, MVC, MVVM)


`Flux`と`Redux`は、JavaScriptアプリケーションのための状態管理パターン及びライブラリです。

  1. Flux: Facebookによって開発された、クライアントサイドWebアプリケーションのためのアーキテクチャパターンです。Fluxは、単一方向のデータフローを採用しており、アプリケーションの状態管理をより予測可能にします。主要な概念は`Actions`(アクション)、`Dispatcher`(ディスパッチャー)、`Stores`(ストア)、および`Views`(ビュー)です。アクションがディスパッチャーによってストアに送られ、ビューが更新されます。

  2. Redux: Fluxを基にして、よりシンプルで小さなライブラリとして開発されました。Reduxは、アプリケーション全体の状態を単一のストア(状態ツリー)で管理します。Reduxの主要な原則は、状態が読み取り専用であり、状態を変更するにはアクションを発行する必要があること、そしてアクションは純粋な関数(レデューサー)によって処理されることです。Reduxは特にReactと一緒によく使用されますが、他のUIライブラリやフレームワークとも組み合わせることができます。

どちらも、大規模なアプリケーションの状態を効果的に管理し、コンポーネント間でのデータの流れを整理することに役立ちます。ReduxはFluxよりも人気があり、広範囲にわたるエコシステムとツールセットを持っています。


Redux

以下に、Reduxを用いた基本的な例を示します。この例では、単純なカウンターアプリケーションを作成します。Reduxの三つの主要な要素(アクション、レデューサー、ストア)を使用して、アプリケーションの状態(この場合は数値)を管理します。

ステップ1: アクションの定義

まず、アクションタイプとアクションクリエーターを定義します。

// アクションタイプの定義
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// アクションクリエーター
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });

ステップ2: レデューサーの作成

次に、アプリケーションの状態を変更するためのレデューサー関数を作成します。

// 初期状態
const initialState = {
  count: 0
};

// レデューサー関数
function counterReducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    default:
      return state;
  }
}

ステップ3: ストアの作成

最後に、Reduxストアを作成します。ストアはアプリケーションの状態を保持し、レデューサーを使用して状態の更新を行います。

import { createStore } from 'redux';

// ストアの作成
const store = createStore(counterReducer);

// ストアの状態が変更されたときのリスナー
store.subscribe(() => console.log(store.getState()));

// アクションのディスパッチ
store.dispatch(increment()); // カウントを1増やす
store.dispatch(increment()); // もう一度1増やす
store.dispatch(decrement()); // カウントを1減らす

このコードは、カウンターの値を増減させる基本的なReduxの例です。実際のアプリケーションでは、ReactなどのUIフレームワークと組み合わせて、ユーザーインターフェースにこれらの状態の変化を反映させます。また、実際のアプリケーションでは、より複雑な状態管理や非同期処理、ミドルウェアなどの概念も取り入れることが一般的です。

Reduxの仕組み

Reduxの内部実装の仕組みをシンプルなJavaScriptで示し、その後にFluxの基本的な概念を説明します。

Reduxの内部実装のサンプル

Reduxは主に三つの部分から成り立っています:ストア、レデューサー、アクション。以下の例では、これらの部分を独自に実装しています。

1. アクションとアクションクリエーター

// アクションタイプ
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// アクションクリエーター
function createActionIncrement() {
  return { type: INCREMENT };
}

function createActionDecrement() {
  return { type: DECREMENT };
}

2. レデューサー

function counterReducer(state = 0, action) {
  switch (action.type) {
    case INCREMENT:
      return state + 1;
    case DECREMENT:
      return state - 1;
    default:
      return state;
  }
}

3. ストア

function createStore(reducer) {
  let state;
  let listeners = [];

  function getState() {
    return state;
  }

  function dispatch(action) {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  }

  function subscribe(listener) {
    listeners.push(listener);
    return function unsubscribe() {
      listeners = listeners.filter(l => l !== listener);
    };
  }

  dispatch({}); // 初期状態を設定するためのダミーのディスパッチ

  return { getState, dispatch, subscribe };
}

// ストアの作成
const store = createStore(counterReducer);

// リスナーの登録
store.subscribe(() => console.log(store.getState()));

// アクションのディスパッチ
store.dispatch(createActionIncrement());
store.dispatch(createActionIncrement());
store.dispatch(createActionDecrement());

Fluxの基本的な概念

Fluxは単一方向データフローを持つアーキテクチャパターンです。主要な部分は以下の通りです:

  • アクション: ユーザーの操作やサーバーからのイベントなど、システムに何かが起きたことを表すシンプルなオブジェクトです。

  • ディスパッチャー: アクションを受け取り、登録されたコールバックを呼び出します。

  • ストア: アプリケーションの状態を保持し、ディスパッチャーからのアクションに応じてその状態を更新します。

  • ビュー: ストアの状態に基づいてユーザーインターフェースを更新します。

Fluxの実装例は、Reduxとは異なり、ディスパッチャーとストアがより明示的に分離されている点が特徴です。ただし、Fluxはあくまでパターンであり、その具体的な実装は多岐にわたります。

Fluxアーキテクチャ

dispacherがstoreのハンドラを保持。
ストアにリスナーを作っておき、このリスナーのハンドラが画面描画を担当。 ディスパッチャにアクションを渡すとストアにある内部データが更新されて、続けてストアのリスナー(画面描画)が起動する


Fluxアーキテクチャに基づいたシンプルなJavaScriptの実装をいくつか示します。Fluxは、アクション、ディスパッチャー、ストア、ビューという4つの主要な部分から成ります。ここでは、これらの概念を実装するための簡単な例を紹介します。

1. アクションの定義

まず、アクションタイプとアクションクリエーターを定義します。

// アクションタイプ
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

// アクションクリエーター
function increment() {
  return { type: INCREMENT };
}

function decrement() {
  return { type: DECREMENT };
}

2. ディスパッチャーの作成

次に、Fluxディスパッチャーを実装します。

class Dispatcher {
  constructor() {
    this.handlers = [];
  }

  dispatch(action) {
    this.handlers.forEach(handler => handler(action));
  }

  register(handler) {
    this.handlers.push(handler);
  }
}

const dispatcher = new Dispatcher();

3. ストアの実装

ストアはアプリケーションの状態を保持し、ディスパッチャーからのアクションに応じて状態を更新します。

class Store {
  constructor(dispatcher) {
    this.state = { count: 0 };
    dispatcher.register(this.handleActions.bind(this));
  }

  handleActions(action) {
    switch (action.type) {
      case INCREMENT:
        this.state.count += 1;
        this.emitChange();
        break;
      case DECREMENT:
        this.state.count -= 1;
        this.emitChange();
        break;
      default:
        // 何もしない
    }
  }

  addChangeListener(callback) {
    this.onChange = callback;
  }

  emitChange() {
    if (this.onChange) {
      this.onChange(this.state);
    }
  }
}

const store = new Store(dispatcher);

4. ビューの連携

ビュー(この場合はコンソール出力)がストアの変更を監視し、更新されたときに反応します。

store.addChangeListener((state) => {
  console.log(`現在のカウント: ${state.count}`);
});

// アクションのディスパッチ
dispatcher.dispatch(increment());
dispatcher.dispatch(increment());
dispatcher.dispatch(decrement());

この例では、簡単なカウンターアプリケーションをFluxアーキテクチャで実装しています。実際のアプリケーションでは、この構造を拡張し、より複雑なロジックや非同期操作、複数のストアなどを組み込むことが一般的です。また、ビューは通常、Reactなどのライブラリを用いて実装されます。

MVCとMVVM

MVCとMVVMのアーキテクチャパターンの基本的な概念を示すため、シンプルなJavaScriptの例を作成します。この例では、ユーザーがボタンをクリックするとカウンターが増加する、基本的なカウンターアプリケーションを想定します。

MVCのサンプル

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MVC Example</title>
</head>
<body>
    <div id="app">
        <button id="incrementButton">Increment</button>
        <div id="counterValue">0</div>
    </div>

    <script>
        // Model
        class CounterModel {
            constructor() {
                this.count = 0;
            }

            increment() {
                this.count++;
            }
        }

        // View
        class CounterView {
            constructor() {
                this.incrementButton = document.getElementById('incrementButton');
                this.counterValue = document.getElementById('counterValue');
            }

            setOnClickListener(callback) {
                this.incrementButton.addEventListener('click', callback);
            }

            updateCounter(count) {
                this.counterValue.textContent = count;
            }
        }

        // Controller
        class CounterController {
            constructor(model, view) {
                this.model = model;
                this.view = view;
                this.view.setOnClickListener(() => this.increment());
                this.view.updateCounter(this.model.count);
            }

            increment() {
                this.model.increment();
                this.view.updateCounter(this.model.count);
            }
        }

        // Initialization
        const app = new CounterController(new CounterModel(), new CounterView());
    </script>
</body>
</html>

MVVMのサンプル

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MVVM Example</title>
</head>
<body>
    <div id="app">
        <button id="incrementButton">Increment</button>
        <div id="counterValue">0</div>
    </div>

    <script>
        // Model
        class CounterModel {
            constructor() {
                this.count = 0;
            }

            increment() {
                this.count++;
            }
        }

        // ViewModel
        class CounterViewModel {
            constructor(model) {
                this.model = model;
                this.counterValueElement = document.getElementById('counterValue');
                this.incrementButton = document.getElementById('incrementButton');
                this.incrementButton.addEventListener('click', () => this.increment());
                this.updateView();
            }

            increment() {
                this.model.increment();
                this.updateView();
            }

            updateView() {
                this.counterValueElement.textContent = this.model.count;
            }
        }

        // Initialization
        const app = new CounterViewModel(new CounterModel());
    </script>
</body>
</html>

これらの例では、MVCではモデル、ビュー、コントローラーがはっきりと分離されており、ビューとモデルの直接的な通信をコントローラーが仲介しています。一方、MVVMではビューモデルがモデルとビューの間のデータバインディングを担当し、ビューのUIロジックをカプセル化しています。これらのパターンはアプリケーションの設計を整理し、開発とメンテナンスを容易にするためのものです。

この記事が気に入ったらサポートをしてみませんか?