l

o

a

d

i

n

g

.

.

.

GoF以外のプログラミング・デザインパターン #22 Container-Presenter Pattern(Smart/Dumb Components)

2025/12/13

プログラミング 学習

t f B! P L
eyecatch Container-Presenter Pattern(または Smart/Dumb Components パターン)は、UIロジックと表示ロジックを明確に分離するための設計パターンです。 ReactやVueなどのコンポーネント指向UIでよく使われています。

基本概念

1. Container(Smart Component)

・状態管理(state)・データ取得・ビジネスロジックを担当 ・API呼び出しやイベント処理など、「アプリの頭脳」部分 ・UIは直接描画せず、データをPresenterに渡す

2. Presenter(Dumb Component)

・データを受け取り、表示だけを担当 ・propsとしてデータを受け、イベントをコールバックで返す ・ロジックは持たず、見た目の責任だけを負う

データフロー構造

[Container] ← 状態を管理(Smart) useState/useEffect handleEvent() ↓ props Presenter UIのみ担当(Dumb) render(data) onClick()

サンプルコード1 : React風

// Container Component(Smart) function CounterContainer() { const [count, setCount] = React.useState(0); const handleIncrement = () => setCount(c => c + 1); const handleDecrement = () => setCount(c => c - 1); // Presenter に state とイベントを渡す return ( <CounterPresenter count={count} onIncrement={handleIncrement} onDecrement={handleDecrement} /> ); } // Presenter Component(Dumb) function CounterPresenter({ count, onIncrement, onDecrement }) { return ( <div style={{ textAlign: 'center' }}> <h2>Container-Presenter Pattern</h2> <div style={{ fontSize: '2em' }}>{count}</div> <button onClick={onIncrement}>+</button> <button onClick={onDecrement}>-</button> </div> ); }

サンプルコード2 : Vanilla風

Reactなしで「状態管理とUI描画の分離」を体験できる構成バージョンです。 <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Container-Presenter Pattern (Vanilla JS)</title> <style> body { font-family: sans-serif; text-align: center; padding-top: 50px; } .count { font-size: 2em; margin: 10px; } button { font-size: 1.2em; margin: 5px; padding: 5px 15px; } </style> </head> <body> <div id="app"></div> <script> /** * Presenter(UI担当)— Dumb Component * @param {Object} props - count, onIncrement, onDecrement */ function CounterPresenter(props) { const container = document.createElement('div'); const title = document.createElement('h2'); title.textContent = 'Container-Presenter Pattern (Vanilla JS)'; const countDisplay = document.createElement('div'); countDisplay.className = 'count'; countDisplay.textContent = props.count; const incBtn = document.createElement('button'); incBtn.textContent = '+'; incBtn.addEventListener('click', props.onIncrement); const decBtn = document.createElement('button'); decBtn.textContent = '-'; decBtn.addEventListener('click', props.onDecrement); container.append(title, countDisplay, incBtn, decBtn); return container; } /** * Container(状態管理担当)— Smart Component * 管理ロジックと再描画処理を担当 */ function CounterContainer(root) { let count = 0; function render() { root.innerHTML = ''; const presenter = CounterPresenter({ count, onIncrement: () => { count++; render(); }, onDecrement: () => { count--; render(); } }); root.appendChild(presenter); } render(); } // 初期化 const app = document.getElementById('app'); CounterContainer(app); </script> </body> </html>

ポイント解説

CounterContainer() ・状態(count)を持つ ・イベントハンドラを定義 ・render()関数でPresenterを再生成して描画 CounterPresenter() ・DOM操作を担当 ・props(状態とイベント)を受け取り、表示専用

メリット

・責務の分離:UIとロジックを完全に分離できる ・テスト容易性:PresenterはUIスナップショットテストだけで済む ・再利用性:同じPresenterを複数のContainerで使える ・保守性向上:データの流れが明確になる

デメリット

・小規模アプリでは分けすぎるとコードが冗長になる ・適用箇所を見極めることが重要(例:複雑な画面、再利用性が高いUI)

あとがき

ReactやVee特有の、HTMLコンポーネントを、無理やりVanillaJSで書き直して見ましたが、コードのボリュームや見た目のわかりやすさを比べると、 フレームワークの簡単さが手に取るようにわかりますね。 でも、個人的には、こうしたコンポーネントは、プログラムソースに埋め込むのではなく、コンポーネントファイルとしてフォルダ管理されている方が、その後の拡張などが効率的になるような気がします。 ちなみに、PHPなどでやる場合は、普通にこ右下構成は、プログラム内(HTML組み込みコード)でできてしまうので、Javascriptの悩みでもある、コンポーネント組み込みの難しさ(そもそもネイティブ機能で持っていないので)を際理解できるデザインパターンだと思いました。

人気の投稿

このブログを検索

ごあいさつ

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

ブログ アーカイブ