React.jsの開発者であるvjeuxが「React:CSS in JS」というタイトルでTalkをしていて、その内容がなかなか興味深いものでReact.jsにも関係するものなので紹介しておきたいと思います。
また、このアプローチについては同じくReact.jsの開発者であるzpaoによる「React Through the Ages」というTalkでも言及されています。
CSSをスケールさせる時に問題になる点
- Global Namespace
- Dependencies
- Dead Code Elimination
- Minification
- Sharing Constantsn
- Non-deterministic Resolution
- Isolation
ここでいうスケールさせるというのは、Facebookくらいの規模のことだと思います。
Global Namespace
そのままですが、CSSでは全てはグローバルな空間なので、命名規則などで頑張る必要があります。
(Bootstrapは600個のグローバルな名前を定義しています。)
Dependencies
Componentとの依存関係を管理するのが難しいということです。
Component内でrequireCSSみたいなことをやってCSSを読み込むようにしたとして、例えばrequireするCSSが足りていなかった場合でも他の箇所でそのCSSがrequireされていればうまく動いてしまいます。
Dead Code Elimination
使われていないコードの検出が難しいということです。
Minification
class名のminificationについてですね。(する必要あるのかという疑問がありますが..)
これもtemplate(HTML or JS)とCSSの対応付けを行う必要があります。
Sharing Constants
CSSとJS側で変数の共有が難しいということです。
Non-deterministic Resolution
CSSでは詳細度が同じ場合、後に書かれた方が優先されます。
なのでrequireCSSなどの仕組みを使ってComponentと同様に非同期でCSSを読むようにした場合、読み込みの順番によってCSSの順番も異なり、最初にアクセスした時は大丈夫だけど、このページにいってから来ると表示がおかしくなるといったことも起きかねません。
<div class="foo bar">xxx</div>
.foo {
color: red;
}
.bar {
color: blue;
}
or
.bar {
color: blue;
}
.foo {
color: red;
}
それを回避するには詳細度を調整するなどが必要になります。
Isolation
React.jsでButtonというComponentを作った時に、このclassの中にあるbuttonのstyleという指定をしようとすると、Buttonがどのタグで実装されているのかを意識する必要があり、Componentがうまく分離出来ないということです。
<div className="foo">
<Button /> <!-- <div><button>xxx</button></div> -->
</div>
.foo > div {
...
}
ならばCSS in JS だ
上記のような問題でもSassのCSS Preprocessorなどを使ったり設計レベルで解決出来ることもありますが、CSSをJavaScriptのObjectにして、Componentのstyleに突っ込むことで上記のような問題が解決出来るのではないかというアプローチです。
つまりJSXとしてtemplate(HTML)をJSの中に持ってきたように、CSSもJSの中に持ってこようということです。
var style = {
container: {
backgroundColor: "#ddd",
width: 900
}
}
var Container = React.createClass({
render() {
return <div style={style.container}>{this.props.children}</div>;
}
});
このような関数を用意しておくことで、柔軟なstyleの指定が出来るようになります。
var assign = require('object-assign');
function m() {
var res = {};
for (var i=0; i < arguments.length;; ++i) {
if (arguments[i]) assign(res, arguments[i]);
}
return res;
}
<div style={m(
style.container,
{ marginTop: 10 },
this.props.isWarning && { color: "red" }
)}>xxx</div>
また、外からstyleを指定出来るようにしたい場合は、styleをPropとして公開しておくことで可能になります。
propTypes: {
style: React.PropTypes.object
},
render() {
return <div style={m(style.container, this.props.style)}>xxx</div>
}
定義したStyleを優先させたい場合は順番を変更すればOKです。
propTypes: {
style: React.PropTypes.object
},
render() {
return <div style={m(this.props.style, style.container)}>xxx</div>
}
このようにComponentに直接指定することで詳細度や順番なども意識しなくてよくなり、JavaScriptの世界に持ってくることでプログラマブルに処理出来るようになり共通化や継承なども実現することが出来て、その結果最初に書いた問題点を解決出来るというものです。
styleの定義も例ではComponentの中に書きましたが、別のファイルにまとめておいてrequire
して使うということももちろん可能です。
JavaScriptの世界にmarkupを持ってきたJSXのようにCSSもJavaScriptの世界に持ってくるこのアプローチ、どう思いますか?
残り2日、明日はReact.jsのこれからに思いを馳せたいと思います。