React – Styled Componentsの使い方【CSS-in-JS】

Styled Componentsは、Reactのコンポーネントの内部でCSSを記述する方法(ライブラリー)です。JavaScriptでCSSを書きます(CSS-in-JS)。Reactのコンポーネント設計ととても相性がいいので採用する企業も多く、公式ホームページによれば、Google、GitHub、Spotify、tinder、airbnb、redditなどがStyled Components採用しています(2021年4月現在)。

Styled Componentsの特徴

大きな特徴は次の5点です。2番目がStyled Componentsの本質、3番目も大きな魅力です。

  1. 記述の仕方は従来のCSSと近い
    • CSS部分の書き方は外部スタイルシートを使う従来と同じなので、すぐに書けます。
  2. スコープを絞りやすい(クラス名の重複が発生しない)
    • CSSのスコープはコンポーネントのスコープと一致します(ローカルに限定されます)。固有のクラス名が自動で割り振られるので、グローバルにはクラス名の重複が発生しません。
  3. 高速
    • Reactの仮想DOMと同じ仕組みでレンダリングするので高速です。
  4. propsを使ってCSSを組める
    • Reactのコンポーネントでpropsを通じて、サブコンポーネントに変数を渡せるのと同様に、propsを通じてJSXからCSSに変数を渡せます。動的なCSSも格段に組みやすいです。
  5. 継承がしやすい
    • 1つの基本となるCSSのルールを継承した、複数の派生CSSの作成が容易です。

欠点は次の3点でしょうか。1は慣れます。2は同じシートには記載しやすい点、メリットとも言えます。

  1. HTML(JSX)が読みにくい
    • HTML(JSX)にタグ名がなく、コンポーネントが並ぶだけなので、JXSを見ただけでは構造がよくわかりません。Styled Componentsでタグを確認する必要があります。大きなコンポーネントになればなるほど読みにくくなります。
  2. 別のシートに記述する場合、個別にimportとexportを要する
    • 通常Styled ComponentsはJSXと同じシートに記述しますが、CSSと同様に別のシートに記述することもできます(Separation of Concern)。但しその場合は、個別のimportとexportを要することから、CSSと比較してコードがやや長くなります。
  3. DOMエレメントのターゲティングがややしにくい
    • コンポーネントの中にある特定のエレメントをターゲティングする際には、IDやクラス名を使わずに、慎重にターゲティングする必要があります。応用編1でご紹介します。


Styled Componentsのインストール

npmコマンドを使ってstyled-componentsライブラリーをインストールします。

npm install styled-components

インストールを終えたら、npx create-react-appコマンドで作成されるpackage.jsonファイルの最後の部分に下記を書き加えます。

{
“resolutions”: {
“styled-components”: “^5”
}
}

公式ドキュメントには上記の記載がありますが、最も外側を囲む波括弧は既にpackage.jsonにありますので不要です。下記は、該当部分を貼り付けた後のpackage.jsonです。”resolutions”の上にカンマが必要です。



Styled Components専用の拡張機能のインストール(VSCode)

Styled Componentsは、Prettierでは表示が見にくいので、専用の拡張機能をインストールします。Web開発の一般的な拡張機能については以前の私のこちらの記事をごらんください。

VSCodeの拡張機能の検索ボックスで、「vscode-styled-components」と検索します。下記の左側にあるようによく似た名前の機能が幾つか出てきますが、Julien Poissonnierさんのものを選んで、Installをしてください。



コード本体

コード本体は関数コンポーネントでご案内します。App.jsからMain.jsコンポーネントを呼び出し、Main.jsでStyled Componentsを使用します。本来はApp.jsにもStyled ComponentsによるCSSが必要ですが、ここでは簡便的に省略します。App.jsは次の通りシンプルです。



App.js

import React from “react”;
import Main from “./Main”;

function App() {
return (
< div>
<Main />
</ div>
);
}

export default App;


続いてMain.jsです。2行で先ほどインストールしたstyled-componentsをインポートしています。Main関数の中にある<MainContainer>というタグがstyled-componentsです。styled-componentsは、Reactのコンポーネントを応用していますが、セルクロージングタグだけでなく、このように開始タグと終了タグを持たせることもできます。このタグの内側でCSSが適用されます(特徴の2番目)。今回は<h1>タグを置いています。

export 文の後ろで、JavaScriptで<MainContainer>を定義しています。=の左側は解説不要だと思います。右側でインポートしたstyledを呼び出し、.divによってこのコンポーネントをdivタグとしています。.divの部分は任意のHTMLエレメントにすることができます。なお規定のHTMLにはないエレメントを使用する場合(例:Material UIのアイコンなど)は、styledの後ろはドットではなく、()として括弧内にエレメント名(例:const UiAvatar = styled(Avatar)` `)を入れます。

styled.divに続けてバックティックを打ち、バックティックの中にCSSとを記述します。通常のCSSと全く同じ書き方ができて(特徴の1番目)、ここでは色を赤く(palevioletred)しています。



Main.js

import React from “react”;
import styled from “styled-components”;

function Main() {
return (
< MainContainer>
<h1>I am the Main content<h1>
</ MainContainer>
);
}

export default Main;

const MainContainer = styled.div`
color: palevioletred;
`;



レンダリングすると、下記左側のようになり、color : redが反映されています。右側はソースコードです。<MainContainer>としていた部分がレンダリングされると<div>タグに置き換わっていることがご覧頂けると思います。また、上記のMain.jsでは記述しなかったクラス名”sc-dlfnbm lbuUeU”が割り当てられています。このクラス名はこのコンポーネント固有なので、Webサイト(アプリ)内で他と重複することはありません(特徴の2番目)。





応用編1 – ターゲティング

通常のCSSでも全てのタグにクラス名(又はid)を付けないのと同様に、styled-componentsでも下記のようにコンポーネントの内部にクラス名を持たないHTMLエレメントが並べることができます。内部エレメントのターゲティングは従来のCSSと同様です。先ほどのMain.jsの<h1>の下に<h2>があり、styled-componentsで<h2>だけをターゲットにCSSを適用する場合、下記のように「>」を使った従来のCSSと同じターゲティングができます。


Main.js

import React from “react”;
import styled from “styled-components”;

function Main() {
return (
< MainContainer>
<h1>I am the Main content<h1>
<h2>I am inside the h2<h2>
</ MainContainer>
);
}

export default Main;

const MainContainer = styled.div`
color: palevioletred;

> h2 {
color: mediumseagreen;
}
`;

コンポーネントの内部に同一のタグが複数あり、いずれかだけをターゲットとしたい場合はこの方法ではうまくいきませんので、別途固有のコンポーネントで包むか、CSSでn番目の子要素を特定する:nth-child(n)を使います(後者は従来のCSSと同じ)。下記の例では<h2>タグが2つあり、2番目の<h2>(子要素の並びでは3番目)だけをターゲティングして青くしています。:nth-child(3)を付けないとコンポーネント内の全ての<h2>にcolor: mediumseagreenが適用されます。


Main.js

import React from “react”;
import styled from “styled-components”;

function Main() {
return (
< MainContainer>
<h1>I am the Main content<h1>
<h2>I am inside the h2<h2>
<h2>I am the third element, h2<h1>
</ MainContainer>
);
}

export default Main;

const MainContainer = styled.div`
color: palevioletred;

> h2 {
color: mediumseagreen;
}

> h2:nth-child(3) {
color: royalblue;
}
`;


仕上がりは次の通りです。

応用編2 – propsを使ったCSS

先ほどのMain.jsを書き換え、ボタンを3つ中央に配置しています。Bootstrapのように、(1)primary、(2)secondary、(3)指定なしの属性を持つボタンを設け、それぞれでボタンに係るCSSの大部分を共有しつつも、色だけを変えたいと思います。

<MainContainer>のCSSではmargin-topとdisplay-flexを適用しています。


Main.js

import React from “react”;
import styled from “styled-components”;

function Main() {
return (
< MainContainer>
<Button primary>I am a primary button</Button>
<Button secondary>I am a secondary button</Button>
<Button>I am a normal button</Button>
</ MainContainer>
);
}

export default Main;

const MainContainer = styled.div`
margin-top: 15%;
display: flex;
flex-direction: column;
align-items: center;
`;

const Button = styled.button`
color: white;
border: none;
border-radius: 5px;
width: 150px;
height: 40px;
margin: 10px;

background-color: ${(props) => props.primary
? “palevioletred”
: props.secondary
? “royalblue”
: “mediumseagreen”};
`;

<Button>のCSSは2つの部分に分かれています。前段のcolor: whiteから、margin: 10pxまでは3つのボタンに共通のCSSです。後段で各ボタンに固有の背景色(background-color)を付けます。バックティックの内部ですのでドルサインと波括弧で囲ってJavaScriptでロジックを記述します。ここではpropsを引数として、無名・即時実行のアロー関数を配置しています。

なお、この<Button>は後述のMaterial-UIのCSSの上書きで紹介するMaterial-UIの<Button>とは異なり、ここで独自に宣言した<Button>です。

アロー関数では、if文を使い、propsの値によって適用させる背景色を変えています。primaryを受ければ、背景色は“palevioletred”、secondaryを受ければ“royalblue”、propsがなければ“mediumseagreen”としています。

以上実行すると、下記のような色以外は共通の3つのボタンが表示されます。

応用編3 – CSSの継承

応用編2で作成した共通の性質(CSS)を持つ3つボタンは、Styled Componentsの継承の機能を使うことでも実現できます。応用編2のコードを下記のように書き換えます。

3つ目のボタンを基本とし、1つ目と2つ目のボタンにその性質に継承させます。JSXの部分では3つ目のボタンのコンポーネント名をButtonとし、1つ目と2つ目のボタンのコンポーネント名は、それぞれButtonPrimary、ButtonSecondaryとしています。


Main.js

import React from “react”;
import styled from “styled-components”;

function Main() {
return (
< MainContainer>
<ButtonPrimary>I am a primary button</Button>
<ButtonSecondary</Button>
<Button>I am a normal button</Button>
</ MainContainer>
);
}

export default Main;

const MainContainer = styled.div`
`;

const Button = styled.button`
color: white;
border: none;
border-radius: 5px;
width: 150px;
height: 40px;
margin: 10px;
background-color: mediumseagreen;
`;

const ButtonPrimary = styled(Button)`
background-color: palevioletred;
`;

const ButtonSecondary = styled(Button)`
background-color: royalblue;
`;

Styled Componentsの部分をご覧ください。基本となるButtonコンポーネントに、全てのボタンに共通のCSSを持たせた上、3つ目のボタンに独自の背景色、mediumseagreenを指定しています。背景色だけが継承後に上書きされます。なお、MainContainerは先ほどと同じですのでCSSの記載を省略しています。

ButtonPrimaryを定義した部分の右側ではstyledに括弧で引数を持たせ、括弧の後ろにバックティックを打ちます。括弧の中で継承元のButtonを指定します。バックティックの中では、基本となるButtton内のCSSは全て継承されますので、ButtonPrimaryに固有のCSSだけを記述します。ここではButtonの背景色を上書きしています。ButtonSecondaryも同様です。

別の方法としては、JSX内には存在しないBasicButtonコンポーネントをStyled Componentsで作った上で、NormalButton、PrimaryButton、SecondaryButtonにBasicButtonのCSSを継承させることもできます。コードは長くなりますが、この場合は上書きが発生しません。

Material-UIへのCSS追加

この継承機能を使うと、CSSの内蔵されたアイコン(例えばMaterial-UI)にCSSを追加することも可能です。下記の例は、Material-UIのアバターアイコンにカーソルを合わせたときのCSSを追加しています。


Main.js

import React from “react”;
import styled from “styled-components”;
import { Avatar } from “@material-ui/core”;

function Main() {
return (
< MainContainer>
<UserAvatar />
</ MainContainer>
);
}

export default Main;


const UserAvatar = styled(Avatar)`
cursor: pointer;

:hover {
opacity: 0.8;
}
`;


JSXでは、Material-UIの<Avatar />コンポーネントではなく、<UseAvatar />コンポーネントを設置し、Styled ComponentsでAvatarを継承する形でこのUseAvatarを定義しています。

Material-UIのCSS上書き

Material-UIのCSSは、規定の内容と矛盾するカスタムのCSSに対して優先適用されるので、CSSの追記ではなく上書きをしたい場合は上記の方法ではうまくいきません。上書きをしたい場合は、アンド”&”のマークを3つ繋げて上書きします。この例では下記既定のCSSではボーダーを持っていない<Button>コンポーネントに、lightgrayのボーダーを持たせたいと思います。

前述の通り、この例で使っているButtonコンポーネントはMaterial-UIの<Button><Button/>コンポーネントですので、3行目でインポートをしています。

widthの部分は上書きではないので&&&の外側に、borderの部分は上書きなので&&&の中に記述します。


Main.js

import React from “react”;
import styled from “styled-components”;
import { Button } from “@material-ui/core”;

function Main() {
return (
< MainContainer>
<CustomButton>Custom Button<CustomButton />
</ MainContainer>
);
}

export default Main;


const CustomButton = styled(Button)`
width: 170px;

&&& {
border: 1px solid lightgray;
}
`;


ボタンの仕上がりは次のようになります。先ほどと異なり、ボタンの周りにボーダーがあります。

今日はStyled Componentsをご紹介しました。最後まで読んで頂きありがとうございました。