素のJSでアプリを書いていて、「あーだりぃ...これってElmっぽくならないかな?」と思ったわけで。
まずは基本的なとこから、っていうことで、Ellieを開くと最初に出てくるカウンターのサンプルに、追加でリセットボタンをつけたやつ https://ellie-app.com/9SX5Fqtg7cZa1
これを素のJSで何とかできないかやってみました。
See the Pen counter by ttatsf (@ttatsf) on CodePen.
あれ?何かいい感じじゃない?
意外とシンプルでした。これだったらいろいろ応用できるかも。
ひとつずつ見ていきます。
model
ほぼ同じ。
// Elm:
initialModel : Model
initialModel =
{ count = 0 }
// JavaScript:
const init =
{count: 0}
update
ほとんど同じ。
//Elm:
type Msg
= Increment
| Decrement
| Reset
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }
Decrement ->
{ model | count = model.count - 1 }
Reset ->
initialModel
// JavaScript:
const Inc = Symbol("increment")
const Dec = Symbol("decrement")
const Reset = Symbol("reset")
const update = msg => model =>
msg === Inc ?
{...model, count: model.count + 1}
: msg === Dec ?
{...model, count: model.count - 1}
: msg === Reset ?
init
: model
ここはほぼそっくり。
素のJSでは Symbolを使って msg を表現している。
view
かなり違う。
// Elm:
view : Model -> Html Msg
view model =
div []
[ button [ onClick Increment ] [ text "+1" ]
, div [] [ text <| String.fromInt model.count ]
, button [ onClick Decrement ] [ text "-1" ]
, button [ onClick Reset ] [ text "reset" ]
]
// JavaScript:
document.body.insertAdjacentHTML(
'beforeend'
,
` <button type="button"id="b0">+1</button>
<div id="d0"></div>
<button type="button"id="b1">-1</button>
<button type="button"id="b2">reset</button>
`
)
const view = model => {
document.querySelector("#d0").textContent =
model.count.toString()
}
Elmでは、独自の関数を使って
- 静的な要素の配置
- 動的な要素の書き換え
- イベントリスナーの登録
を、一気に宣言的にやってしまっている。
素のJSでは、1.は html で直書きするか innerHTML/insertAdjacentHTML で、3.はonclick/addEventLister でやって、2.だけ view 関数としてここで定義している。
main
今回のキモ。
// Elm:
main : Program () Model Msg
main =
Browser.sandbox
{ init = initialModel
, view = view
, update = update
}
// JavaScript:
const browserSandBox =
model => msg => {
model = update( msg )( model )
view( model )
}
const sendMsg =
browserSandBox(init)
sendMsg(Reset)
document.querySelector("#b0")
.addEventListener('click',
_ => sendMsg(Inc)
)
document.querySelector("#b1")
.addEventListener('click',
_ => sendMsg(Dec)
)
document.querySelector("#b2")
.addEventListener('click',
_ => sendMsg(Reset)
)
Elmでは、init, view, update を登録すれば、いい感じにしてくれます。何をどうしてるかはわからない。ブラックボックスです。
素のJSでどうしたら実現できるかな?と考えました。やりたいことは:
- model と msg を取得して
- その msg で model を更新して
- その model を 保存して
- html要素を書き換える
ということ。あれ? なんだかできそうだな?
2., 3., 4. を関数で表すと:
model => msg =>{
model = update( msg )( model )
view( model )
}
これを browserSandBox ということにして、model (=init) を部分適用した関数
const sendMsg =
browserSandBox(init)
(狭義のクロージャでもある)を、addEventListener('click',... で適当な msg 引数で登録すればいいんじゃね? -> できたー!
まとめ
素のJSでもTEAっぽいことができる。クロージャを使えばね。
もっといいやりかた、ちゃんとしたやりかたがあるよっていうのがあればコメントお願いします。