0. 記事作成の動機:コンポーネントを作る煩わしさ
ReacやVueなどのJsでコンポーネントをつくるのがめんどくさいと感じるときが多々あった。
なので、コンポーネントを作る意味を再考してみる。
以前、似たような記事を作ったけど、それをもう少し整理してみた。
SPA(Single Page Application) を採用するメリット再考(2020年)
1. コンポーネントを作ることの目的
knockoutjsを皮切りに10年以上SPAでのアプリを作ってきた。
最近感じるのは、VueやReactでのコンポーネント分割はすごく手間がかかるということ。
そこで改めて、コンポーネントを作ることを考えてみる。
コンポーネントを作る目的は大きく分けて2つあると思う。
- 再利用
- コード分割(独立性)
以下、上記について補足。
(1) 再利用
コンポーネントを作ることの醍醐味であり、便利さを実感できるのがこの再利用性。
一度作ったコンポーネントをプロパティを替えていろいろな箇所で使える。
最近では、bit.devみたいなコンポーネントのリポジトリがあってそこで広く使えるようになると凄く便利。
とはいえ、そこまで汎用性をもったコンポーネントというのはそうたくさんはできない。
どうしても汎用的に再利用しようとするとすごくシンプルなものになってくる。
そうすると、「わざわざつくらなくても、jQueryベースでもよくない?」となりかねない。
プロジェクトでも、1度しか使わないようなコンポーネントがあったりしないだろうか?
では、1度しか使わないコンポーネントは要らないのか?
いや、そうではない。それが次の項目。
(2) コード分割
コンポーネントのもう一つの側面は、ある目的や関心を小さく区切ってそれだけに焦点をあててコーディングすること。
そうすることで、コードの可読性があがったり、修正時の影響が極限化される。
これを踏まえた上で、いろいろなところで使われるようになると再利用性が実現される。
とはいえ、1度しか使わないようなコンポーネントですら、上記のようなコーディング上のメリットがある。
2. JavascriptとHTML/CSSとの関係性:ライブラリの2つのグループ
コンポーネントライブラリを大きく分けると2つに分類できると思う。
- Javascript内にHTMLやCSSを内包する。(Javascriptベース型)
- Javascript、HTML、CSSを独立させるが一つのファイルで管理する。(分離パッケージ型)
以下、補足。
(1) Javascriptベース型
ReactやMithrillをベースにしたコンポーネントライブラリは、JSXによりJavascript(以下、Js)内にHTMLやCSSを内包する。そして、Jsベースでコンポーネントを作る。
(2) 分離パッケージ型
Angular、Vue、Svelte、Riotなどは、JsとHTML/CSSを分けて操作する。その代わり、HTMLに独自の属性(v-if)などを付与する。
3. ライブラリグループの評価
前節のグループのそれぞれの利点と問題点を指摘する。
(1) Javascriptベース型
a. 利点
Reactの場合、JsのクラスとHTML構造がマッピングされる。要は、HTMLのデータモデルをJsのクラスで表現する。
HTMLを扱う場合に、Js視点でコーディングでき、思考が1元化して分かりやすい。
また、HTMLとクラスをマッピングするという考え方により、コーディングルールが明確化し、コードの可読性が向上する。
b. 問題点
ReactのようにJsをベースにすると、HTMLやCSSを別でコーダーさんがつくった場合、分割して、さらにJs内に取り込むという作業が発生する。
単なる分割は、分離パッケージ型でも発生するのだが、さらにJs内に取り込むという2段階の分割過程があり、意図通りに表示されないことがある。
(2) 分離パッケージ型
a. 利点
Js、HTML、CSSをそれぞれ分離しながらも一つのコンポーネントファイル(たとえば、.vue)にまとめるので、関心が絞られて分かりやすい。
また、Js、HTML、CSSのコンテキスト(文脈)が別れているので読みやすく、コーダーさんが作ったものを分割するときも比較的手間が少ない。
タグ内に、独自属性を埋め込むことで、HTMLベースで構造ををプログラム化できるので、見た目を意識したい人には分かりやすい。ここは、Jsベースが良い人と好みが分かれるところかもしれない。
b. 問題点
HTMLやCSSをコーダーさんが作った場合分割する手間が発生する。
また、HTMLに独自属性を埋めていく手間が発生する。
4. JavascriptとHTMLの関係:コンポーネントライブラリとjQuery
(1) 有名ライブラリはJsとHTMLが密接に依存する
上記にあげたライブラリを使うと、JsとHTMLは完全に独立しているわけでなく、HTMLを分割し、Jsに内包したり、HTMLに独自属性を付与するという依存性、手間が発生する。
そもそも、そうしないとコンポーネントはつくれないだろうか?
そんなことはない。
(2) HTMLを壊さずに操作するjQueryの視点
jQueryはコンポーネントを作るためのライブラリではない。あくまでもHTML(DOM)を手軽に操作するシンタックスシュガー、ユーティリティライブラリである。
ただ、このライブラリの観点は、HTMLをReactのようなJsベース型のように内側から操作するのではなく、外側から操作する。
また、Vueのような分離パッケージ型のように独自属性も挿入しない。
つまり、jQueryは、HTMLと完全に独立した存在で、HTMLを変更せず、完全に外側から操作する。
コーダーさんがつくったコードを分割することも、変更することもなく操作することができる。
(3) jQueryの欠点:Js内の値(モデル)とHTML(DOM)の関係性が曖昧、不定
jQueryは前述したとおり、コンポーネントをつくることを目的にはしていない。
なので、書き手によっていろいろな操作コードが書かれることになり、可読性が必ずしも保たれない。
ReactのようにJsとHTMLの関係性が明確であれば、コードリーディングは容易であるが、jQueryの場合そのルールがないので、コードリーディンが必ずしも容易とはならない。
特に、データモデルというJs内でのデータとDOMとの関係性をどうつくるかは明示されないので、書き手任せになってしまう。
ここは、ReactのようなJsベース型ライブラリの方が優位である。
5. JsとHTMLを独立化させる利点
jQueryにはJsとの関係性が曖昧、不定になるという欠点はあったが、JsがHTMLと独立化するというのは利点もある。
(1) 構造と振る舞いを独立し、異なるデザインに対応
自社サービスなどでは、デザインやHTMLの構造がそう大きく変わることはないが、受託開発の場合、デザインもHTML、CSSも案件ごとに変わることがある。
それを自社開発したコンポーネントに合わせて変換させていくこともできるが、やはり分割や調整の手間が発生する。
JsとHTMLが依存関係になければこのようなことはあまり発生しない。適宜、HTMLにidやclassなどセレクターを打ち直すか、Js側で指定するセレクターを変えるだけでよい。
(2) JsとHTML/CSSの技術的な変化速度の違い
Jsの技術変化の速度は速い。一方、HTMLやCSSの仕様が変化される速度はとても遅い。つまり、JsとHTMLが依存化している場合、Jsの変更にあわせてHTMLやCSSも変更させる可能性がある。
JsとHTMLを独立しておけば、Jsが変わろうともHTMLとCSSの構造やスタイルは変更することは少ない。
6. JsとHTMLを独立させつつ対応付け
(1) ReactとjQueryの視点を合わせると良いのでは?
ここまでにみてきたとおり、有名なコンポーネントライブラリはHTMLとの依存性が高い。けれど、それは表裏一体で、HTMLとJsとの関係性が分かりやすくもある。
ただ、ソースコードとして、それらを混ぜ合わせておく必要があるだろうか?
コードのルールとしてそれがあればよいのではないだろうか?
そうすることで、JsとHTMLの依存による問題も起きないが、コードリーディングもしやすくなる。
つまり、ReactのようなDOMをJsのクラスで表現しつつ、jQueryのようにHTMLに変更を加えず、操作する方法であれば、可読性と独立性が保たれる。
ただし、VueのようにHTMLに属性をうって、HTMLをみながら構造を操作するのが好きな人は直感性が失われるかもしれない。
ただ、コーダーさんが作ったものを分割する手間は減るので、かなり効率性はあがると思う。
ちなみに、外側からDOMを対応させるクラスを作るのであればAngularのComponentクラスがよいと思う。クラスのプロパティにDOMを指示するselectorを持ったせて、対応づけをするのは良いと思う。
Angular公式:Introduction to components and templates
export class HeroListComponent implements OnInit {
heroes: Hero[];
selectedHero: Hero;
constructor(private service: HeroService) { }
ngOnInit() {
this.heroes = this.service.getHeroes();
}
selectHero(hero: Hero) { this.selectedHero = hero; }
}
(2) Simulacra.js:JsとHTMLを独立化させたデータバインディングライブラリ
このような考えができるライブラリとしては、以下のライブラリがある。要は、SPAでいうところのデータバインディングの問題で、HTMLを変更しないデータバインディングライブリである。
ただし、双方向バインディングではなく一方向だけである。そもそも、双方向はいつも必要だろうか?
JsでHTMLのセレクターをキーとするオブジェクトリテラルをつくり、ライブラリに渡すと、データバインディングしてくれる。
<template id="product">
<h1 class="name"></h1>
<div class="details">
<div><span class="size"></span></div>
<h4 class="vendor"></h4>
</div>
</template>
上記のタグに対応するモデルとなるオブジェクトリテラルを作る。
var state = {
name: 'Pumpkin Spice Latte',
details: {
size: [ 'Tall', 'Grande', 'Venti' ],
vendor: 'Coffee Co.'
}
}
バインディング。
var bindObject = require('simulacra') // or `window.simulacra`
var template = document.getElementById('product')
var node = bindObject(state, [ template, {
name: '.name',
details: [ '.details', {
size: '.size',
vendor: '.vendor'
} ]
} ])
document.body.appendChild(node)
個人的に、全体的な視点や発想はいいと思うが、モデルとセレクタのバインディングコードが無駄な気がしている。
この部分もなくすことができる。(自作したことがある)
(3) eleventy.js(静的サイトジェネレーター):HTMLの再利用
JsとHTMLを分けると、HTMLを再利用するときどうするのかというと、シンプルに静的サイトジェネレーターのinclude機能を使えばいいと思います。
オススメの静的サイトジェネレーターはJsで動く「11ty:イレブンティ」。
デプロイ前に動的に作りたいなら、11tyでJsのフィルター関数を作ってしまえばいいと思います。
コーディングが楽です。
わざわざGatsbyみたいにJsでコンポーネント作らなくても、pug、nanjucks、markdown、htmlなどでHTMLを作れます。
もちろん、フィルター関数とかも作れるので、繰り返し作業とか動的な処理も対応できます。
7. まとめ:コンポーネントという「発想・視点」は大切
(1) コンポーネントライブラリを使う煩わしさ
この記事を書いた動機は、受託をしていると毎回デザインがかわり、HTMLやCSSも変わる事が多く、その都度、コンポーネントを別途つくる手間が凄くめんどくさかった。
また、わざわざコーディングしてあるHTMLをバラすという手間も無駄に感じた。
その他、VuexやReduxなどの状態管理が煩わしく、そもそも双方向バインディングなどをせず、ワントランザクションでSPAをつくれば、状態も複雑化しないので、使う必要をあまり感じなかった。
というように、コンポーネントという「考え方・設計」はいいのだけど、その実現方法について無駄や手間を感じたので、こうした記事を書いてみた。
(2) コンポーネントライブラリを使う場所(自社サービス・大規模開発)
利用シーンによっては煩わしいのだけど、自社サービスの大規模開発の場合は、ReactやVueなどの有名なコンポーネントライブラリを使うのがよいと思う。
その方がドキュメントの整備も公式に任せればいいし、ネット上に情報が沢山ある。
またコーディングを規制することができるので、書き方をプログラマー間で統一させやすい。
自社サービスの場合、デザインやHTMLの構造などもコンポーネント側に合わせた開発できる。
裏返せば、受託で毎回デザインが変わる場合、必ずしもコンポーネントライブラリを使うべきかは考えようだと思う。
以下に分かりやすいコーディング設計ができるかに依存する。でも、それができないと考えるなら、有名なライブラリを使うのが無難だと思う。
(3) コンポーネントは「発想・視点」が大切
コードを独立化、局所化するという発想がコンポーネントはよいと思う。
これは疑義なく支持したい。
けれど、その実現方法については、ケースバイケースかなという気もする。
自社サービスか受託開発、大規模か小規模か、などプロジェクトの前提条件によっても異なると思う。
とにかくコンポーネントを作る手間はどんなものでも発生するのだけど、できるだけその手間が最小化するものがよいと思う。