これからのReactコンポーネントのスタイリングはstyled-componentsが良さそう

[前々回のprettier](http://qiita.com/kiida/items/405bb07c4b52bfee0219)と[前回のflow](http://qiita.com/kiida/items/ea758e4d80053663e12a)に引き続きReact Confで紹介されてたものを使うシリーズ第3弾です。

styled-componentsはSassやRadium、CSS Moduleといったスタイル系ライブラリの問題を解決するために生まれた新しいライブラリで、今Reactコンポーネントのスタイリングをするならベストの選択肢なんじゃないかと思います。

# 追記

* 2017/4/2 - jestでのテストについて追記しました。
* 2017/5/26 - v2について簡単に記載しました。

# styled-componentsとは

* [styled-components](https://styled-components.com/)
* 所謂CSS in JSの一種でjsx内に普通にcssが書ける。
    * キャメルケースとかせずにcssが書けるので、(複雑なことをしない限りは)移行が楽。
* props渡したりテーマ作ったり、extendできたり機能豊富。
* 擬似要素やmedia queryといったCSSでできることはほぼ(全部？)サポートされている。
* ベンダープレフィックスも自動で付く。(が、いらない場合に消したいときもある...)
* SSRやReact Nativeにも対応。
* 少し前に流行った[CSS Module](https://github.com/css-modules/css-modules)とさらにその前に流行った[Radium](https://github.com/FormidableLabs/radium)とスター数を比較すると[こんな感じ](http://www.timqian.com/star-history/#styled-components/styled-components&css-modules/css-modules&FormidableLabs/radium)。かなり勢いがあるのが分かる。
* [この記事](https://m.alphasights.com/css-evolution-from-css-sass-bem-css-modules-to-styled-components-d4c1da3a659b#.gy51wrtmb)がCSS -> SASS -> BEM -> CSS Module -> styled-componentsへの遷移をよく説明していると思う。
* エディタ連携も豊富
* flowとかlintのサポートもあるけどまだ安定してない。
* CSSを使っていた場合はJestのsnapshotテストでクラス名しかテストできなかったが、スタイルの中身をテストできるようになる。
* (CSS in JS系全般に言えることだけど)パフォーマンスへの影響の懸念はある。

# 使い方

前回の続きからeslint、prettier、flowが入ったところに追加します。
ソースは[ここ](https://github.com/daikikohara/learning-from-reactconf2017/tree/master/styled-components)にあります。

英語問題ない人は公式サイト見たほうがいいと思いますｗ

## 前準備

前回まではReact使わずに来たので、まずはReactを入れます。既にReact使ってるプロジェクトなら飛ばしてください。
というかReactのインストールは色んなところで紹介されてる&本題では無い&自分の構成は現在のベストプラクティスでは無いので詳細は書きません。
React入れた分のdiffは[このコミット](https://github.com/daikikohara/learning-from-reactconf2017/commit/23b742061cfa1983e894b5c20eed01cbe38a5473)見てもらえればと思います。

## cssの代わりにスタイルしてみる

まずはstyled-componentsをインストールします。

```bash
yarn add styled-components
```

そして以下のようにstyled-componentを使ったcomponentを作ります。

```javascript:Container.jsx
import styled from 'styled-components';

const Container = styled.article`
  background: #F6F6F6;
  width: 100%;
  height: 100vh;
`;

export default Container;
```

```javascript:Header.jsx
import styled from 'styled-components';

const Header = styled.header`
  font-size: 1.5rem;
  padding: 2rem;
  text-align: center;
  color: #FFC507;
  background: #353B41;
`;

export default Header;
```

素のcssなのでかなり分かりやすいんじゃないでしょうか。

そしてこれらを使うコンテナを作ります。

```javascript:App.jsx
import React from 'react';
import {Container, Header} from './components';

const App = () => (
  <Container>
    <Header>Title</Header>
  </Container>
);

export default App;
```

するとこのような結果になります。
![Screenshot from 2017-03-25 00-20-33.png](https://qiita-image-store.s3.amazonaws.com/0/36003/28c33809-b9bc-80aa-db9d-49a7806c3a46.png)

かなり直感的に使えます。

## propsを渡す

こんな感じでpropsを使うこともできます。

```javascript:DefaultButton.jsx
import styled from 'styled-components';

const Button = styled.button`
  background: ${props => props.active ? 'gray' : 'white'};
  color: ${props => props.active ? 'white' : 'black'};
  margin: 1rem;
  font-size: 2rem;
  padding: 0.25rem 1rem;
  border: 2px solid ${props => props.active ? 'transparent' : 'black'};
  border-radius: 3px;
  &:hover {
    cursor: pointer;
  }
`;

export default Button;
```

ちなみに上記の通り擬似要素なども問題なく使えます。
そして使う側では普通にpropsを渡せばOKです。

```javascript:Main.jsx
class Main extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      active: false,
    };
  }

  toggleButton() {
    this.setState({
      active: !this.state.active,
    });
  }

  render() {
    return (
      <Container>
        <Buttons>
          <DefaultButton
            onClick={() => this.toggleButton()}
            active={this.state.active}
          >
            Button
          </DefaultButton>
        </Buttons>
      </Container>
    );
  }
}
```

するとこんな感じのボタンになります。

![Untitled.png](https://qiita-image-store.s3.amazonaws.com/0/36003/b5117224-a7d8-ca1c-545d-5fa77f7f6bf8.png)

## Overrideする

コンポーネントのOverrideもできます。
上記で作ったDefaultButtonをOverrideして以下のような文字サイズを倍にしたボタンを作ってみます。

```javascript:BigButton.jsx
import styled from 'styled-components';
import DefaultButton from './DefaultButton';

const Button = styled(DefaultButton)`
  font-size: 4rem;
`;

export default Button;
```

するとこんな感じのボタンを作ることができます。
フォントサイズ以外はOverrideした元のコンポーネントのスタイルが適用されていることが分かります。

![Screenshot from 2017-03-25 11-53-09.png](https://qiita-image-store.s3.amazonaws.com/0/36003/8c3ddc9a-ea5a-f7a6-04da-a374316ea7b5.png)

## Themeを使う

Themeを使って共通のスタイルを適用することもできます。

まずは以下のようなGreenButtonsというコンポーネントを作ります。

```javascript:GreenButtons.jsx
import React from 'react';
import {ThemeProvider} from 'styled-components';
import Buttons from './Buttons';

const theme = {
  main: 'mediumseagreen',
};

const GreenButtons = props => {
  return (
    <ThemeProvider theme={theme}>
      <Buttons>
        {props.children}
      </Buttons>
    </ThemeProvider>
  );
};

GreenButtons.propTypes = {
  children: React.PropTypes.element,
};

export default GreenButtons;
```

そしてThemeを使うButtonを作ります。

```javascript:ThemedButtons.jsx
import styled from 'styled-components';
import DefaultButton from './DefaultButton';

const ThemedButton = styled(DefaultButton)`
  background: ${props => props.theme.main};
`;

ThemedButton.defaultProps = {
  theme: {
    main: 'palevioletred',
  },
};

export default ThemedButton;
```

上記の通り、Themeが指定されなかった場合に備えてdefaultPropsを指定することもできます。

そして使う側で以下のようにGreenButtonsコンポーネント囲うとテーマが適用されたButtonになります。

```javascript:Main.jsx
<GreenButtons>
  <ThemedButton>
    Green Button
  </ThemedButton>
</GreenButtons>
```

![Screenshot from 2017-03-25 12-23-01.png](https://qiita-image-store.s3.amazonaws.com/0/36003/8c104291-df60-8634-8f98-61f34c7823c8.png)

Themeを使ったコンポーネントで囲わないと、defaultPropsで与えた色のButtonになります。

```javascript:Main.jsx
<GreenButtons>
  <ThemedButton>
    Green Button
  </ThemedButton>
</GreenButtons>
<Buttons>
  <ThemedButton>
    Default Themed Button
  </ThemedButton>
</Buttons>
```

![Screenshot from 2017-03-25 12-23-07.png](https://qiita-image-store.s3.amazonaws.com/0/36003/dfe03fb8-0099-8208-dedf-38891a732cc4.png)

## 他ツールとの連携

### lint

[stylelint](https://stylelint.io/)と連携する[stylelint-processor-styled-components](https://github.com/styled-components/stylelint-processor-styled-components)を使うとstyled-componentのスタイルをチェックすることができます。(まだexperimental)

まずは必要なパッケージをインストールします。

```bash
yarn add -D stylelint-processor-styled-components stylelint stylelint-config-standard
```

そして以下の内容でプロジェクトルートに`.stylelintrc`を作ります。


```json:.stylelintrc
{
  "processors": ["stylelint-processor-styled-components"],
  "extends": "stylelint-config-standard",
  "syntax": "scss"
}
```

そして以下のように実行するとエラーがある場合はエラー内容を出力してくれます。

![Screenshot from 2017-03-25 14-01-17.png](https://qiita-image-store.s3.amazonaws.com/0/36003/c37226ca-6ea1-7848-8de0-a1217df02e13.png)

stylelintは[stylefmt](https://github.com/morishitter/stylefmt)と連携することで自動整形もしてくれますが、[styled-componentのissue](https://github.com/styled-components/styled-components/issues/589)でprettierによる整形が議論されてるので、今回は見送りました。あとで方針が固まったら追記します。

## flow

[このページ](https://github.com/styled-components/styled-components/blob/master/docs/flow-support.md)にある通りflowとの連携もできます。

が、現状はその通りに設定しても[このissue](https://github.com/styled-components/styled-components/issues/334)で触れられている問題と[このissue](https://github.com/styled-components/styled-components/issues/458)で触れられている問題に当たってしまい有効にできなかったため一旦見送りました。こちらも解決したあとにまた追記したいと思います。

## vim

[vim-styled-components](https://github.com/fleischie/vim-styled-components)を使うとシンタックスハイライトが使えます。
deinとかでインストールして有効にするだけでシンタックスハイライトが効きます。

```toml:deinlazy.toml
[[plugins]]
repo = 'fleischie/vim-styled-components'
on_ft = ['javascript', 'javascript.jsx']
```

## jest

jestのsnapshotテストはstyled-componentと相性抜群です。外部css(sass)に書くとクラス名しかテストできずあまり意味のないテストになっているのが課題でした。
しかしstyled-componentsを使うとスタイルがすべてテストできるようになります。

まずは必要なライブラリを追加します。

```bash
yarn add -D jest react-test-renderer jest-styled-components
```

.eslintrc.jsを編集してjest関連に対応します。

```json:.eslintrc.js
  env: {
    browser: true,
    jest: true,
  },
```

package.jsonにjestのセクションを作り、以下のように指定します。

```json:package.json
  "jest": {
    "bail": true,
    "testEnvironment": "node",
    "rootDir": "src"
  },
```

そしてDefaultButton.jsxと同じディレクトリに以下のようなファイルを作成します。

```javascript:DefaultButton.test.jsx
import React from 'react';
import {matcher, serializer} from 'jest-styled-components';
import renderer from 'react-test-renderer';
import DefaultButton from './DefaultButton';

expect.extend(matcher);
expect.addSnapshotSerializer(serializer);

describe('DefaultButton Test', () => {
  it('should be rendered correctly on active', () => {
    const renderedElement = renderer.create(<DefaultButton active={true} />);
    expect(renderedElement).toMatchStyledComponentsSnapshot();
  });
  it('should be rendered correctly on disabled', () => {
    const renderedElement = renderer.create(<DefaultButton active={false} />);
    expect(renderedElement).toMatchStyledComponentsSnapshot();
  });
});
```

そしてプロジェクトのトップでjestコマンドを実行すると以下のようなsnapshotファイルが作成されます。

```:DefaultButton.test.jsx.snap

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`DefaultButton Test should be rendered correctly on active 1`] = `
.jXtbih {
  background: gray;
  color: white;
  margin: 1rem;
  font-size: 2rem;
  padding: 0.25rem 1rem;
  border: 2px solid transparent;
  border-radius: 3px;
}

.jXtbih:hover {
  cursor: pointer;
}

<button
  className="jXtbih"
/>
`;
(略)
```

スタイル自体がテストできることが分かります。かなり便利ですね！

ただし、欠点があって現状はDOM操作ができないので、挙動のテストができません。
なのでスタイルも挙動もテストするとなると別々のテストファイルとpackage.json作ってテストするとかめちゃくちゃな対応が必要になります...中々でかい欠点ですね...
ただ[issueが上がってるので](https://github.com/styled-components/jest-styled-components/issues/2)その内解決するんじゃないかと思います。
解消したらまた追記したいと思います。

# v2

2017/5/25に[v2が発表された](https://medium.com/styled-components/announcing-v2-f01ef3766ac2)のでv2の新機能を簡単に紹介します。

## extend

[extend](https://www.styled-components.com/docs/basics#extending-styles)を使うと既存のコンポーネントを拡張できます。
もともと上記の[Overrideする](http://qiita.com/kiida/items/ceac662d9a8bdd960499#override%E3%81%99%E3%82%8B)で書いた通り`styled(AnotherComponent)`みたいな書き方でできましたが、その場合は二つのクラス名が追加されてしまう、両方のコンポーネントがReactのrender tree以下にできてしまうといった問題があるため、extendが推奨らしいです。

上記の続きの例としてExtendedButtonを作ると以下のような感じで作ることができます。

```js:ExtendedButton.jsx
import DefaultButton from './DefaultButton';

const ExtendedButon = DefaultButton.extend`
  background: midnightblue;
  font-size: 4rem;
  color: white;
  &:hover { text-shadow: 0 0 4px white; }
`;

export default ExtendedButon;
```

![Screenshot from 2017-05-26 15-04-36.png](https://qiita-image-store.s3.amazonaws.com/0/36003/02eba14b-a243-84a6-1dd7-57596465f642.png)


## attrs

[attrs](https://www.styled-components.com/docs/basics#attaching-additional-props)で属性を渡すことが可能になりました。

例えばクラス名を与えればglobalに定義されたCSSのクラスを当てることができます。

```js
const Header = styled.header.attrs({
  className: 'p2 bold white bg-blue'
})`
  ...
`
```

attrよってReact RouterのLinkコンポーネントのactiveClassNameに値を渡したりできるようになります。

## withComponent

withComponentは別のタグに同じスタイルを使いたいみたいな状況で役立ちます。

```js
const Heading = styled.h1`
  /* ... */
`

export const H1 = Heading
export const H2 = Heading.withComponent('h2')
```

## SSRがサポートされた

自分はSSRはなるべくしたくない派なので[使い方は公式](https://www.styled-components.com/docs/advanced#server-side-rendering)を見てくださいということで...
とりあえず必要最低限のCSSしか送らない、一度injectされたCSSは再renderしないみたいな作りになってるらしいです。

ということで、簡単にv2の紹介でした。詳しくは[最初に記載したブログポスト](https://medium.com/styled-components/announcing-v2-f01ef3766ac2)や[新しくなった公式サイト](https://www.styled-components.com/)を見てもらえればと思います。


# 最後に

以上、参考になれば幸いです。

また必要に応じて追記・更新したいと思います。