styled-componentsは、<ThemeProvider>
ラッパーコンポーネントをエクスポートすることで、テーマ設定を完全にサポートしています。このコンポーネントは、コンテキストAPIを介して、その下にあるすべてのReactコンポーネントにテーマを提供します。レンダーツリーでは、すべてのstyled-componentsは、複数レベルの深さであっても、提供されたテーマにアクセスできます。
これを説明するために、Buttonコンポーネントを作成してみましょう。ただし、今回はいくつかの変数をテーマとして渡します。
theme propに関数を渡すこともできます。この関数は、ツリーの上位にある別の<ThemeProvider>
からの親テーマを受け取ります。このようにして、テーマ自体をコンテキスト化できます。
この例では、上記のテーマ付きButtonと、2番目のThemeProviderを使用して背景色と前景色を反転した2番目のButtonをレンダリングします。関数invertTheme
は上位テーマを受け取り、新しいテーマを作成します。
withTheme
経由...styledコンポーネントの外部(たとえば、大きなコンポーネント内)で現在のテーマを使用する必要がある場合は、高階コンポーネントwithTheme
を使用できます。
import { withTheme } from 'styled-components' class MyComponent extends React.Component { render() { console.log('Current theme: ', this.props.theme) // ... } } export default withTheme(MyComponent)
useContext
経由React Hooksで作業している場合は、useContext
を使用して、styledコンポーネントの外部から現在のテーマにアクセスすることもできます。
import { useContext } from 'react' import { ThemeContext } from 'styled-components' const MyComponent = () => { const themeContext = useContext(ThemeContext) console.log('Current theme: ', themeContext) // ... }
useTheme
経由React Hooksで作業している場合は、useTheme
を使用して、styledコンポーネントの外部から現在のテーマにアクセスすることもできます。
import { useTheme } from 'styled-components' const MyComponent = () => { const theme = useTheme() console.log('Current theme: ', theme) // ... }
theme
prop...theme
propを使用して、コンポーネントにテーマを渡すこともできます。
これは、欠落している<ThemeProvider>
を回避したり、オーバーライドしたりするのに役立ちます。
styledコンポーネントにref
propを渡すと、styledターゲットに応じて2つのいずれかが得られます。
styled.div
をターゲットにしている場合)React.Component
から拡張されたカスタムコンポーネントをターゲットにしている場合)古いバージョンのstyled-components(4.0.0未満)またはReactを使用していますか?代わりにinnerRef
propを使用してください。
styled-componentsでは、任意の入力を補間として使用できるため、その入力をサニタイズするように注意する必要があります。ユーザー入力をスタイルとして使用すると、攻撃者がアプリケーションに配置できるCSSがユーザーのブラウザで評価される可能性があります。
この例は、悪意のあるユーザー入力がAPIエンドポイントをユーザーの代わりに呼び出すことにつながる可能性があることを示しています。
// Oh no! The user has given us a bad URL! const userInput = '/api/withdraw-funds' const ArbitraryComponent = styled.div` background: url(${userInput}); /* More styles here... */ `
非常に注意してください!これは明らかにでっち上げの例ですが、CSSインジェクションは目立たず、悪影響を及ぼす可能性があります。一部のIEバージョンでは、url宣言内で任意のJavaScriptが実行されることさえあります。
JavaScriptからCSSをサニタイズするための新しい標準、CSS.escape
が策定されています。まだブラウザ全体で十分にサポートされていないため、アプリでMathias Bynensによるpolyfillを使用することをお勧めします。
styled-componentsを既存のCSSと一緒に使用する場合は、注意すべき実装の詳細がいくつかあります。
styled-componentsは、クラスを持つ実際のスタイルシートを生成し、className
propを介してそれらのクラスをstyledコンポーネントのDOMノードに添付します。実行時にドキュメントのheadの最後に生成されたスタイルシートを挿入します。
styled(MyComponent)
表記を使用し、MyComponent
が渡されたclassName
propをレンダリングしない場合、スタイルは適用されません。この問題を回避するには、コンポーネントが渡されたclassName
をDOMノードに添付していることを確認してください。
class MyComponent extends React.Component { render() { // Attach the passed-in className to the DOM node return <div className={this.props.className} /> } }
クラスを持つ既存のスタイルがある場合は、グローバルクラスと渡されたクラスを組み合わせることができます。
class MyComponent extends React.Component { render() { // Attach the passed-in className to the DOM node return <div className={`some-global-class ${this.props.className}`} /> } }
グローバルクラスとstyledコンポーネントクラスを一緒に適用すると、予期しない結果になる場合があります。同じ特異性を持つ両方のクラスでプロパティが定義されている場合、最後のものが優先されます。
// MyComponent.js const MyComponent = styled.div`background-color: green;`; // my-component.css .red-bg { background-color: red; } // For some reason this component still has a green background, // even though you're trying to override it with the "red-bg" class! <MyComponent className="red-bg" />
上記の例では、styled-componentsはデフォルトで実行時に<head>
の最後にスタイルを挿入するため、styledコンポーネントクラスはグローバルクラスよりも優先されます。したがって、そのスタイルは他の単一のクラス名セレクターよりも優先されます。
1つの解決策は、スタイルシートのセレクターの特異性を高めることです。
/* my-component.css */ .red-bg.red-bg { background-color: red; }
完全に制御できないページにstyled-componentsをデプロイする場合は、コンポーネントのスタイルがホストページのスタイルと競合しないように注意する必要があります。
最も一般的な問題は、特異性が不十分なことです。たとえば、このスタイルルールを持つホストページを考えてみましょう。
body.my-body button { padding: 24px; }
ルールにはクラス名と2つのタグ名が含まれているため、このstyledコンポーネントによって生成される単一のクラス名セレクターよりも特異性が高くなります。
styled.button` padding: 16px; `
コンポーネントをホストページのスタイルから完全に隔離する方法はありませんが、babel-plugin-styled-components-css-namespace を使用することで、スタイルルールの特異性を少なくとも高めることができます。このプラグインを使用すると、すべてのスタイル付きコンポーネントに CSS 名前空間を指定できます。すべてのスタイル付きコンポーネントが id="my-widget"
を持つコンテナ内でレンダリングされる場合、良い名前空間は #my-widget
のようなものになります。ID セレクタは、クラス名よりも特異性が高いためです。
より稀な問題は、ページ上の styled-components の 2 つのインスタンス間の競合です。これは、styled-components インスタンスを含むコードバンドルで process.env.SC_ATTR
を定義することで回避できます。この値は、デフォルトの <style>
タグ属性 data-styled
(v3 以前は data-styled-components
)をオーバーライドし、各 styled-components インスタンスが独自のタグを認識できるようにします。
タグ付きテンプレートリテラルは ES6 の新機能です。カスタム文字列補間ルールを定義できます。これが、スタイル付きコンポーネントを作成できる方法です。
補間を渡さない場合、関数が受け取る最初の引数は、文字列を含む配列です。
// These are equivalent: fn`some string here`; fn(['some string here']);
補間を渡すと、配列には渡された文字列が補間の位置で分割されて含まれます。残りの引数は、順番に補間になります。
const aVar = 'good'; // These are equivalent: fn`this is a ${aVar} day`; fn(['this is a ', ' day'], aVar);
これは少し扱いにくいですが、styled-components で変数、関数、または mixin(css
ヘルパー)を受け取り、それを純粋な CSS にフラット化できることを意味します。
ちなみに、フラット化中に、styled-components は undefined
、null
、false
、または空の文字列(""
)と評価される補間を無視します。つまり、短絡評価 を使用して CSS ルールを条件付きで追加できます。
const Title = styled.h1<{ $upsideDown?: boolean; }>` /* Text centering won't break if props.$upsideDown is falsy */ ${props => props.$upsideDown && 'transform: rotate(180deg);'} text-align: center; `;
タグ付きテンプレートリテラルの詳細については、Max Stoiber の記事をご覧ください:💅🏾 styled-components の魔法
styled-components は、スタイルシートの再水和により、同時サーバーサイドレンダリングをサポートしています。基本的な考え方は、サーバーでアプリをレンダリングするたびに、ServerStyleSheet
を作成し、コンテキスト API を介してスタイルを受け入れるプロバイダーを React ツリーに追加できるということです。
これは、keyframes
や createGlobalStyle
などのグローバルスタイルに干渉せず、React DOM のさまざまな SSR API で styled-components を使用できます。
サーバーサイドレンダリングを確実に実行し、クライアントサイドバンドルが問題なくピックアップするには、babel プラグイン を使用する必要があります。これは、各スタイル付きコンポーネントに決定論的な ID を追加することにより、チェックサムの不一致を防ぎます。詳細については、ツーリングドキュメント を参照してください。
TypeScript ユーザー向けに、TS の第一人者である Igor Oleinikov が、webpack ts-loader / awesome-typescript-loader ツールチェーン用の TypeScript プラグイン を作成しました。これは、同様のタスクを実行します。
可能であれば、babel プラグインは最も頻繁に更新されるため、babel プラグインを使用することをお勧めします。Babel を使用して TypeScript をコンパイルすることができるようになったため、エコシステムのメリットを得るために TS ローダーをオフにして純粋な Babel 実装に切り替える価値があるかもしれません。
基本的な API は次のとおりです
import { renderToString } from 'react-dom/server'; import { ServerStyleSheet } from 'styled-components'; const sheet = new ServerStyleSheet(); try { const html = renderToString(sheet.collectStyles(<YourApp />)); const styleTags = sheet.getStyleTags(); // or sheet.getStyleElement(); } catch (error) { // handle error console.error(error); } finally { sheet.seal(); }
collectStyles
メソッドは、要素をプロバイダーでラップします。必要に応じて、このメソッドの代わりに StyleSheetManager
プロバイダーを直接使用できます。クライアント側では使用しないでください。
import { renderToString } from 'react-dom/server'; import { ServerStyleSheet, StyleSheetManager } from 'styled-components'; const sheet = new ServerStyleSheet(); try { const html = renderToString( <StyleSheetManager sheet={sheet.instance}> <YourApp /> </StyleSheetManager> ); const styleTags = sheet.getStyleTags(); // or sheet.getStyleElement(); } catch (error) { // handle error console.error(error); } finally { sheet.seal(); }
sheet.getStyleTags()
は、複数の <style>
タグの文字列を返します。CSS 文字列を HTML 出力に追加する際には、これを考慮する必要があります。
または、ServerStyleSheet
インスタンスには、React 要素の配列を返す getStyleElement()
メソッドもあります。
何らかの理由でレンダリングが失敗した場合、try...catch...finally
を使用して、sheet
オブジェクトが常にガベージコレクションに使用できるようにすることをお勧めします。sheet.getStyleTags()
または sheet.getStyleElement()
が呼び出された後にのみ sheet.seal()
が呼び出されるようにしてください。そうでない場合、別のエラーがスローされます。
sheet.getStyleTags()
と sheet.getStyleElement()
は、要素がレンダリングされた後にのみ呼び出すことができます。その結果、sheet.getStyleElement()
からのコンポーネントを <YourApp />
と組み合わせてより大きなコンポーネントにすることはできません。
基本的には、カスタムの pages/_document.js
を追加する必要があります(ない場合)。次に、ロジックをコピーして、styled-components がサーバーサイドでレンダリングされたスタイルを <head>
に挿入します。
最新の使用方法の例については、Next.js リポジトリの 例 を参照してください。
Next.js は、バージョン 12 以降、SWC と呼ばれる Rust コンパイラを使用しています。babel プラグインを使用していない場合は、代わりに この例 を参照してください。
このバージョンでは、next.config.js
ファイルのコンパイラオプションに styledComponents: true,
を追加 し、SSR をサポートするために この例 のように _document
ファイルを getInitialProps
で変更するだけで済みます。
Next.js v13+ の app/
ディレクトリで定義されたルートの場合、Next.js のドキュメントで説明されているように、レイアウトファイルのいずれかに styled-components レジストリを配置する必要があります。これは styled-components v6+ に依存することに注意してください。また、'use client'
ディレクティブが使用されていることにも注意してください。そのため、ページはサーバーサイドでレンダリングされますが、styled-components は引き続きクライアントバンドルに表示されます。
Gatsby には、styled-components のサーバーサイドレンダリングを有効にする公式プラグインがあります。
セットアップと使用方法の手順については、プラグインのページ を参照してください。
styled-components は、ReactDOMServer.renderToNodeStream() で使用するためのストリーミング API を提供しています。ストリーミング実装には 2 つの部分があります
サーバー側
ReactDOMServer.renderToNodeStream
は、styled-components がラップする「読み取り可能」ストリームを発行します。HTML のチャンク全体がストリームにプッシュされると、対応するスタイルがレンダリング準備完了になると、スタイルブロックが React の HTML の前に追加され、クライアントブラウザに転送されます。
import { renderToNodeStream } from 'react-dom/server'; import styled, { ServerStyleSheet } from 'styled-components'; // if you're using express.js, you'd have access to the response object "res" // typically you'd want to write some preliminary HTML, since React doesn't handle this res.write('<html><head><title>Test</title></head><body>'); const Heading = styled.h1` color: red; `; const sheet = new ServerStyleSheet(); const jsx = sheet.collectStyles(<Heading>Hello SSR!</Heading>); const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx)); // you'd then pipe the stream into the response object until it's done stream.pipe(res, { end: false }); // and finalize the response with closing HTML stream.on('end', () => res.end('</body></html>'));
クライアント側
import { hydrate } from 'react-dom'; hydrate(); // your client-side react implementation
クライアント側の再水和が完了すると、styled-components は通常どおり引き継ぎ、再配置されたストリーミングの後にさらに動的なスタイルを挿入します。
これは **Web 特有** の API であり、react-native では **使用できません**。
コンポーネントのスタイリングにコンテキストオーバーライドを適用する方法はたくさんあります。とはいえ、よく知られているターゲティング CSS セレクタパラダイムを準備し、補間で使用できるようにすることなく、簡単に行えることはめったにありません。
styled-componentsは、「コンポーネントセレクタ」パターンを用いることで、このユースケースをきれいに解決します。コンポーネントがstyled()
ファクトリ関数によって作成またはラップされると、ターゲティングに使用するための安定したCSSクラスも割り当てられます。これにより、名前付けやセレクタの衝突を回避することなく、非常に強力なコンポジションパターンが可能になります。
実践的な例:ここでは、Iconコンポーネントは、親のLinkがホバーされたときの反応を定義しています。
色の変更ルールをLinkコンポーネント内にネストすることもできましたが、その場合は、Iconがそのように動作する理由を理解するために両方のルールセットを考慮する必要があります。
この動作は、Styled Componentsのコンテキスト内でのみサポートされています。次の例では、コンポーネントA
がStyled ComponentではなくReact.Component
のインスタンスであるため、B
をマウントしようとすると失敗します。
class A extends React.Component { render() { return <div /> } } const B = styled.div` ${A} { } `
スローされるエラー - Cannot call a class as a function
- は、styled componentがコンポーネントを補間関数として呼び出そうとしているために発生します。
ただし、A
をstyled()
ファクトリでラップすると、補間が可能になります。ラップされたコンポーネントがclassName
を渡すようにしてください。
class A extends React.Component { render() { return <div className={this.props.className} /> } } const StyledA = styled(A)`` const B = styled.div` ${StyledA} { } `
styled-componentsは、文字列の代わりにJavaScriptオブジェクトとしてCSSを記述することをオプションでサポートしています。これは、既存のスタイルオブジェクトがあり、styled-componentsに段階的に移行する場合に特に便利です。