LoginSignup
4
4

More than 1 year has passed since last update.

JavaScript: ElmのTEAっぽいことを素のJSでやってみた

Last updated at Posted at 2020-09-05

素の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では、独自の関数を使って

  1. 静的な要素の配置
  2. 動的な要素の書き換え
  3. イベントリスナーの登録

を、一気に宣言的にやってしまっている。

素の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でどうしたら実現できるかな?と考えました。やりたいことは:

  1. model と msg を取得して
  2. その msg で model を更新して
  3. その model を 保存して
  4. html要素を書き換える

ということ。あれ? なんだかできそうだな?
2., 3., 4. を関数で表すと:

model => msg =>{
  model = update( msg )( model )
  view( model )
}

これを browserSandBox ということにして、model (=init) を部分適用した関数

const sendMsg = 
  browserSandBox(init)

(狭義のクロージャでもある)を、addEventListener('click',... で適当な msg 引数で登録すればいいんじゃね? -> できたー!

まとめ

素のJSでもTEAっぽいことができる。クロージャを使えばね。

もっといいやりかた、ちゃんとしたやりかたがあるよっていうのがあればコメントお願いします。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4