Reactにおけるスタイリング手法まとめ
開発初期に「どのスタイリング手法でいくべきか」などとよく悩むと思います。
筆者ならそこで「悩むよりもとりあえず書き始めよう」と言いたいところですが、規模が大きいプロジェクトなどでは途中でスタイリング手法を変えようとすると大きな負担になったりと柔軟な変更ができないことがあります。
だがしかし!!
Google先生で調べようものなら
「CSS-in-JSをやめた理由」
「CSS Modulesをやめた話」
などなど...
じゃあ何ならええねん!
そもそもReactにおけるスタイリング手法は多種多様で、時代によって流行が移り変わっていくものでもあり、Googleのトップに来るのは古い情報ばかりです。
そんな悩めるあなたにこの記事では筆者が利用したことのあるスタイリング手法を、CSS-in-JSやCSS Modulesに限らず、筆者の使用感もふまえて紹介していきます。
古い情報だけでなく、zero-runtime CSS-in-JS
やTailwind CSS
などこれからのデファクトスタンダードになりうる手法も記載してあるので、あなたがスタイリング沼から脱出する手助けになれば幸いです。
なおこの記事ではwebpack
などモジュールバンドラーを利用したフロントエンドフレームワーク(create-react-app
など)を利用している前提で話を進めていきます。
グローバルスタイル
.Button {
padding: 20px;
}
import './Button.css';
const Button = () => <div className="Button" />;
普段JSフレームワークを触っていない人からすれば一番直感的な記法です。
ただしスタイルの適用先(スコープ)がグローバルですので、名前の衝突を避けなければなりません。
だからと言って「それならばBEM記法を...」となるのはオススメしません。
HTMLとCSSだけだとBEM記法になりますがReactなどのJSフレームワークはその先に連れて行ってくれます。
また、同じスタイルを使いまわしたいという場合でも、基本的にはクラスではなくコンポーネントを使いまわすというのがReact的考えです。(コンポーネント駆動開発)
そもそも一度に複数のスタイルを読み込むのではなく、コンポーネントが読み込まれたときにそのコンポーネントのスタイルだけが読み込まれたほうが、初期レンダリングにかかる時間が少なくUXの向上に繋がります。
使用する場面
App.js
や_app.js
のようなルートファイルで、グローバルCSSを適用する時(リセットCSSやライブラリ使用時)でしょう。
styleプロップ
<div style={{ height: 10 }}>
Hello World!
</div>
とても簡単にスタイルを適用できます。
しかしReact公式サイトにも書いてありますが、再レンダリングの度に計算されるのでパフォーマンス的にオススメできません。
また、疑似クラスやメディアクエリにも対応していません。
使用する場面
const Hello = () => {
const [height, setHeight] = useState(0)
return (
<div style={{ height }}>
Hello World!
</div>
)
}
ダイナミックスタイリングとして活用できます。
と、言いたいところですが...
そもそもstyleプロップはパフォーマンスが悪く、ダイナミックスタイリングをしたい場合は読み込むcssを切り替えたり、CSS custom properties (variables)
や後述するCSS-in-JS
を利用した方が何百倍もいいです。
CSS Modules
.error {
color: white;
background-color: red;
}
import styles from './Button.module.css'
const Button = () => (
<button className={styles.error}>
Destroy
</button>
)
CSS Modulesはコンポーネントレベルでスタイルを適用することができ、名前の衝突なども考える必要がありません。
コード分割した上でminifyされた複数のcss
ファイルへとコンパイルされ、描画のために読み込むCSSを最小限にしてくれるため、パフォーマンスは最高です。
プリプロセッサーであるSass
やPost CSS
との相性も良く、Post CSS
を使用するならコンパイル時に自動でベンダープリフィックスを入れてくれるAutoprefixer
のようなものは必須でしょう。
また、後述のCSS-in-JS
とは違って生のCSSであるため、開発環境で拡張機能を入れなくても入力補完などしてくれます。
...じゃあなんで全面的に使われていないの?
ファイルが増える事によるDX低下
当たり前ですが、1コンポーネント毎に1ファイルを追加しなければなりません。
昔からCSSを触ってきている人にとっては「何言ってんだこいつ」となると思いますが、3,4行のスタイルを適用したいだけでも1ファイル作成しなくてはいけなく、後述するCSS-in-JS
の方がだいぶ書きやすいです。
追い風ではない
そもそもCSS Modules
自体ががとても古い技術で、CSS Modules
を使用する際に必要なwebpack
のcss-loader
ではdeprecatedとしてメンテナンスステージになるそうです。
Sorry, it is out of scope CSS modules spec, In the near future we want to deprecate CSS modules, there are a lot of other solutions - BEM, CSS-in-JS, CSS-in-JS without runtime https://github.com/callstack/linaria, future CSS module (WICG/webcomponents#759), do not confuse with current CSS modules, Web Components and shadow DOM.
CSS modules is maintenance stage (only fixes), it is really old technology and very controversial. We will support them for a while so that all developers can migrate, but no new features, sorry. You can write an own loader based on the current module code.
引用: https://github.com/webpack-contrib/css-loader/issues/1050#issuecomment-592541379
CSS-in-JS
CSS-in-JS
と言っても様々なライブラリがあります。
筆者は古のradium
からstyled-jsx
やTheme UI
まで様々なCSS-in-JS
に触れましたが、仕組みも異なれば記法も大きく異なってきます。
その中でも有名なstyled-components
とemotion
、そしてzero-runtime CSS-in-JS
なlinaria
について触れていきます。
styled-components (CSS-in-JS)
import styled, { css } from 'styled-components'
const Button = styled.a`
display: inline-block;
border-radius: 3px;
padding: 0.5rem 0;
margin: 0.5rem 1rem;
width: 11rem;
background: transparent;
color: white;
border: 2px solid white;
${props => props.primary && css`
background: white;
color: black;
`}
`
const Demo = () => (
<div>
<Button primary>
Apply
</Button>
<Button>
Cancel
</Button>
</div>
)
上記はstyled-components
の一般的な記法です。
とても直感的でわかりやすいですね。
ダイナミックスタイリングはもちろん、自動でベンダープリフィックスを挿入してくれたりSass
のようなネストセレクターもデフォルトで対応しています。
styled-components
は検索したらまず出てくる王道のCSS-in-JS
です。
だがしかし、みんなが使っているからいいとは限りません。
Googleで出てくるCSS-in-JS
をやめた話にはほぼstyled-components
の愚痴が出てくるでしょう。
パフォーマンス
一度レンダリングしたあとは差分だけレンダリングしてくれるそうですが、jsからスタイルを動的に生成しているので、その一度のレンダリングコストが若干重いです。
普通は問題にはなりませんが、懸念すべき点でしょう。
いちいち命名しなくてはいけない
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
このようにTitleの外から少しスタイリングしたくてもWrapperコンポーネントを作ります。これだけならいいですが複雑になるとInnerやらOuterやらContainerやらStyled〇〇などと命名しなくてはいけません。
また、これはスタイリング用のコンポーネントなのか、それとも機能をもったコンポーネントなのかというのが非常に分かりづらいです。
しかし、これを解決してくれるcss prop
という機能があります。
<Button css="padding: 0.5em 1em;" />
こちらもstyleプロップと違って複雑な記法が使えたり、styleプロップよりはレンダリングコストが軽いです。
emotion (CSS-in-JS)
import { css, cx } from '@emotion/css'
const Demo = () => {
const [color, setColor] = useState('white')
return (
<div
className={css`
padding: 32px;
background-color: hotpink;
font-size: 24px;
border-radius: 4px;
&:hover {
color: ${color};
}
`}
>
Hover to change color.
</div>
)
}
emotion
にはstyled-components
のようなcss prop
やstyled
もありますが、別で上記のようなフレームワークにとらわれない@emotion/css
があります。
メリットやデメリットはstyled-components
とほぼ一緒です。
linaria (CSS-in-JS)
import { styled } from 'linaria/react'
const Button = styled.button`
background: ${props => props.color};
padding: 16px 24px;
&:hover {
color: ${props => props.color};
background: white;
}
`
linariaはzero-runtime CSS-in-JS
であり、レンダリング時ではなくビルド時にcssを生成します。
また、propによるダイナミックスタイリングは自動的にCSS custom properties (variables)
に変換されます。
よってCSS-in-JS
の書きごこちを残したまま、生のcssを読み込んでいるのと同じパフォーマンスを出せます。
しかし、css prop
はもちろん使用できず、また下記のようなcss
タグを使用している場合にはダイナミックスタイリングはできません。
import { css } from '@linaria/core';
import { modularScale, hiDPI } from 'polished';
import fonts from './fonts';
const header = css`
text-transform: uppercase;
font-family: ${fonts.heading};
font-size: ${modularScale(2)};
${hiDPI(1.5)} {
font-size: ${modularScale(2.5)};
}
`;
const Header = () => <h1 className={header}>Hello world</h1>;
CSS-in-JS
全般に言えることですが、CSS Modules
と同じく、描画のために読み込むスタイルは分割されていて最小限であるため、そこのパフォーマンスも期待できます。
また、上記のようにJavaScriptの定数を簡単に使用できたり、polished
ライブラリにあるようなヘルパー関数を使えるのがメリットでしょう。
何よりファイルをjsとcssの間で切り替えなくていい...
CSSフレームワーク
過去にはBootstrapのようなCSSフレームワークが流行りましたが、このReactにおいて一つとても相性の良いCSSフレームワークがあるので紹介します。
Tailwind CSS
<div className="md:flex space-x-4 text-white">
<button className="bg-blue-400">Apply</button>
<button>Cancel</button>
</div>
ユーティリティファーストのCSSフレームワークで非常にReactと相性がいいです。
CSSフレームワークであるがゆえ、最小限のスタイルを順次読み込んでいくという芸当はできませんが、その代わりPurgeCSS
で使用していない不必要なクラスを消しバンドルサイズを最小限にすることができます。
メディアクエリやhoverやfocusなどの状態も、prefixをつけるだけで簡単に指定できます。
また、具体的な数値を指定せずに定数を使用するため、スタイルに一貫性を保てます。
もちろん具体的な数値も使えますし、テーマとしてカスタマイズすることもできます。
また、CSS Modules
にも@apply
で組み合わせたり、CSS-in-JS
にもtwin.macro
というライブラリで組み合わせて使う事も可能で、汎用性が高いです。
他にもTailwind CSSを使ったこんなUIライブラリまで...
何と言ってもその書き心地...
className
に適用したいクラスを追加していくだけですぐにスタイルが適用できるのでDXは最高です。
もちろん開発環境の拡張機能により入力補完が可能です。
複雑なスタイルはわかりにくい
<figure class="md:flex bg-gray-100 rounded-xl p-8 md:p-0">
<img class="w-32 h-32 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512">
<div class="pt-6 md:p-8 text-center md:text-left space-y-4">
<blockquote>
<p class="text-lg font-semibold">
“Tailwind CSS is the only framework that I've seen scale
on large teams. It’s easy to customize, adapts to any design,
and the build size is tiny.”
</p>
</blockquote>
<figcaption class="font-medium">
<div class="text-cyan-600">
Sarah Dayan
</div>
<div class="text-gray-500">
Staff Engineer, Algolia
</div>
</figcaption>
</div>
</figure>
しかし、メディアクエリや、適用したいスタイルが多くなるとこのように長くなり分かりにくくなります。
そもそも同じスタイルを複数回適用している場合はコンポーネントを分けた方が良い場合もあります。
また、クラス切り替えでは実装できないダイナミックスタイリングは別の方法でやる必要があります。
UIライブラリ
Reactでスタイリングを考えると、Material UI
やAnt Design
などのUIライブラリの使用も視野に入ってくるのではないでしょうか。
UIライブラリにはデザインだけでなくModal
などの機能も付属していることが多いので便利ですよね。
しかしUIライブラリ全体の特徴として、学習に時間がかかるという点があります。(癖が強いことが多い)
そこで、カスタマイズがしやすいChakra UI
を紹介します。
Chakra UI
<Box p="4">
<Text
bgGradient="linear(to-l, #7928CA,#FF0080)"
bgClip="text"
fontSize="6xl"
fontWeight="extrabold"
>
Welcome to Chakra UI
</Text>
</Box>
このようにTailwind CSS
のようなユーティリティファーストでプロップから簡単にカスタマイズできます。
Button
コンポーネントなどのデフォルトのスタイルに癖がないというのも良い点です。
また、Modal
などのコンポーネントもa11yに対応済で機能も充実しています。
まとめ
必ずしも一つに絞らなくてよく、複数の手法を採用するのが現段階では現実的な判断です。
8割はTailwind CSS
、ダイナミックスタイルが必要になる2割はCSS-in-JS
のように、得意不得意によって柔軟に変えていくことがDXとUXそれぞれの向上に繋がるでしょう。
また、状態によるクラスの切り替えが簡単に書けるclassnames
や、UI開発を補助してくれるStorybook
を導入するとさらにスタイリングがやりやすくなります。
この記事を参考に良きスタイリングライフを送ってください!
Discussion
Emotionの2番目にアクティブな開発者が、ランタイムのCSS生成によるパフォーマンス問題を理由にCSS in JSを捨ててCSS Modulesに移行したらしいです。
ご参考までに。
そうなんですか!?最近の事情を追っておらず初耳です、有益な情報ありがとうございます!
記事にその情報を追加させていただきます!