ReactでStyled-Componentsを使用する方法
公開: 2022-03-10スタイリングされたコンポーネントは、コンポーネントとスタイリングの間のギャップを埋めるCSS-in-JSツールであり、機能的で再利用可能な方法でスタイリングコンポーネントを起動して実行するための多数の機能を提供します。 この記事では、スタイル設定されたコンポーネントの基本と、それらをReactアプリケーションに適切に適用する方法を学習します。 このチュートリアルを実行する前に、以前にReactに取り組んでいたはずです。 Reactコンポーネントのスタイリングでさまざまなオプションを探している場合は、このテーマに関する以前の投稿を確認してください。
CSSの中核となるのは、DOMツリー内の位置に関係なく、任意のHTML要素をグローバルにターゲットにする機能です。 コンポーネントを使用する場合、これは障害になる可能性があります。これは、コンポーネントが、使用場所に近いコロケーション(つまり、状態やスタイルなどのアセットの保持)を合理的な範囲で要求するためです(ローカリゼーションと呼ばれます)。
React自身の言葉では、スタイル設定されたコンポーネントは「コンポーネントの視覚的なプリミティブ」であり、その目標は、コンポーネントをスタイル設定するための柔軟な方法を提供することです。 その結果、コンポーネントとそのスタイルが緊密に結合されます。
注:スタイル付きコンポーネントは、ReactとReact Nativeの両方で使用できます。必ず、React Nativeガイドを確認する必要がありますが、ここでは、Reactのスタイル付きコンポーネントに焦点を当てます。
なぜスタイル付きコンポーネントなのか?
スタイルのスコープ設定を支援するほかに、スタイル付きコンポーネントには次の機能が含まれています。
- 自動ベンダープレフィックス
標準のCSSプロパティを使用でき、スタイル付きコンポーネントは必要に応じてベンダープレフィックスを追加します。 - 一意のクラス名
スタイル付きコンポーネントは互いに独立しており、ライブラリが自動的に処理するため、名前について心配する必要はありません。 - デッドスタイルの排除
スタイル付きコンポーネントは、コードで宣言されている場合でも、未使用のスタイルを削除します。 - などなど。
インストール
スタイル付きコンポーネントのインストールは簡単です。 CDNを介して、またはYarnなどのパッケージマネージャーを使用して実行できます…
yarn add styled-components
…またはnpm:
npm i styled-components
私たちのデモはcreate-react-appを使用しています。
はじめに
おそらく、スタイル付きコンポーネントについて最初に気付くのはその構文です。これは、スタイル付きコンポーネントの背後にある魔法を理解していない場合、気が遠くなる可能性があります。 簡単に言うと、スタイル付きコンポーネントはJavaScriptのテンプレートリテラルを使用して、コンポーネントとスタイルの間のギャップを埋めます。 したがって、スタイル付きコンポーネントを作成する場合、実際に作成しているのは、スタイルを備えたReactコンポーネントです。 次のようになります。
import styled from "styled-components"; // Styled component named StyledButton const StyledButton = styled.button` background-color: black; font-size: 32px; color: white; `; function Component() { // Use it like any other component. return <StyledButton> Login </StyledButton>; }
ここで、 StyledButton
はスタイル付きコンポーネントであり、含まれているスタイルを含むHTMLボタンとしてレンダリングされます。 styled
は、JavaScriptから実際のCSSにスタイリングを変換する内部ユーティリティメソッドです。
生のHTMLとCSSでは、次のようになります。
button { background-color: black; font-size: 32px; color: white; } <button> Login </button>
スタイル付きコンポーネントがReactコンポーネントの場合、小道具を使用できますか? はい、できます。
小道具に基づく適応
スタイル設定されたコンポーネントは機能的であるため、要素を動的に簡単にスタイル設定できます。 ページに2種類のボタンがあり、1つは背景が黒で、もう1つは青であると仮定します。 それらのために2つのスタイル付きコンポーネントを作成する必要はありません。 私たちは彼らの小道具に基づいて彼らのスタイリングを適応させることができます。
import styled from "styled-components"; const StyledButton = styled.button` min-width: 200px; border: none; font-size: 18px; padding: 7px 10px; /* The resulting background color will be based on the bg props. */ background-color: ${props => props.bg === "black" ? "black" : "blue"; `; function Profile() { return ( <div> <StyledButton bg="black">Button A</StyledButton> <StyledButton bg="blue">Button B</StyledButton> </div> ) }
StyledButton
は小道具を受け入れるReactコンポーネントであるため、 bg
小道具の存在または値に基づいて異なる背景色を割り当てることができます。
ただし、ボタンにtype
が指定されていないことに気付くでしょう。 それをしましょう:
function Profile() { return ( <> <StyledButton bg="black" type="button"> Button A </StyledButton> <StyledButton bg="blue" type="submit" onClick={() => alert("clicked")}> Button B </StyledButton> </> ); }
スタイル付きコンポーネントは、受け取る小道具のタイプを区別できます。 type
がHTML属性であることを知っているので、 bg
プロップを独自の処理で使用しながら、実際に<button type="button">Button A</button>
をレンダリングします。 イベントハンドラーをどのようにアタッチしたかにも注目してください。
属性について言えば、拡張構文を使用すると、 attrs
コンストラクターを使用して小道具を管理できます。 これをチェックしてください:
const StyledContainer = styled.section.attrs((props) => ({ width: props.width || "100%", hasPadding: props.hasPadding || false, }))` --container-padding: 20px; width: ${(props) => props.width}; // Falls back to 100% padding: ${(props) => (props.hasPadding && "var(--container-padding)") || "none"}; `;
幅を設定するときに3値が必要ないことに注意してください。 これは、 width: props.width || "100%",
width: props.width || "100%",
また、できるのでCSSカスタムプロパティを使用しました。
注:スタイル設定されたコンポーネントがReactコンポーネントであり、小道具を渡すことができる場合、状態も使用できますか? ライブラリのGitHubアカウントには、この問題に対処するための問題があります。
スタイルの拡張
ランディングページで作業していて、コンテナを特定の最大幅に設定して、中央に配置したとします。 そのためのStyledContainer
があります。
const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `;
次に、20ピクセルではなく、両側に10ピクセルのパディングを備えた、より小さなコンテナが必要であることがわかります。 最初に考えたのは、別のスタイルのコンポーネントを作成することでした。その通りですが、スタイルを複製していることに気付くまで、それほど時間はかかりません。
const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `; const StyledSmallContainer = styled.section` max-width: 1024px; padding: 0 10px; margin: 0 auto; `;
上記のスニペットのように、 StyledSmallContainer
を作成する前に、スタイルを再利用して継承する方法を学びましょう。 これは、 spread
演算子がどのように機能するかに多かれ少なかれ似ています。
const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `; // Inherit StyledContainer in StyledSmallConatiner const StyledSmallContainer = styled(StyledContainer)` padding: 0 10px; `; function Home() { return ( <StyledContainer> <h1>The secret is to be happy</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer> <h1>The road goes on and on</h1> </StyledSmallContainer> ); }
StyledSmallContainer
では、 StyledContainer
からすべてのスタイルを取得しますが、パディングはオーバーライドされます。 通常、 StyledContainer
がレンダリングするのは、 StyledSmallContainer
用にレンダリングされたセクション要素であることに注意してください。 しかし、それはそれが石に刻まれている、または変更できないという意味ではありません。
「as」多型プロップ
as
polymorphic propを使用すると、レンダリングされる終了要素を交換できます。 1つのユースケースは、スタイルを継承する場合です(最後の例のように)。 たとえば、 StyledSmallContainer
のsection
よりもdiv
を使用したい場合は、次のように、好みの要素の値を使用しas
aspropをスタイル付きコンポーネントに渡すことができます。
function Home() { return ( <StyledContainer> <h1>It's business, not personal</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer as="div"> <h1>Never dribble when you can pass</h1> </StyledSmallContainer> ); }
これで、 StyledSmallContainer
がdiv
としてレンダリングされます。 値としてカスタムコンポーネントを使用することもできます。
function Home() { return ( <StyledContainer> <h1>It's business, not personal</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer as={StyledContainer}> <h1>Never dribble when you can pass</h1> </StyledSmallContainer> ); }
当たり前のことと思ってはいけません。
SCSSのような構文
CSSプリプロセッサStylisを使用すると、スタイル設定されたコンポーネントで、ネストなどのSCSSのような構文をサポートできます。
const StyledProfileCard = styled.div` border: 1px solid black; > .username { font-size: 20px; color: black; transition: 0.2s; &:hover { color: red; } + .dob { color: grey; } } `; function ProfileCard() { return ( <StyledProfileCard> <h1 className="username">John Doe</h1> <p className="dob"> Date: <span>12th October, 2013</span> </p> <p className="gender">Male</p> </StyledProfileCard> ); }
アニメーション
スタイル付きコンポーネントには、(再利用可能な)アニメーションキーフレームの構築を支援するkeyframes
ヘルパーがあります。 ここでの利点は、キーフレームがスタイル設定されたコンポーネントから切り離され、必要な場所にエクスポートして再利用できることです。
import styled, {keyframes} from "styled-components"; const slideIn = keyframes` from { opacity: 0; } to { opacity: 1; } `; const Toast = styled.div` animation: ${slideIn} 0.5s cubic-bezier(0.4, 0, 0.2, 1) both; border-radius: 5px; padding: 20px; position: fixed; `;
グローバルスタイリング
CSS-in-JSの当初の目標、ひいてはスタイル付きコンポーネントはスタイルのスコープですが、スタイル付きコンポーネントのグローバルなスタイルを活用することもできます。 私たちは主にスコープスタイルを扱っているので、それは不変の工場設定だと思うかもしれませんが、あなたは間違っているでしょう。 考えてみてください:実際にスコーピングとは何ですか? グローバルスタイリングの名の下に、私たちが次のようなことを行うことは技術的に可能です。
ReactDOM.render( <StyledApp> <App /> </StyledApp>, document.getElementById("root") );
しかし、すでにヘルパー関数であるcreateGlobalStyle
があり、その唯一の存在理由はグローバルスタイルです。 それで、なぜそれがその責任を否定するのですか?
createGlobalStyle
を使用できることの1つは、CSSを正規化することです。
import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ `; // Use your GlobalStyle function App() { return ( <div> <GlobalStyle /> <Routes /> </div> ); }
注: createGlobalStyle
で作成されたスタイルは、子を受け入れません。 詳細については、ドキュメントをご覧ください。
この時点で、なぜcreateGlobalStlye
を使用する必要があるのか疑問に思われるかもしれません。 ここにいくつかの理由があります:
- ルートレンダリングの外側にあるもの(たとえば、
html
、body
など)をターゲットにすることはできません。 -
createGlobalStyle
はスタイルを挿入しますが、実際の要素はレンダリングしません。 最後の例をよく見ると、レンダリングするHTML要素が指定されていないことがわかります。 実際には要素が必要ないかもしれないので、これはクールです。 結局のところ、私たちはグローバルスタイルに関心を持っています。 特定の要素ではなく、セレクター全体を対象としています。 -
createGlobalStyle
はスコープがなく、アプリのどこにでもレンダリングでき、DOMにある限り適用できます。 構造ではなく、概念について考えてください。
import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ .app-title { font-size: 40px; } `; const StyledNav = styled.nav` /* Your styles here */ `; function Nav({children}) { return ( <StyledNav> <GlobalStyle /> {children} </StyledNav> ); } function App() { return ( <div> <Nav> <h1 className="app-title">STYLED COMPONENTS</h1> </Nav> <Main /> <Footer /> </div> ); }
構造について考える場合、 app-title
はGlobalStyle
で設定されたスタイルに設定しないでください。 しかし、それはそのようには機能しません。 GlobalStyle
をレンダリングすることを選択した場合は常に、コンポーネントがレンダリングされるときに注入されます。
注意: createGlobalStyles
は、DOMにある場合にのみレンダリングされます。
CSSヘルパー
小道具に基づいてスタイルを適応させる方法はすでに見てきました。 もう少し先に進みたい場合はどうなりますか? CSSヘルパー関数はこれを実現するのに役立ちます。 空とアクティブの2つのテキスト入力フィールドがあり、それぞれが異なる色であると仮定します。 できるよ:
const StyledTextField = styled.input` color: ${(props) => (props.isEmpty ? "none" : "black")}; `;
すべて順調です。 その後、別の塗りつぶし状態を追加する必要がある場合は、スタイルを変更する必要があります。
const StyledTextField = styled.input` color: ${(props) => props.isEmpty ? "none" : props.active ? "purple" : "blue"}; `;
現在、三項演算は複雑さを増しています。 後でテキスト入力フィールドに別の状態を追加するとどうなりますか? または、各州に色以外の追加のスタイルを与えたい場合はどうなりますか? 三項演算にスタイルを詰め込むことを想像できますか? css
ヘルパーが便利です。
const StyledTextField = styled.input` width: 100%; height: 40px; ${(props) => (props.empty && css` color: none; backgroundcolor: white; `) || (props.active && css` color: black; backgroundcolor: whitesmoke; `)} `;
私たちが行ったことは、より多くのスタイルに対応するために、より理解しやすく整理された構文を使用して、三項構文を拡張したものです。 前のステートメントが間違っているように思われる場合は、コードが多くのことを実行しようとしていることが原因です。 それでは、一歩下がって洗練してみましょう。
const StyledTextField = styled.input` width: 100%; height: 40px; // 1. Empty state ${(props) => props.empty && css` color: none; backgroundcolor: white; `} // 2. Active state ${(props) => props.active && css` color: black; backgroundcolor: whitesmoke; `} // 3. Filled state ${(props) => props.filled && css` color: black; backgroundcolor: white; border: 1px solid green; `} `;
私たちの改良により、スタイリングは3つの異なる管理しやすく理解しやすいチャンクに分割されます。 それは勝利です。
StyleSheetManager
CSSヘルパーと同様に、 StyleSheetManager
は、スタイルの処理方法を変更するためのヘルパーメソッドです。 サブツリーからベンダープレフィックスをオプトアウトするのに役立つ、 disableVendorPrefixes
(完全なリストを確認できます)などの特定の小道具が必要です。
import styled, {StyleSheetManager} from "styled-components"; const StyledCard = styled.div` width: 200px; backgroundcolor: white; `; const StyledNav = styled.div` width: calc(100% - var(--side-nav-width)); `; function Profile() { return ( <div> <StyledNav /> <StyleSheetManager disableVendorPrefixes> <StyledCard> This is a card </StyledCard> </StyleSheetManager> </div> ); }
disableVendorPrefixes
は、小道具として<StyleSheetManager>
に渡されます。 したがって、 <StyleSheetManager>
でラップされたスタイル付きコンポーネントは無効になりますが、 <StyledNav>
のコンポーネントは無効になりません。
より簡単なデバッグ
スタイリングされたコンポーネントを同僚の1人に紹介したとき、彼らの不満の1つは、レンダリングされた要素をDOMまたはReact DeveloperToolsで見つけるのが難しいということでした。 これは、スタイル付きコンポーネントの欠点の1つです。一意のクラス名を提供しようとすると、要素に一意のハッシュが割り当てられます。これは、たまたま不可解ですが、 displayName
を読み取り可能にして、デバッグを容易にします。
import React from "react"; import styled from "styled-components"; import "./App.css"; const LoginButton = styled.button` background-color: white; color: black; border: 1px solid red; `; function App() { return ( <div className="App"> <LoginButton>Login</LoginButton> </div> ); }
デフォルトでは、スタイル付きコンポーネントは、 LoginButton
をDOMでは<button class="LoginButton-xxxx xxxx">Login</button>
としてレンダリングし、React DeveloperToolsではLoginButton
としてレンダリングします。これによりデバッグが容易になります。 この動作が必要ない場合は、 displayName
ブール値を切り替えることができます。 これには、Babel構成が必要です。
注:ドキュメントでは、パッケージbabel-plugin-styled-components
と.babelrc
構成ファイルが指定されています。 これに関する問題は、 create-react-app
を使用しているため、イジェクトしない限り多くの設定を行うことができないということです。 これがバベルマクロの出番です。
npmまたはYarnを使用してbabel-plugin-macros
をインストールしてから、アプリケーションのルートに次のコンテンツを含むbabel-plugin-macros.config.js
を作成する必要があります。
module.exports = { styledComponents: { displayName: true, fileName: false, }, };
fileName
の値を逆にすると、 displayName
の前にファイル名が付けられ、さらに一意の精度が得られます。
また、 macro
からインポートする必要があります。
// Before import styled from "styled-components"; // After import styled from "styled-components/macro";
結論
プログラムでCSSを作成できるようになったので、自由を乱用しないでください。 その価値については、スタイル設定されたコンポーネントの健全性を維持するために最善を尽くしてください。 重い条件文を作成しようとしないでください。また、すべてのものをスタイル付きコンポーネントにする必要があると思い込まないでください。 また、角を曲がったところにあると推測しているだけのユースケース用に、初期のスタイルのコンポーネントを作成して過度に抽象化しないでください。
その他のリソース
- ドキュメント、スタイル付きコンポーネント
- 「React.jsとstyled-componentsを使用した再利用可能なコンポーネントシステムの構築」、Lukas Gisder-Dube
- Next.jsでの使用
- Gatsbyでの使用