Atomic DesignをReact + styled-componentsで頑張る

備忘録で書きます。

## styled-components
`css in js`の一種でJSX内でcssを書くためのツールです。
ES6のタグ付きテンプレートリテラルを使用し、ネストして書けたり、`extend`機能などが使えて人気のツールとなっています。
SSRやReact Nativeにも対応していて、位置的にはzeitが作っている`styled-jsx`に近いんじゃないかと。

> styled-components
> https://github.com/styled-components/styled-components

具体的には以下のように`styled.button`のように記述すると`button`要素としてReactコンポーネントが返る仕組みです。これを`styled.div`にすれば`div`要素が、`styled.a`にすれば`a`要素が返ります。

```jsx:Button.js
import styled from 'styled-components';

const Button = styled.button`
    display: inline-block;
    padding: 0.8rem;
    border-radius: 5px;
    border: 1px solid #ddd;
    font-weight: bold;
    font-size: 0.8rem;
    line-height: 1;
    text-decoration: none;
    transition: opacity 0.3s;
    color: #8c8c8c;
    cursor: pointer;
`;

const ButtonComponent = ({...props}) => <Button {...props} />;

export default ButtonComponent;
```

```jsx:Index.js
import Button from './Button.js';

...

render() {
    return() {
        <Button>Default Button</Button>
    }
}

...

```

上記を実行すると以下のようにボタン要素でレンダリングになります。
classもハッシュのような形式で自動生成されますので命名規則などに縛られることはありません。

![スクリーンショット 2018-06-11 14.07.37.png](https://qiita-image-store.s3.amazonaws.com/0/80473/68ae29fd-173f-dd8a-2fa4-93c2086e982f.png)

![スクリーンショット 2018-06-11 14.11.35.png](https://qiita-image-store.s3.amazonaws.com/0/80473/b9ebb759-b5d9-f34c-5c20-b64c7130f354.png)

## Atomic Designとの併用
Atomic Designはかなり有名なため説明は割愛します。
超ざっくり説明すると最小単位のAtomsからコンポーネントを作成し、Molecules、Organismsなどコンポーネントの組み合わせてUIを構築する手法です。親コンポーネントの変更が子コンポーネントに依存しないことがポイントで、これにより堅牢な設計を担保できるという設計手法です。

以下は有名な図です。

![atomic-design-process.png](https://qiita-image-store.s3.amazonaws.com/0/80473/ee4e507d-a077-6baa-adb2-db05cdcb0706.png)

### Atoms
#### Button.js
先程の例でButtonコンポーネントを作ります。

```jsx:Button.js
import React from 'react';
import styled from 'styled-components';

const Button = styled.button`
    display: inline-block;
    padding: 0.8rem;
    border-radius: 5px;
    border: 1px solid #ddd;
    font-weight: bold;
    font-size: 0.8rem;
    line-height: 1;
    text-decoration: none;
    transition: opacity 0.3s;
    color: #8c8c8c;
    cursor: pointer;
`;

const ButtonComponent = ({...props}) => <Button {...props} />;

export default ButtonComponent;
```

![スクリーンショット 2018-06-11 14.07.37.png](https://qiita-image-store.s3.amazonaws.com/0/80473/42309142-21ee-7259-03c8-b7bab7f7f19f.png)

#### TextBox.js
続いて`input[type="text"]`のコンポーネントを作成します。
`styled.input.attrs`で`type: 'text'`にするところがポイントです。

```jsx:TextBox.js
import React from 'react';
import styled from 'styled-components';

const TextBox = styled.input.attrs({
    type: 'text',
})`
    padding: 0.5rem;
    border: 1px solid #ddd;
    border-radius: 2px;
    color: #000000;
    font-size: 0.8rem;
`;

const TextBoxComponent = ({ ...props }) => <InputEle {...props} />;

export default TextBoxComponent;
```

![スクリーンショット 2018-06-11 14.25.10.png](https://qiita-image-store.s3.amazonaws.com/0/80473/2a2d4b4b-b2fc-af14-73ec-5debafd09445.png)

### Molecules

#### MailForm

ここでは上で作成したAtomsである`Button`コンポーネント・`TextBox`コンポーネントを使用して、Moleculesの`MailForm`を作成してみます。

ポイントは`styled(TextBox)`の部分で、こうすることによってAtomsを拡張してcssを書くことができます。しかもclass名は自動で生成されるため、Atomsの`TextBox`コンポーネントには何も影響がありません。

```jsx:MailForm.js
import React from 'react';
import styled from 'styled-components';

import TextBox from '../Atoms/TextBox';
import Button from '../Atoms/Button';

const Wrapper = styled.div`
    display: flex;
    align-items: center;
`;

const TextBox_Styled = styled(TextBox)`
    flex: 1;
    margin-right: 0.5rem;
`;

const MailForm = ({ onSubmit, ...props }) => (
    <form {...props}>
        <Wrapper>
            <TextBox_Styled />
            <Button onClick={onSubmit}>認証メール送信</Button>
        </Wrapper>
    </form>
);

export default MailForm;
```

で、これをレンダリングすると以下のように横並びになります。

![スクリーンショット 2018-06-11 14.37.40.png](https://qiita-image-store.s3.amazonaws.com/0/80473/f562207b-e1b9-3017-34ae-14c0783fe302.png)

### Organisms ~ Templates
このあたりは上記と同じやり方で、子コンポーネントを`styled(component)`で書いていくだけだと思います。

## Tips
### 複数のHTML要素になる可能性があるコンポーネント
コンポーネントを書いていると`div`でもいいし`section`でもいいし、使い方によっては`main`とか`article`にもなり得る要素が存在します。その場合は、`styled()`のコールバック関数に`React.createElement`を渡すことで対応可能です。

```jsx
import React from 'react';
import styled from 'styled-components';

const Tag = styled(({ children, tag = 'div', ...props }) => {
    return React.createElement(tag, props, children);
})``;

const Layout = ({ ...props }) => <Tag {...props} />;

export default Layout;
```