[React] 年末だしstyled-componentsで爆速レスポンシブ対応していこうな

# はじめに
こんにちは、ねりこ @nerikosans と申します。もう2019年も終わりが近づいてきましたね。
皆様におかれましてはますます `import * as React from 'react';` の由、何よりと存じます。日頃は特別の `export default` にあずかり心より御礼申し上げます。

さて、[Styled Components](https://www.styled-components.com/) は、DOMのスタイルを、その定義ファイル( `.jsx`,  `.tsx` )内で完結させてしまおうという思想のフレームワークです。コードの見通しの良さ、動的レンダリングのしやすさなどから昨今は人気になってい(ると感じてい)ます。

そして、DOMスタイリングにはレスポンシブ対応がつきものですが、やっぱり爆速で書きたいですよね。
ということで、 styled-componentsでレスポンシブするときのメモ書きです。

# 目標
最低限の記法で書きたいので、これ↓だけ書けば対応が済むように構築します。

``` .tsx
const Box = styled.div`
  background: black;

  /* PC */
  ${media.pc`
    background: red;
  `}

  /* Smartphones */
  ${media.sp`
    background: red;
  `}
`;
```

# Step1. styled-media-query
[morajabi/styled-media-query](https://github.com/morajabi/styled-media-query)
よく使うサイズをサクッと使いたい場合はこれだけで大丈夫。
`small`, `medium`, `large`, `huge` の4つの width breakpoint を備えていて、media queryを自動で生成してくれます。

公式サンプルが以下の通り。

``` .tsx
const Box = styled.div`
  background: black;

  ${media.lessThan("medium")`
    /* screen width is less than 768px (medium) */
    background: red;
  `}

  ${media.between("medium", "large")`
    /* screen width is between 768px (medium) and 1170px (large) */
    background: green;
  `}

  ${media.greaterThan("large")`
    /* screen width is greater than 1170px (large) */
    background: blue;
  `}
`;
```

便利ですね。でも `.lessThan("medium")` って毎回書きたくないので、これをwrapします。
`styled-media-query` の export されていない type を使用しているところがあります。

``` media.tsx

import media from 'styled-media-query';
import {
  ThemedStyledProps,
  InterpolationValue,
  FlattenInterpolation,
} from 'styled-components';

/**
 * https://github.com/morajabi/styled-media-query/blob/master/src/index.d.ts
 */
type InterpolationFunction<Props, Theme> = (
  props: ThemedStyledProps<Props, Theme>
) => InterpolationValue | FlattenInterpolation<ThemedStyledProps<Props, Theme>>;

type GeneratorFunction<Props, Theme> = (
  strings: TemplateStringsArray,
  ...interpolations: (
    | InterpolationValue
    | InterpolationFunction<Props, Theme>
    | FlattenInterpolation<ThemedStyledProps<Props, Theme>>
  )[]
) => any;

const rules: { [v: string]: GeneratorFunction<unknown, any> } = {
  pc: (...args) => media.greaterThan('medium')(...args),
  sp: (...args) => media.lessThan('medium')(...args),
};

export default rules;
```

よし、これでこのファイルを `media` として default importすれば、 ``${media.pc` .... `}`` だけでpc専用スタイルを書けるようになりました！

# Step2. カスタマイズ
さて、これだけでも便利ですが、styled-media-query では今のところ pre-defined な4つのサイズ以外は指定できないようなので、これに加えて自由なmedia queryを書きたい場合 (例えば、heightで区切りたい場合) は以下のようにすればOKです。

``` media.tsx
import media from 'styled-media-query';
import {
  ThemedStyledProps,
  InterpolationValue,
  FlattenInterpolation,
  css,
} from 'styled-components';

/* (... 中略) */

const rules: { [v: string]: GeneratorFunction<unknown, any> } = {
  pc: (...args) => media.greaterThan('medium')(...args),
  sp: (...args) => media.lessThan('medium')(...args),
  short: (...args) => css`
    @media screen and (max-height: 480px) {
      ${css(...args)}
    }
  `,
};

export default rules;
```
これで新たに ``media.short`...` ``が使えるようになりました！

# おわりに / 展望
以上で、晴れて簡潔にレスポンシブスタイルが書けるようになりました。最高ですね。

しかし、そもそもComponentを `render` するかどうかから出し分けたい場合などは、この方法では足りません。
例えば

``` .tsx
const media = useMedia();
const isPC = media.pc;
```
みたいに書けたら便利ですよね。これを実現する方法は~~また今度書きたいと思います~~。
**追記: 書きました！ [[React] 年末だしwindow.matchMedia()で爆速レスポンシブ対応していこうな](https://qiita.com/nerikosans/items/fe722727da3a0008d082)**

お読みいただきありがとうございました！