Help us understand the problem. What is going on with this article?

Vue.jsとElmを比べてみよう! テンプレートとview編

Vue.jsは「簡単である」Elmは「シンプルであること」ということがガイド等で強調されています。私の中では上手く抽象的な言葉のレベルでこれらを整理することが出来なかったので、二つのフレームワーク(言語)で意識し続けなければならないであろうHTMLの表現を比較することで、それぞれの特徴や問題点を考えてみることにしました。その中でもアプリケーションで特に重要であろう複数の値の扱いに特化して比較してみます。

オブジェクトの表示

Vue

おそらく多用することであろう汎用ループを扱うことが可能なディレクティブv-forを用いていきます。

<div id="app">
  <ul>
    <li v-for="item in items">
      {{ item.message }}
    </li>
  </ul>
</div>

起動に必要な最低限のコードの中に、テンプレートの中で使用するデータをdataプロパティの中で用意してあげます。

new Vue({
  el: '#app',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

この時点では、Vueは値と表現をテンプレートとスクリプトで分けるものという風に認識ができます。

Vueの利点

全体のコード量も非常に短く、誰がみてもわかりやすくとっつき易いHTMLの表現になりました。このとっつきやすさは、テンプレートを持つVueの強みだと思われます。起動スクリプトも短く、すぐに何かを作り始めるのが簡単に感じました。

Vueの問題点

以下のようにタイポをしてしまうと、リストの文字列が空っぽになってしまいます。この程度ならすぐ気付けるかもしれませんが、ベースがHtmlという元が文章を書くための表現方法のためシンタックスのミスなどは検知がしにくいです。よって短くコンポーネント(ファイル)を保つなど人間がミスを起こしにくいような仕組みを工夫し続けなければなりません。ツールが解決することもあるとは思いますが、今度はツールを使うための知識やテクニックを抑える必要があり覚えることは徐々に増えていきます。

<ul>
  <li v-for="item in items" >
    {{ item.messag }}
  </li>
</ul>

Elm

ElmのHtml側は、Elmを起動するためのDOMの用意と起動スクリプトのみがあります。

<main></main>
  <script>
    var app = Elm.Main.init({ node: document.querySelector('main') })
  </script>

最低限の準備でもたくさんのコードを用意する必要があります。今回は、view関数のみをフォーカスすれば良いので、そこだけ切り出したものを下に用意します。

module Main exposing (main)

import Browser
import Html exposing (..)


type alias Model =
    ()


initialModel : Model
initialModel =
    ()


type Msg
    = NoOp


update : Msg -> Model -> Model
update msg model =
    model


items : List { message : String }
items =
    [ { message = "Foo"
      }
    , { message = "Bar"
      }
    ]


view : Model -> Html Msg
view model =
    ul []
        [ li [] <|
            List.map (\item -> li [] [ text item.message ]) items
        ]


main : Program () Model Msg
main =
    Browser.sandbox
        { init = initialModel
        , view = view
        , update = update
        }

全ては関数なため、関数の単位でデータとHtmlの表現を分けています。単なる値はHtmlの表現にすることができないため、itemsの一つ一つの値(item)は、li関数(Html Msg型)に渡され評価されます。文字列も同じくHtml Msg型に変換する必要があるためtext関数に渡す必要があります。

items : List { message : String }
items =
    [ { message = "Foo"
      }
    , { message = "Bar"
      }
    ]


view : Model -> Html Msg
view model =
    ul []
        [ li [] <|
            List.map (\item -> li [] [ text item.message ]) items
        ]

Elmの利点

Vueと同じようにタイポしてみましょう。するとElmは絶対コンパイルを通しません。Elmにおいてエラーが出ることは怖いことではありません。原因が何であるかを明確かつ親切に正解へと導いてくれる可能性が高いです。

スクリーンショット 2020-03-06 8.34.38.png

Elmの問題点

Htmlをパッと書くVueと比べるとview関数はとっつきにくいと感じる方が多いかもしれません。ElmではListもHtmlも同じ値であると考えるため現時点ではそれが同居している良さが感じられにくいです。

数値の扱い

Vue

テンプレート中ではJavaScriptの表現が使用可能なため、String interpolationを用いて計算し文字列を導き出します。

<div id="app">
  <ul>
    <li v-for="item in items">
      {{ `x = ${item.num + 1}` }}
    </li>
  </ul>
</div>
data: {
  items: [
    { num: 1 },
    { num: 2 }
  ]
}

Vueの利点

表現がとても柔軟です。最初の例と変わらずに記述することができました。

Vueの問題点

柔軟が故に以下のような事故も引き起こし易いです。次のプログラムの実行結果は多くの人が意図したものではなく、

  • x = 11
  • x = 21

という結果になります。タイポのときと同様に、実行するまでどんな結果が表現されるかがわからない(もしくは工夫が必要)ためミスが発見しにくいことになります。大規模やミスがクリティカルになるようなアプリケーションではかなりの苦労が見込まれます。また、書き方の統一性が無いがために他人のコードにフラストレーションを感じるケースも少なくは無いと思います。

<div id="app">
  <ul>
    <li v-for="item in items">
      {{ "x = " + item.num + 1 }}
    </li>
  </ul>
</div>

Elm

数値はtext関数には渡すことができません。文字列に直してあげる必要があります。

items : List { num : Int }
items =
    [ { num = 1
      }
    , { num = 2
      }
    ]


view : Model -> Html Msg
view model =
    ul []
        [ li [] <|
            List.map (\item -> li [] [ text <| "x=" ++ (String.fromInt (item.num + 1 ) ) ]) items
        ]

Elmの利点

先ほどの例はとても見通しが悪いものでしたが、表現と値と値の計算が同居してるが故にその中で分けて考えることも簡単に行うことができます。text関数がStringしか受け取らないのであれば、数値の計算と数値から文字列の変換や結合は切り出して先に行ってしまえば解決します。Elmでは最終的に出来ること(コンパイラが許してくれること)の幅はものすごく少ないですが、コードを見やすくするための柔軟さはとても高く感じます。

items : List { num : Int }
items =
    [ { num = 1
      }
    , { num = 2
      }
    ]

evaluatedItems : List String
evaluatedItems  =
  items
      |> List.map (\item -> item.num + 1)
      |> List.map (\num -> "x=" ++ String.fromInt num)

view : Model -> Html Msg
view model =
    ul []
        [ li [] <|
            List.map (\item -> li [] [ text item ]) evaluatedItems
        ]

以下のように単なる値をREPLで気軽に確認することやテストで保証することが簡単に行えます。

> items |> List.map (\item -> item.num + 1) |> List.map (\num -> "x=" ++ String.fromInt num )
["x=2","x=3","x=4"] : List String

Elmの問題点

いきなり利点で書いたような綺麗なコードを書くのは難しいです。そのために一番最初に書いたような愚直な書き方を辿ることになると思いますが、ゴチャッっとしがちなので正解に行き着くまで何度かコンパイラエラーか挫折を経験してしまうかもしれません。実行まで一歩一歩進むための覚悟が必要となります。

条件付き繰り返し

Vue

以下のように書くことができます。

<div id="app">
  <ul>
    <li v-for="n in nums" v-if="n % 2 == 0">
      {{ n }}
    </li>
  </ul>
</div>
data: {
  nums: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}

が、しかし、for と一緒に v-if を使うのを避けてください。実際には以下のように書く必要があります。

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers() {
    return this.numbers.filter((number) => {
      return number % 2 === 0
    })
  }
}

Vueの利点

依然として表記としては短く簡単であることは変わりません。

Vueの問題点

気づかず動いてしまうが悪いコードを書いてしまう可能性が高いです。この場合、ミスをしてしまったから動かして間違いを確認する。という問題よりも一段深刻になってしまいます。ある時までは動いていたが、あるときに失敗し始めてしまう。そのためVueで良いコードを書き続けるには、ある種膨大な知識を溜め込み続ける必要があります。また、今まではデータの表現(加工)はテンプレートでデータはスクリプトという考えでしたが、テンプレートエンジンの都合上v-forとv-ifが同居できないために棲み分けを考えなければならなくなりました。これはどのように書くべきかパターンを見出すまでは、気を使い続ける必要があります。チーム開発などの場合、書き方の意思の統一が課題となります。

Elm

filter関数で奇数のみを取り出し、text関数に渡すために文字列に変換します。

numbers : List Int
numbers =
    List.range 1 10


evenNumberTexts : List String
evenNumberTexts =
    numbers
        |> List.filter (\num -> modBy 2 num == 0)
        |> List.map (\num -> String.fromInt num)


view : Model -> Html Msg
view model =
    ul []
        [ li [] <|
            List.map (\item -> li [] [ text item ]) evenNumberTexts
        ] 

Elmの利点

とっつきにくかったHtml表現ですが、文字列にすることさえわかってしまえば、計算と加工は手前で行うだけです。パターンが増えるわけでは無いので多くの場合は、このやり方さえ身につけておけば問題がありません。手前の処理が複雑になればなるほど値のテストのしやすさなどの利点が生かされていきます。チーム開発では、計算の仕方というアプリケーションのドメインに関わる部分と表現の問題を切り分けやすくなります。微妙な違いは現れるものの、意思統一にブレが起きるということは考えにくいです。

Elmの問題点

今までの問題点と変わりありません。

まとめ

Vueは初期の学習コストが低いですが、複雑性が増した時に柔軟に書けることの利点が問題点に跳ね返ってくることが多いです。その時にどう工夫をして解決していくかが課題となると思います。Elmは問題が複雑になってもElm自体が問題になることはあまり多くありませんが、やはりとっつきやすさでは初期の学習コストが障壁になると思います。乗り越えさえすればその後はほぼ平坦なため、参入障壁を抑えてデフォルトの選択肢に入ると選択の幅が広がり嬉しいと思います。是非覚悟が決まった方はチャレンジしてみてください!

elm-jp
主に日本で活動する Elm 利用者のコミュニティです。
https://elm-lang.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away