Webの歴史を歴史を振り返りつつjQueryとVueの書き方を比較します。
# Webの歴史
# 古代
サーバといったら静的なHTMLを返す役割だった時代です。
サーバには静的なHTMLファイルを配置しておいてクライアントからそれを要求する形でWebページを表現していました。
http://example.com/about/index.html
というリクエストに対してサーバはabout
ディレクトリの中にあるindex.html
をクライアントに返すイメージです。
この頃のJavaScriptはというと画面をちょっと装飾するためのものでした。例えばボタンを押すと文字の色が変わるだとか、ローディング画面を出すだとかです。
整理すると サーバの役割・・・静的なHTMLを返す JavaScriptの役割・・・画面を装飾する
# 中世
ページの表示に関わる処理はほぼ全てサーバの役割だった時代です。
古代の仕組みだと一般的にWebアプリケーションと言われるアプリは作れません。Instagramのようにユーザーが画像を投稿したり、何かを検索したりという機能は静的なページとして事前に用意しておくことは原理的に不可能です。 もう少し抽象的な表現をすると、ユーザーのアクションによって動的にページを生成するアプリケーションは古代の仕組みでは作れないのです。
動的にページを生成するということはリクエストによって適切なレスポンスを返すためのプログラムをサーバ側に用意する必要があります。JavaやPHP、Rubyなどのいわゆるプログラミング言語がそれにあたります。ちなみにSpringやCakePHP、Railsなんかはそれらの言語を利用したフレームワークです。
この頃はルーティングもHTMLの生成(レンダリング)もサーバ側で行なっていました。
/users
というURLに対するリクエストを受け取ったサーバは適切なコントローラに処理を振り分け(ルーティング)、そのコントローラはHTMLの雛形のファイルからHTMLを生成しそれをクライアントに返す。クライアントはそのHTMLを解釈して表示する。
サーバは一連のこれらの処理を一挙に担っていました。
一方でJavaScriptはどんな役割を持っていたかというと、古代のようなページを装飾する役割に加え、Ajaxを用いて画面の一部を非同期的に書き換えるという役割を持つことになりました。このようなことをしなくてもWebアプリケーションは作れますが、よりユーザー体験を追求していくと、同期的にページを読み込むよりも非同期的にページを読み込む方が「使い勝手」「スピード」の双方においてメリットがあります。「ヌルッと動く」などと表現したりしますね。
非同期的にAPIを叩き(=サーバと通信し)、非同期的にHTMLを書き換えるという役割が中世におけるJavaScriptに求められるようになりました。ブラウザによる差異を意識することなくDOMを書き換えることができるjQueryというライブラリが一世を風靡しました。
整理すると サーバの役割・・・ユーザーのアクションに対して動的にHTMLを生成して返す JavaScriptの役割・・・ユーザー体験向上のため、非同期通信を用いて画面の一部を書き換える
# 近代
近代になってくるとクライアント(フロント)に要求されるユーザー体験がよりハイレベルなものになってきます。Trelloなどを思い浮かべてもらえるとよいと思います。使い心地の良いスマホアプリが一般に浸透してきたのも背景としてあるのかもしれません。
そういったアプリにおいては、RailsのScaffoldで作ったような1画面に1テーブルという単純なCRUDで済むような画面はほぼ存在しません。非同期処理を多用し、通信中の場合はローディング画面を表示し、通信が成功したら結果を画面に表示する。もしエラーだった場合はローディング画面は非表示にし、代わりにエラーメッセージを画面に表示する。再度通信をする際はそのエラーメッセージを非表示にし、ローディング画面を際表示する。などなど、画面の状態が目まぐるしく変化するUI/UXが求められるようになってきました。 詳細は後述しますが、そういったイベント・アクションを契機にDOM(DOMの説明はここではしません)を人間が直接いじることは非常に辛いです。
この問題に対する一つの解として、画面を表示する役割をサーバからクライアントに移すという動きが出てきました。古代と中世とは根本的に異なるアーキテクチャです。ルーティングやレンダリングをサーバにお任せしていたのが従来のアーキテクチャでしたが、それをクライアントにお任せするようになりました。いわゆるSPA(Single Page Application)の時代です。ではサーバは何をするかというとJSON形式のデータをクライアントに返すだけになりました。 例
user: {
name: "dyson",
age: 30
}
これを受け取ったクライアントが画面のレンダリングを行います。サーバがHTMLを丸々返すアーキテクチャとは大きく異なるのがわかると思います。いわゆるAPIとかAPIサーバといわれる類のものはJSON形式のレスポンスを返すサーバと思ってもらって差し支えないです。
整理すると
サーバの役割・・・JSON形式のレスポンスを返す「APIサーバ」としての役割 JavaScriptの役割・・・ルーティングやレンダリングなどを全て担う
ここまででサーバとクライアントの役割がそれぞれどのように変化してきたかが掴めたと思います。
# 具体的にどういったところがjQueryだと辛いのか
# DOMを直接変更するのが辛い
これにつきます。 今回はjQueryとの比較対象としてRUNTEQでも教えているVue.jsを用いて、具体例を交えながら説明していきます。
# 例1)フォームバリデーション
# 要件
- メッセージフィールドは必須であること
- メッセージフィールドは20文字以内であること
- バリデーションが通っていない場合は送信ボタンは非活性状態にすること
- 通信中は「通信中...」とメッセージを出すこと
- 送信後はメッセーフィールドをクリアすること
# jQueryで書いた例
See the Pen bGVQmzJ by だいそんさいとう@筋肉エンジニア (@daidai3110) on CodePen.
# 辛みポイント
- いたるところでDOMを取ってこないといけない
- 変更イベント検知のためにテキストフィールドを取ってくる必要がある
- 通信中の表示を切り替えるために通信中のラベルを取ってくる必要がある
- 送信後に空っぽにするためにテキストフィールドを取ってくる必要がある
→ DOMが増えたら収拾つかなくなりそうです。また、通信を行う機能が他にも追加された場合、そちらにも通信中のラベルの表示制御を書く必要が生じます。
- 送信ボタンの活性化制御が「手続き的」
- テキストフィールドの変更を検知する
- テキストフィールドの長さを判定する
- その結果によってdisabled属性を書きかえろと命令する
→ 送信ボタンの活性化条件が増えて、テキストフィールド以外の要素に依存するようになったらどうなるでしょう?例えば「同意する」ボタンにチェックを入れないとを活性化されないという要件が増えた場合、テキスト変更イベントの中では「同意するボタンにチェックが入っているか」も尋ねなければいけないし、チェックボックス変更イベントの中では「テキストフィールドの長さ」を尋ねないといけません。「送信ボタンの制御」に関する処理が散らばってしまいコードの見通しが非常に悪くなってしまいます。
# Vue.jsで書いた例
See the Pen VwvVEQM by だいそんさいとう@筋肉エンジニア (@daidai3110) on CodePen.
# 幸せポイント
jQueryの辛みポイントが解決できています。
DOMを取ってくる処理は一切ありません。
また活性・非活性の制御はisLoading
という算出プロパティに集約されているので、もし何かボタン制御の条件に変更があったとしてもここ一箇所を直せばOKです。いわゆる「宣言的」という書き方で、Vue.jsなどのフレームワークの特徴でもあります。「宣言的」の理解を深めるためにここでjQueryの場合の制御を思い出してみます。
1. テキストフィールドの変更を検知する
1. テキストフィールドの長さを判定する
1. その結果によってdisabled属性を書きかえろと命令する
→ 活性・非活性を切り替えるために上から順に処理を行なっています。決められた手続きによって処理が行われていくイメージなので「手続き的」とも言われます。 一方でVue.jsの場合は「活性化条件は1文字以上20文字以下です」という宣言をしているに過ぎません。テキストに変更があるたびにここが自動的に評価され、この条件に合致したら活性化、合致しなかったら非活性化されます。これを「宣言的」といいます。
# 例2)タスク管理アプリケーション
# 要件
- テキストフィールドは必須項目であること
- 追加ボタンを押すとタスク一覧に一行追加されること
- チェックボックスを押すとタスク名に取り消し線が入ること
- 削除ボタンを押すとそのタスクが一覧から取り除かれること
- 合計タスク数を表示すること
# jQueryで書いた例
See the Pen タスク管理アプリケーション(jQuery) by だいそんさいとう@筋肉エンジニア (@daidai3110) on CodePen.
# 辛みポイント
- いたるところでDOMを取ってくる必要がある
- これは例1)と同じ
- DOMを追加する際にHTMLをJSに埋め込む必要がある
- jQueryでこれをやろうとすると非常に読みづらい(JSX等の話はここではしない)
- 合計を表す変数がグローバルに存在する
- そもそもグローバル変数はバッティングする恐れがあるので推奨されていない
- 追加時の処理と削除時の処理の双方に合計をいじる処理が書かれている(処理が散らばる)
- 完了や削除をするために各チェックボックス、ラベル、削除ボタンを紐づけるなんらかの情報が必要になる
- この例ではdata属性とid属性で紐づけているが、もうこの規模のアプリでも辛いのが伝わってくるはず
例1)と同様、基本的に全て「手続き的」です。タスクの追加や削除、完了時の取り消し線付与などが手続き的に行われています。
# Vue.jsで書いた例
See the Pen タスク管理アプリケーション(Vue.js) by だいそんさいとう@筋肉エンジニア (@daidai3110) on CodePen.
# 幸せポイント
jQueryでは「手続き的」だったものがVue.jsでは「宣言的」に書けています。タスクの追加や削除はJavaScriptにおけるtasks
という変数に対してオブジェクトを追加・削除するだけで良くなります。取り消し線の付与に関してもtask
が持っているisDone
を見て自動的にclassを切り替えてくれます。
この程度のアプリでも人間がDOMを直接変更するのがいかに難しいかがわかってもらえたと思います。
# まとめ
アプリが複雑性を持つようになるととたんにDOM操作は辛くなるのでjQueryだけで可読性の高いコードを保ち続けるのは厳しい。ちょっとした操作であればやはり楽ではあるので適材適所かな。