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においてエラーが出ることは怖いことではありません。原因が何であるかを明確かつ親切に正解へと導いてくれる可能性が高いです。
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自体が問題になることはあまり多くありませんが、やはりとっつきやすさでは初期の学習コストが障壁になると思います。乗り越えさえすればその後はほぼ平坦なため、参入障壁を抑えてデフォルトの選択肢に入ると選択の幅が広がり嬉しいと思います。是非覚悟が決まった方はチャレンジしてみてください!