@emotion/reactでのスタイル指定方法

## この記事の概要

EmotionはCSS in JSのライブラリの1つです。
The css PropやStyled Componentsなど様々な書き方ができるのが特徴です。

https://emotion.sh

筆者は基本的に`@emotion/react`のみを使っています。
その中でできる色々な書き方について紹介します。

:::note warn
中には`@emotion/styled`を使えばすぐに解決するようなものもあるのですが、面白半分で読んでいただけると助かります。
:::

## 基本の書き方

通常のCSSで言えば、1つのクラスとしてスタイルを適用するような書き方です。
これらの書き方がすべての基礎を成します。

### String Styles

テンプレートリテラルでCSSを指定する方法です。

既存のCSSやサンプルコードを参考にする場合、単にコピー&ペーストするだけで済むので楽です。
しかしあくまで文字列として適用するため、インテリセンスや型チェックは行われません。

以下のコードは[公式ドキュメント](https://emotion.sh/docs/install)の内容を一部変更したものです。

```jsx
import { css } from '@emotion/react'

const style = css`
  color: hotpink;
`

const SomeComponent = () => (
  <div css={style}>
    Some hotpink text.
  </div>
)
```

同じ内容をインラインでも書けます。

```jsx
import { css } from '@emotion/react'

const SomeComponent = () => (
  <div css={css`color: hotpink;`}>
    Some hotpink text.
  </div>
)
```

### Object Styles

オブジェクトとしてCSSを指定する方法です。

（TypeScriptと併用する前提ですが）インテリセンスと静的な型チェックが可能です。
ただし、既存のCSSを複製する際は書式を変更しないといけないので一手間かかります。
量が多い場合など、筆者は以下のツールを使用します。

https://transform.tools/css-to-js

以下のコードは[公式ドキュメント](https://emotion.sh/docs/install)の内容を一部変更したものです。

```jsx
import { css } from '@emotion/react'

const anotherStyle = css({
  textDecoration: 'underline'
})

const SomeComponent = () => (
  <div css={anotherStyle}>
    Some text with an underline.
  </div>
)
```

同じ内容をインラインでも書けます。

```jsx
import { css } from '@emotion/react'

const SomeComponent = () => (
  <div css={{textDecoration: 'underline'}}>
    Some text with an underline.
  </div>
)
```

## Composition

通常のCSSで言えば、マルチクラスでスタイルを適用するような書き方です。

### Include

明確に命名されているわけではなく、Sassのincludeに近いのでそう呼んでいるだけです。

以下のコードは[公式ドキュメント](https://emotion.sh/docs/composition)の内容を一部変更したものです。
これぐらいの内容だとそこまでメリットはありませんが「似ているけど少しずつ違う」スタイルを定義する際に使えるのが伝われば幸いです。

```jsx
import { css } from '@emotion/react'

const base = css`
  color: hotpink;
`

const SomeComponent = () => (
  <div>
    <div
      css={css`
        ${base};
        background-color: #eee;
      `}
    >
      Hotpink and Gray.
    </div>
    <div
      css={css`
        ${base};
        background-color: #000;
      `}
    >
      Hotpink and Black.
    </div>
  </div>
)
```

### Combine

これも明確に命名されているわけではありません。
また、考え方もほぼIncludeと同じです。
インラインで書いた方が早いときと、別途constを用意した方が使い回しやすいときと、状況に応じて使い分けるイメージです。

以下のコードは[公式ドキュメント](https://emotion.sh/docs/composition)の内容を参考にしたものです。

```jsx
import { css } from '@emotion/react'

const base = css`
  background-color: darkgreen;
  color: turquoise;
`

const loose = css`
  padding: 10px;
`

const SomeComponent = () => (
  <div>
    <div css={base}>This is a basic text.</div>
    <div css={[base, loose]}>This is text with lots of white space.</div>
  </div>
)
```

### Nested Selectors （公式の書き方）

[公式ドキュメント](https://emotion.sh/docs/nested)にあるように、タグセレクターをネストしてスタイルを指定できます。

```jsx
import { css } from '@emotion/react'

const paragraph = css`
  color: turquoise;

  a {
    border-bottom: 1px solid currentColor;
    cursor: pointer;
  }
`

const SomeComponent = () => (
  <p css={paragraph}>
    Some text. <a>A link with a bottom border.</a>
  </p>
)
```

また`&`を使うことで親子関係を逆にした指定も可能です。

```jsx
import { css } from '@emotion/react'

const paragraph = css`
  color: turquoise;

  header & {
    color: green;
  }
`

const SomeComponent = () => (
  <div>
    <header>
      <p css={paragraph}>This is green since it's inside a header</p>
    </header>
    <p css={paragraph}>This is turquoise since it's not inside a header.</p>
  </div>
)
```

### Nested Selectors （非公式な書き方）

とは言え、ネスト指定したいときはこういう書き方ではないでしょうか。
先程の例に少し変更を加えてみました。

```jsx
import { css } from '@emotion/react'

const paragraph = css`
  color: turquoise;
`

const box = css`
  ${paragraph} {
    color: green;
  }
`

const SomeComponent = () => (
  <div>
    <div css={box}>
      <p css={paragraph}>This is green.</p>
    </div>
    <div>
      <p css={paragraph}>This is turquoise.</p>
    </div>
    <p css={paragraph}>This is turquoise.</p>
  </div>
)
```

実現したいことは以下の通りですが、このコードでは叶いません。

- 通常の`paragraph`の色は`turquoise`
- `box` propを持った要素の中にある`paragraph`の色は`green`

というわけで、以下のように書き換えます。

```diff_jsx
  import { css } from '@emotion/react'

  const paragraph = css`
    color: turquoise;
  `

  const box = css`
-   ${paragraph} {
+   .css-${paragraph.name} {
      color: green;
    }
  `

  // 省略
```

ただしこの書き方はあくまでハックです。
Emotionのアップデート次第では動かなくなるため注意が必要です。

## その他色々

使わなくても何の支障もありませんが、知っていると便利、くらいの内容です。

### スタイルを1つのオブジェクトにまとめる

スタイルを1つのオブジェクトにまとめておくと、1つ1つのconstの名前を覚えておかないで済むので楽です。
以下の例でいえば`styles`をタイプした時点で`self`と`title`が候補として表示されます。

こちらのコードは[公式ドキュメント](https://emotion.sh/docs/best-practices)の内容を一部変更したものです。

```jsx
import { css } from '@emotion/react'

const styles = {
  self: css({
    backgroundColor: 'white',
    border: '1px solid #eee',
    borderRadius: '0.5rem',
    padding: '1rem'
  }),
  title: css({
    fontSize: '1.25rem'
  })
}

const SomeComponent({ title, children }) => {
  return (
    <div css={styles.self}>
      <span css={styles.title}>{title}</span>
      {children}
    </div>
  )
}
```

副次的ですが、CSS Modulesから移行したい際は書式が同じなので少し楽かもしれません。

### 動的に変化させる

css propの場合は動的なスタイリングは提供されていないのですが、CSS Custom Propertiesを用いれば実現できます。

以下は[公式ドキュメント](https://emotion.sh/docs/best-practices)の内容を一部変更したものです。

```jsx
import { css } from '@emotion/react'

const avatar =  css`
  border-radius: 50%;
  width: 40px;
  height: 40px;
  background-image: var(--background-style);
`

const SomeComponent = ({ imageUrl }) => (
  <div css={avatar} style={{ '--background-style': imageUrl }} />
)
```

CSS Custom Propertiesではなく、propsを引数として渡した関数を作り、その返り値をcss propsにすることも可能です。
その場合は三項演算子による分岐なども使えます。

```jsx
import { css } from '@emotion/react'

const avatar = (imageUrl) => css`
  border-radius: 50%;
  width: 40px;
  height: 40px;
  background-image: ${imageUrl ? imageUrl : 'url("path/to/defaultImage")'};
`

const SomeComponent = (props) => (
  <div css={avatar(props.imageUrl)} />
)
```

### propsの名前にあわせたスタイルを作る 1

渡ってきたpropsの名前が`primary`だったら`primary`のスタイルを適用し、`secondary`だったら`secondary`を、などと制御するのは意外と面倒なので、こういう指定の仕方も可能です。

例のためにJavaScriptで書いていますが、頭の中でパターンを網羅するのも大変なのでTypeScriptで書いた方が良いでしょう。

```jsx
import { css } from '@emotion/react'

const styles = {
  base: css({
    // 基本のスタイル
  }),
  primary: css({
    // primaryなときのスタイル
  }),
  secondary: css({
    // secondaryなときのスタイル
  }),
}

const SomeComponent = ({ children, priority }) => {
  return (
    <div css={[styles.base, styles[priority]]}>
      {children}
    </div>
  )
}
```

### propsの名前にあわせたスタイルを作る 2

先程の応用で、このようなことも可能です。

こちらも、実際の使用にあたってはTypeScriptが前提です。
``styles[`${priority}-${size}`]``をもとに有り得るパターンをすべてサジェストしてくれます。

```jsx
import { css } from '@emotion/react'

const styles = {
  base: css({
    // 基本のスタイル
  }),
  'primary-large': css({
    // 該当するスタイル
  }),
  'primary-medium': css({
    // 該当するスタイル
  }),
  'primary-small': css({
    // 該当するスタイル
  }),
  'secondary-large': css({
    // 該当するスタイル
  }),
  'secondary-medium': css({
    // 該当するスタイル
  }),
  'secondary-small': css({
    // 該当するスタイル
  }),
}

const SomeComponent = ({ children, priority, size }) => {
  return (
    <div css={[styles.base, styles[`${priority}-${size}`]]}>
      {children}
    </div>
  )
}
```

## 最後に

そんなの使う？みたいなものも含めて色々出してみました。
曲芸っぽいものもありましたが、何かの場面で役立てば幸いです。

---

最後まで読んでくださってありがとうございます！
Twitterでも情報を発信しているので、良かったらフォローお願いします！

https://twitter.com/xrxoxcxox