Reactを使ったWebアプリケーションを開発する際、スタイル定義にCSS Modulesを使うことで、いろいろなメリットを得ることができます。 この記事では、CSS Modulesの概要とreact-css-modulesによる導入方法についてまとめました。
CSS設計の問題点
これはReactコンポーネントに限らない話ですが、CSSを設計する際、一般的に次のような問題があります。
- スコープがグローバルになる
- クラス定義の場所が一意的でない
- ファイル分割の指針が開発者に委ねられる
これを解決する既存の方法として、1はBEMなどの設計手法を取り入れたり、2-3はチーム内でルールを整備する、などが考えられます。
ただ、Webアプリケーションのコンポーネントは原則的に閉じた世界なので、閉じられたコンポーネント単位でスタイルを用意し、これに一意のローカルなクラス名を与えれば、上記の問題はすべて解決することができます。
これを実現するのがCSS Modulesです。
CSS Modulesとは
CSS Modulesは、すべてのクラス名が標準でローカルスコープとなるCSSファイルのこと、またそれを扱う仕組みのことをいいます。
1つのコンポーネントに対応するCSSを用意すれば、そのCSSは対応するコンポーネント専用になり、たとえクラス名が被っても他のコンポーネントに影響を与えることはありません。 BEMのような長ったらしいクラス名は必要なくなります。
また、CSSはコンポーネントから参照するため、クラス定義の場所も分かりやすく、必然的にファイル分割の指針もできます。
css-loaderによるモジュール化
webpackのcss-loaderを使うと、次のように書けます。
デフォルトではグローバルスコープになるため、ローカルスコープにするには:local
構文をつける必要があります。
.globalClass {
...
}
:local(.localClass) {
...
}
こうすることで、次のようなrender()
メソッドをもつReactコンポーネントを書くと:
render() {
return (
<span className={styles.localClass + ' globalClass'}></span>
);
}
次のようなHTMLが出力されます。
<span class="EYtsuvGX0ck2MVhh_I1Zc globalClass"></span>
このように、.localClass
が一意のクラス名になり、他のスタイルとの衝突の可能性がなくなります。
問題点
css-loaderによる対応では、次のような問題が残ります。
className
にグローバルスコープとローカルスコープのクラス名が混在しており分かりづらい- クラス名にキャメルケースしかつかえない
styles
オブジェクトを毎回参照しなければならない
BootstrapなどのCSSフレームワークを導入する場合、グローバルスコープをもつクラスを頻繁に付与することになり、このままでは可読性が大きく下がってしまいます。
この問題を解決する方法として、Reactの場合react-css-modulesを用いる方法があります。 これにより、HTMLを次のように書けます。
<div className='global-class' styleName='local-class'></div>
この導入について以下で説明します。
動作環境
この記事の内容は、次の各バージョンで動作を確認しています。
- webpack v1.13.2
- css-loader v0.25.0
- react-css-modules v3.7.10
手順
1. 開発環境を構築する
まず、webpackによる開発環境を構築しておく必要があります。 まだの場合は、次の記事を参考に構築します。
2. インストールする
次に、react-css-modulesをインストールします。
$ npm install --save react-css-modules
3. ローダーの設定を変更する
上記の記事の設定の中で、ローダーの設定として次のようにmodules
とlocalIdentName
の2つのパラメータを付与します。
この設定では、次の2点を行なっています。
- クラス定義のデフォルトスコープをグローバルからローカルに変更する
- 生成されるクラス名を分かりやすくする(デバッグ対策)
module.exports = {
module: {
loaders: [
{
test: /\.s?css$/,
loader: ExtractTextPlugin.extract([
'css?modules&localIdentName=[name]---[local]---[hash:base64:8]',
'sass'
])
}
]
}
}
4. 実装する
以上の設定により、次のようにスタイル定義を行なっていけるようになります。
.local-class {
:
}
:global(.global-class) {
:
}
import React, { Component } from 'react';
import CSSModules from 'react-css-modules';
import styles from './path/to/css';
class App extends Component {
render() {
return (
<div styleName='local-class' className='global-class'></div>
);
}
}
export default CSSModules(App, styles);
おまけ
上記の設定で、デフォルトスコープがローカルになりました。
ただ、CSSフレームワークをimport
するときなどはグローバルで読みたいと思います。
これは、次のように書けばよいです。
:global {
@import './path/to/css';
}