JavaScript(ES6)でElm Architecture(Virtual-DOMなし)
Elm自体がJavaScriptを生成するものなので実際には利用することはないと思いますが(さらに生のJavaScriptがよければReact.jsがあります)、ElmのModelとUpdateの仕組みを生のJavaScriptで実装してみました。
WPFとC#で実装されたものは、下記記事にて参照できます。
WPFでElm Architecture --- Xaml利用しない版 - 何でもプログラミング
アプリケーションコード
簡単なカウンタアプリを実装します。
initialModel | 状態の初期値を定義 |
message | update関数で処理させる内容の識別子 |
update | 現在のmodelと、messageを受け取って、新しいmodelを返す |
bind | modelのCell(現在値+変更通知)と、send(message)関数を、html要素にバインドする |
CellはFunctional Rective Programming(FRP)でのBehaviorを意図しており、詳しくは下記を参照してください。
SodiumでFunctional Reactive Programming (F#) --- 導入 - 何でもプログラミング
<body> <button id="dec">dec</button> <input type="text" id="count"></input> <button id="inc">inc</button> </body>
const initialModel = { count : 0, }; const message = { increment : Symbol(), decrement : Symbol(), } function copy(obj, diff) { return Object.assign({}, obj, diff); } function update(model, msg){ switch (msg.id) { case message.increment : return copy(model, {count : model.count + 1}); case message.decrement : return copy(model, {count : model.count - 1}); default : throw "invalid message" } } function byId(name) { return document.getElementById(name); } function bind(model, send) { model.map(x => x.count).listen(x => byId("count").value = x); byId("dec").onclick = () => send({ id: message.decrement }); byId("inc").onclick = () => send({ id: message.increment }); } window.onload = () => startApp(initialModel, update, bind);
Cellクラス
listenでコールバックを登録し、sendでデータを送出し、mapでデータの変換を行います。
listenした後、コールバックを解除する機能は実装してありませんので、利用する際(コントロールを動的に追加or削除)はリークに注意してください。
FRPのライブラリSodiumにJavaScript版がありますので、そちらのCellを利用することも可能です。
class Cell { constructor(initialValue) { this.value = initialValue; this.callbacks = []; } listen(f) { f(this.value); this.callbacks.push(f); } send(value) { if (this.value === value) return; this.value = value; this.callbacks.forEach(f => f(value)); } map(f) { const cell = new Cell(f(this.value)); this.callbacks.push(x => cell.send(f(x))); return cell; } }
startApp
initialModelとupdateから、modelのCellとsend関数を作成し、bindを呼び出しています。
function startApp(initialModel, update, bind){ const cell = new Cell(initialModel); const send = msg => cell.send(update(cell.value, msg)); bind(cell, send); }