styled-componentsの基礎知識 第1回 styled-componentsの前提知識
このシリーズではCSS in JS用のライブラリであるstyled-componentsについて解説します。第1回目はCSS in JSの予備知識と、styled-componentsの使いどころや、基本機能について解説します。
- カテゴリー
- JavaScript >
- ライブラリ
発行
はじめに
styled-componentsは、ReactコンポーネントにCSSのスタイルをどのように当てるかという課題をきっかけに作られたCSS in JSのライブラリです。
CSS in JSは読んで字のごとく、JavaScriptの中にCSSの記述を取り込んでしまう手法のことで、styled-componentsのほかにもglamorous、Emotion、Linariaなどのライブラリが存在します。
次の図はstyled-components、glamorous、Emotion、Linariaのダウンロード数を比較した折れ線グラフです。styled-componentsがもっともダウンロード数が多いことがわかります。
出典:styled-components vs glamorous vs emotion vs linaria | npm trends
今回、数あるCSS in JSのライブラリの中からstyled-componentsを取り上げて紹介するのは、このライブラリを通じてCSS in JSがどのようなものかを感じとって欲しいという思いがあり、それにはstyled-componentsが最適であると考えているためです。
CSS in JSでJS内にCSSを定義するには、オブジェクトリテラルを用いる方法とタグ付けされたテンプレートリテラルを用いる2つの方法があります。その2つ目の方式の先駆けになったと考えられるのがstyled-componentsであること、そしてこのタグ付けされたテンプレートリテラルがCSS in JSにとって非常に重要であると考えています。
また、後発のライブラリであるEmotionにはstyled-componentsにインスパイアされたこと、 Linariaにはstyled-component風の文法といった具合に紹介されていることもあり、styled-componentsを学べば別のCSS in JSライブラリへの変更もスムーズにできるはずというのも理由の1つです。
今回はstyled-componentsを理解するための周辺知識として、まずはCSS in JSのことについて触れておきたいと思います。
なお、この記事はstyled-componentsの解説がメインであるため、Reactコンポーネント、ReactにおけるCSS in JSなどReactの基礎知識については、深く触れません。
CSS in JSとは
CSS in JSが生まれた背景には、昨今のフロントエンドではコンポーネント指向で開発を進めることが一般化したことがあると考えられます。
外部の要因から独立したコンポーネント単位でJavaScriptの開発をすすめることで、UI上の処理の見通しが良くなりテスト容易性や保守性の向上、さらには開発者が快適に開発できることにもつながります。
このコンポーネント指向がCSSに対しても求められるのは自然な流れで、CSS in JSであればCSSの指定をコンポーネントレベルで抽象化することができます。
JavaScript内にCSSの記述をするのであれば、まず思いつくのはオブジェクトリテラルで指定する方法でしょう。Reactのコンポーネントを返すstyled-componentsであれば、JavaScript(下記の例ではJSX)にCSSを次のように指定することが可能です。
styled-componentsの記述例
import React from 'react';
import styled from 'styled-components';
const Title = styled.h1({
fontSize: "1.5em",
textAlign: "center",
color: "palevioletred"
});
const Wrapper = styled.section({
padding: "4em",
background: "papayawhip"
});
const App = () => (
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
);
export default App;
styled.h1
は受け取ったCSSの内容をもとにスタイルがあてられたh1
要素のReactコンポーネントを返します。同様にstyled.section
はsection
要素のReactコンポーネントを返します。これらスタイルがあてられたコンポーネントを組み合わせていくのがstyled-componentsの基本的な進め方になります。
このAppコンポーネントを描画すると、画面上には次のように表示されます。
実行時には、headタグとbodyタグにそれぞれ次のように出力されます。
headタグ内への出力
<style data-styled="active" data-styled-version="5.0.1">
.eSoyyg {
font-size:1.5em;
text-align:center;
color:palevioletred;
}
.bAHPlK {
padding:4em;
background:papayawhip;
}
</style>
bodyタグ内への出力
<section class="sc-fznZeY bAHPlK">
<h1 class="sc-fznKkj eSoyyg">Hello World!</h1>
</section>
もちろん、h1
とsection
だけでなく、h2
〜h6
、div
やspan
、a
など、スタイルをあてることが可能なHTML要素全般がstyled-componentsから同様に提供されます。
これらHTML要素は、styled-components/domElements.jsにて定義されています。
styled-componentの使いどころ
CSS in JSのライブラリはコードサンプルに必ずといっていいほどReactが取り上げられますが、CSS in JSは特定のライブラリやフレームワークに縛られるものではありません。にもかからわず、Reactとの組み合わせで紹介されることが多い理由のひとつには、もともとコンポーネントにスタイルを当てるための手段が提供されていたVueやAngular*とは異なるReactの立ち位置があったと思われます。
*注:コンポーネント指向でCSSを適用するVueとAngularの取り組み
Vueは、拡張子.vue
のファイル内にコンポーネントのJavaScriptとスコープ付きのCSSを定義することができる単一ファイルコンポーネントの機能を提供しています。
Angularはコンポーネントのメタデータとして、コンポーネント用のスタイルを指定する機能を提供しています。
コンポーネント指向のCSSは、CSS in JSでなくてもwebpackなどのバンドラーの機能を使用してCSS Modulesで実現することもできるでしょう。
筆者としても、CSS in JSを強く勧めるつもりはなく、プロジェクトの内容や開発チームの構成次第だと考えています。次のような傾向があるのであれば、CSS in JSの採用を検討してみると良いでしょう。
- CSSの設計やコーディングの担当者が、JavaScriptファイル内にCSSを記述するCSS in JSに対して抵抗がない
- バンドラー独自のCSSのimportや、煩わしいローダー設定を回避したい
- 手軽にJavaScriptとCSSの間で値を共有したい
上記の1.と2.については、そのプロジェクトのメンバーのスキルセットや、好みの問題に左右される要因です。
3.の手軽に値を共有できるというのは、CSSがJavaScriptに含まれるCSS in JSならではの特徴と言えます。
これまでにJavaScriptまたはCSSのファイルに、次のようなコメントを書いた経験はないでしょうか?
とあるJSファイル内での記述
// 50はstyle.cssの.headerクラスのheightと揃える
const containerHeight = `${height + 50}px`;
とあるCSSファイル内での記述
/* 50pxはcontainer.jsのcontainerHeight変数のheightと揃える */
.header { height: 50px };
筆者の経験では、このような対応をせざるを得なかったのは複雑なインタラクションUIやアニメーションが求められるシステムでした。
その反面、たとえばHTML・CSSのマークアップを担当する開発者とJavaScriptを書く開発者で完全に分業したいケースにはCSS in JSは不向きでしょう。
【ワンポイント】Custom propertiesを使用したJavaScriptとCSS間での値の共有
2020年現在では、Internet Explorerを除くモダンブラウザでCustom propertiesが使用可能です。この機能とCSSStyleDeclaration.setProperty使用することでCSS in JSに頼らずとも、JavaScriptとCSS間で値を共有できます。
詳しくはこちらの記事を参照してください。
styled-componentsの基本的な機能
CSS in JSにしたことで、これまでできていたことができなくなるのは困ります。
たとえば、SassやPostCSSなどはどうなるのでしょうか? ほかにも状態などの外部の要因によって、ボタンを活性・非活性用のスタイルを切り替えたいケースに、どのように対処するのか気になるところです。
このシリーズを通してstyled-componentsのさまざまな機能について解説していきますが、まずは基本的なこの2点について押さえておきます。
CSSプリプロセッサ
styled-componentsには、CSSプリプロセッサとしてstylisが組み込まれているため、SassのSCSS記法のような書き方がサポートされています。
そのため、&
の利用も含めたネストの記述や、出力時にはベンダープレフィックスが追記されることを前提にCSSを書くことが可能です。
次のように書けば、マウスのホバー時にClick me!の文字が青色になるスタイルをあてることができます。
&によるhoverへのスタイル指定
import React from 'react';
import styled from 'styled-components';
const Button = styled.button({
color: "red",
"&:hover": {
color: "blue"
}
});
const App = () => (
<Button>
Click me!
</Button>
);
export default App;
【ワンポイント】サポートするブラウザについて
styled-componentsには、browserslistが備わっていないため、サポートするブラウザや対象とするバージョンの範囲を任意に指定することはできません。
styled-componentsは、Reactの最新バージョンがサポートしている範囲と同じ範囲をサポートします。
外部の要因によるスタイルの変更
外部の要因によって、UIの活性・非活性用のスタイルを切り替えたいケースがあります。
たとえば、項目がいくつか並んでいて選択中のものとそうでないもので見た目を変えたいケースが考えられます。
styled-componentsでは、Reactの機能であるコンポーネントのprops
を受け取ってこの処理を書くことができます。
下記のように書くと、propsにselected
を指定した1つ目の項目の文字列は赤になり、それ以外の指定のない項目の文字列は灰色になります。
propsによるスタイルの切り替え
import React from 'react';
import styled from 'styled-components';
const Item = styled.span({
color: props => props.selected ? "red" : "gray",
});
const App = () => (
<div>
<Item selected>
[ Select me! ]
</Item>
<Item>
[ Select me! ]
</Item>
<Item>
[ Select me! ]
</Item>
</div>
);
export default App;
CSSをオブジェクトリテラルで指定する問題と対策
ここまでCSS in JSやstyled-componentsの基本的ことについて紹介をしてきましたが、これまで例示したCSSの書き方には実は重大な問題があります。冒頭で少し触れましたが、CSSの各指定をオブジェクトリテラルで行っている点です。
これの何が問題かというと、とにかく通常のCSSの書き方ができないという点につきます。少なくとも、通常のCSSの文法とオブジェクトリテラルによる指定には下記の違いがあげられます。
- ケバブケース(文字区切りにハイフンを使用)とキャメルケースの違い(
font-size
とfontSize
) - CSSの値は基本的にクォーテーションで囲わないといけない
- 末尾のセミコロンとカンマの違い
長らくCSSを書いてきた開発者にとって、この違いによるストレスは非常に大きなものになります。
この違いがそんなにストレスになるのかと思われるかもしれません。筆者もCSS in JSを初めて試してみた際にはオブジェクトリテラルによる書き方をしていましたが、この少しの差異をすべてのスタイルの指定で気にしなければいけないのは相当なストレスでした。
また、CSSファイルからstyled-componentsにコピー&ペーストしたいケースやその逆のケースもあるでしょう。CSSファイルからそのままスタイルをコピー&ペーストしてもオブジェクトリテラルによる書き方をしているなら、CSSはそのまま使えません。
styled-componentsは、次のようなタグ付けされたテンプレートリテラルによる書き方も可能にすることで、この問題に対処しています。
タグ付けされたテンプレートリテラルによる書き方
import React from 'react';
import styled from 'styled-components';
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
const App = () => (
<Wrapper>
<Title>
Hello World!
</Title>
</Wrapper>
);
export default App;
どちらの書き方もできますが、公式サイトのサンプルコードを確認すると、ほぼすべての例でオブジェクトリテラルではなくこのタグ付けされたテンプレートリテラルによる書き方で解説されています。おそらく、筆者がストレスを感じたのと同じ理由でこちらの書き方が選択されるケースが多いためだと思われます。
【ワンポイント】2種類の書き方について
styled-componentsの公式サイトでは、タグ付けされたテンプレートリテラルの書き方が推奨されているわけではありません。こちらの公式のブログ記事では、どちらか一方の書き方を推奨したり強制したりしないことが明言されています。
ただ、バッククォーテーション(
`)の中に、CSSを書くという方法は、文法として正しいのだろうかと違和感を感じた方もいるのではないでしょうか?
これはstyled-components独自のものでもなければ、ビルド時や実行時にstyled-componentsが特別な処理をしているわけでもありません。
この文法はれっきとしたECMA Scriptの文法です。タグ付けされたテンプレートリテラルと呼ばれるもので、ECMA Script 2015で導入された機能です。MDNではテンプレートリテラルのページ内にて解説されています。
CodeGridでも過去にECMAScript 2015の新機能のシリーズ内で、このタグ付けされたテンプレートリテラルのことを紹介しています。
先ほどのCSSプリプロセッサとpropsを使った例は、タグ付けされたテンプレートリテラルを使うことで、次のように書き換えられます。
&によるhoverへのスタイル指定(タグ付けされたテンプレートリテラル版)
import React from 'react';
import styled from 'styled-components';
const Button = styled.button`
color: red;
&:hover {
color: blue;
}
`;
const App = () => (
<Button>
Click me!
</Button>
);
export default App;
propsによるスタイルの切り替え(タグ付けされたテンプレートリテラル版)
import React from 'react';
import styled from 'styled-components';
const Item = styled.span`
color: ${props => props.selected ? "red" : "gray"};
`;
const App = () => (
<div>
<Item selected>
[ Select me! ]
</Item>
<Item>
[ Select me! ]
</Item>
<Item>
[ Select me! ]
</Item>
</div>
);
export default App;
このようにSCSS風の記法を含めた通常のCSSを、そのまま書くことが可能であることに加え、propsの値によるスタイル変化はこの書き方でもサポートされます。
まとめ
今回は、CSS in JSとstyled-componentsの基本的なことについて解説し、最後にタグ付けされたテンプレートリテラルのことを取り上げました。
styled-componentsはもちろん、この書き方をサポートするCSS in JSライブラリを使用する際は、このタグ付けされたテンプレートリテラルのことを詳しく理解しておいたほうがよいと筆者は考えています。
そこで、次回はこのタグ付けされたテンプレートリテラルに焦点をあてて、styled-componentsの解説を進めていきます。