要素を作る

styled

styled-componentsからstyled(default)をインポートして使います。styled[htmlタグ名]にはそれぞれタグ付けテンプレート構文になっているのでここに CSS を書いていくだけでスタイル付けした要素を作ることができます。実際には

  1. 定義した CSS にstyled-componentsが適当なクラス名を振る
  2. それを実際に使っている要素のclassNameに指定する

といった感じのことをやっています。

以下はスタイル付けdiv要素を作っている例です。

import styled from 'styled-components';

const StyledDiv = styled.div`
  padding: 1em;
`;

<StyledDiv>foo</StyledDiv>

作られた要素はそのまま React コンポーネントとして使うことができます。

動的スタイル・動的属性

普通の React コンポーネントがpropsを渡せるようにstyled-componentsで作った要素にもpropsを扱える仕組みがあります。それにはタグ付けテンプレート構文の中に関数を埋め込みます。関数を埋め込むとそれにはpropsが渡ってくるのでそれを好きなように触って目的の値を返すようにします。

const MARGIN = '1em';

const StyledDiv = styled.div`
  padding: ${props => props.padding || 0};
  margin: ${MARGIN};
`;

<StyledDiv padding="1em">foo</StyledDiv>
// padding: 1em;
// margin: 1em;

ちなみに埋め込みには関数以外を指定するとそのまま出力されます。

属性の場合は、attrsというメソッドがあるのでこれでpropsを受け取るコールバックを定義します。以下はtitle属性値を少し編集してる例ですが、このように好きな属性値のオブジェクトを返すようにするといいです。

const StyledDiv = styled.div.attrs(props => {
  return {
    title: `edited ${props.title}`
  }
})`
  // ...
`;

共通スタイル

styled-componentsが提供しているcssヘルパーを使うことで共通部分を分けることができます。特にpropsを扱いたいときの共通部分の切り出しには必須です。

import styled, {css} from 'styled-components';

const common = css`
  padding: ${props => props.padding || 0};
`;

const StyledFooDiv = styled.div`
  ${common};
  margin: 1em;
`;

const StyledBarDiv = styled.div`
  ${common};
  margin: 2em;
`;

テーマ

親階層でThemeProviderコンポーネントを使うことでそれ以下のすべてのstyled要素のprops.themeに値を渡せます。全体を通して一貫した動的な値を渡すことができます。

import styled, {ThemeProvider} from 'styled-components';

const StyledDiv = styled.div`
  padding: ${props => props.theme.padding || 0};
`;

const App = () => (
  <StyledDiv />
);

const Root = () => (
  <ThemeProvider theme={{padding: '1em}}>
    <App />
  </ThemeProvider>
);

<Root />

ある要素を拡張した要素を作る

styledに対して既存の Styled なコンポーネントを渡すと、その要素のスタイルに加えて当たらなスタイルを当てた新しい要素を作れます。渡す時は関数の引数として渡します。

const BaseElement = styled.div`
  font-size: 14px;
`;

const ColorOrangeElement = styled(BaseElement)`
  color: orange;
`;

上記のColorOrangeElementは、

  • タグは<div>
  • スタイルはfont-size: 14px; color: orange;

な要素になっています。

もし<div>タグも変えたい場合は Styled なコンポーネントが持つwithComponentメソッドで変えることができます。例えば以下のようにすると<span>に変更できます。

const ColorOrangeElement = styled(
  BaseElement.withComponent('span')
)`
  color: orange;
`;

スコープ付き CSS のように使う

styledの引数に React コンポーネントを渡すとレンダリング時、そのプロパティに Styled Components がランダムに生成したclassNameが渡されます。

const Foo = props => {
  return <div className={props.className}>...</div>
};

const StyledFoo = styled(Foo)`
  color: orange;
`;
// ...

コンポーネントの構造が複雑な時、それら全てにclassNameをクラスに付与することで.scope.my-classNameのようなスコープを付きのセレクタでスタイル付けできるようになります。

またその時styledの中で.scope&と書くことができるので、実際には&.my-classNameと書くことができます。

const Foo = props => {
  return (
    <div className={`${props.className} first`}>
      <div className={`${props.className} second`}>
        <div className={`${props.className} third}`}>
          ...
        </div>
      </div>
    </div>
  );
};

const StyledFoo = styled(Foo)`
  &.first {
    color: orange;
  }

  &.second {
    width: 980px;
    margin: 0 auto;
  }

  &.third {
    display: inline-flex;
  }
`;

メディアクエリのヘルパーを作る

cssをラップするとメディアクエリが楽に書けるようになります。

css

この関数は、styled[element]テンプレートリテラルの中でさらに同じような書き方で書いたスタイルを埋め込みたい時につかいます。共通部分のスタイルを外部にまとめたいときとか便利なやつです。

import styled, {css} from 'styled-components';

const margin = css`
  ${props => props['data-margin'] || '10px'};
`;

const Div = styled.div`
  margin: ${margin};
`;
Edit styled-components の css

ラップしよう

テンプレートリテラル関数

以下はテンプレートリテラル関数の例です。戻り値はcssの戻り値と同じです。なので${desktop...}...はスタイル)という感じで使うことができます。しかもそのスタイルはメディアクエリで囲まれた状態になります。

const desktop = (first, ...interpolations) => css`
  @media (max-width: ${sizes[label]}px) {
    ${css(first, ...interpolations)}
  }
`;

まとめて定義

以下はオブジェクトの情報をまとめて、反復処理することでまとめて定義しています。これはmedia.desktopmedia.tabletといった形で使うことができます。

import {css} from 'styled-components';

const sizes = {
  desktop: 992,
  tablet: 768,
  phone: 576,
};

const media = Object.keys(sizes).reduce(
  (acc, label) => {
    acc[label] = (first, ...interpolations) => css`
      @media (max-width: ${sizes[label]}px) {
        ${css(first, ...interpolations)}
      }
    `;

    return acc;
  },
  {},
);

TypeScript

ちなみに TypeScript で書こうとするとこのようになります。

import {css, CSSObject, SimpleInterpolation} from 'styled-components';

const sizes: {[index: string]: number} = {
  desktop: 992,
  tablet: 768,
  phone: 576,
};

const media = Object.keys(sizes).reduce(
  (acc, label) => {
    acc[label] = (
      first: TemplateStringsArray | CSSObject,
      ...interpolations: SimpleInterpolation[]
    ) => css`
      @media (max-width: ${sizes[label]}px) {
        ${css(first, ...interpolations)}
      }
    `;

    return acc;
  },
  {} as {[index: string]: Function},
);
Edit メディアクエリのヘルパー

<body> や <html> などラップできない領域のタグのスタイルを書く

createGlobalStyleを使います。これはタグテンプレートになっていて、 CSS を書いて実行するとGlobalStyle(名前はなんでもいい)をコンポーネントを返します。これはレンダリング時にタグテンプレートに渡した CSS を<head>領域に追加してくれます。

createGlobalStylestyled-componentsから取り込めます。

import {createGlobalStyle} from 'styled-components';

あとはstyled.divのあとのようにスタイルを書くだけです。コンポーネントの中(children)には何も入れてはいけません。

const GlobalStyle = createGlobalStyle`
  html,
  body {
    height: 100%;
  }
`;

() => (
  <>
    <GlobalStyle />
    <ReduxProvider store={store}>
      <App />
    </ReduxProvider>
  </>
);

スナップショットテスト

スナップショットテストをする時は、そのままでは毎回ランダムなクラス名が設定され2回目以降毎回失敗するようになってしまいます。

Jest 環境ではjest-styled-componentsを使う事で、Styled Components が生成したクラス名を静的にレンダリングされた結果から取り除いてくれます。

使うにはまずこれをインストールし、

yarn add -D jest-styled-components

テストファイルでただ読み込みます。

import Foo from './foo-component';
import 'jest-styled-components';

test(...args);