CSS ModulesでHTMLタグを使用した際の問題
React開発でおなじみのCSS Modulesとはコンポーネントごとにクラス名をカプセル化してくれる便利な仕組みです。
しかし、CSS Modulesはh1やinputなどのHTML要素(要素セレクタ)を直接書くと、スコープが効かずにグローバル汚染を引き起こすという問題があります。
例として、以下のようにh1タグで指定した場合は、About.tsxなどのほかのコンポーネント(ページ)にも適用されてしまいます。
h1 {
color: green;
}
また、CSS Modulesだとclass名の指定が「className={styles.sample} 」のようになり、「className="sample"」のように直感的に書けないので手間がかかり、可読性も良くないです。
import styles from './Home.module.css'
function Home() {
return (
<div className="RootHome">
<h1>Home</h1>
<p className={styles.sample}>sample</p>
</div>
)
}
export default Home@scopeでコンポーネント内だけに適用する方法
前述の問題はコンポーネント内に@scopeで範囲指定するためのCSSのclass名を付与して、@scope内に従来通りのCSSのコードを書くことで解消できます。
まず、コンポーネントに `Root${scopeName}` のCSSのclassを付与するための関数を以下のように作成します。
import type { ComponentType } from 'react'
export function scopeClass<P extends object>(Component: ComponentType<P>, scopeName: string) {
const scopeClassName = `Root${scopeName}`
return function WrappedComponent(props: P) {
return (
<div className={scopeClassName}>
<Component {...props} />
</div>
)
}
}Component.displayNameを使うと関数名がビルド時に難読化(minify)により名前が変わるので、引数から文字列で指定します。
次にHome.tsxやAbout.tsxなどのコンポーネントでscopeClassの関数を読み込んで、「export default scopeClass(Home, 'Home')」にしてコンポーネントを返します。
こうすることで「<div className="RootHome">」のようなclass名を付与した状態で返します。
import { scopeClass } from './ScopeClass'
import './Home.css'
function Home() {
return (
<>
<h1>Home</h1>
<p className="sample">sample</p>
</>
)
}
export default scopeClass(Home, 'Home');import { scopeClass } from './ScopeClass'
import './About.css'
function About() {return (
<>
<h1>About</h1>
<p className="sample">sample</p>
</>
)
}
export default scopeClass(About, 'About');あとはCSSで最初に「@scope (.RootHome) { }」を書いて、その中に記述すれば該当のコンポーネントだけに適用されます。
CSS Modulesではなくなるので、ファイル名はHome.cssのようにシンプルにできます。
@scope (.RootHome) {
h1 {
color: green;
}
.sample {
color: pink;
}
}About.cssなどのほかのコンポーネントのCSSも同様に@scopeで範囲を指定して書きます。
@scope (.RootAbout) {
h1 {
color: blue;
}
}ReactでCSS Modulesではなく@scopeを使用したサンプル
まとめ
CSS Modulesはクラス名の衝突を防げる便利な仕組みですが、h1やinputなどのHTML要素を直接指定するとグローバル汚染が発生するという問題があります。
一方、@scopeを使用すれば、コンポーネント単位でCSSの適用範囲を制限しつつ、従来のシンプルなCSSに近い書き方が可能で、Home.cssのような分かりやすいファイル名にできるメリットもあります。
ReactでCSSのスコープ管理を行いたい場合はCSS Modulesだけでなく、@scopeを利用した構成も選択肢の1つとして検討すると良いでしょう。


