はじめに
先日、EmotoinをNext.jsに導入する記事を書きました。
Next.js 10 + TypeScriptでEmotionを使う
今度は、Emotionをどのように使えるのかを記事にしていきたいと思います。
Emotionを使うと何が嬉しいのかはこちらで記事にしているので、是非こちらも読んでみてください!
Emotionはいいぞ
Emotionを使う
まず、Emotionの基本的な使い方について、解説していきます。
Emotionでは以下のようにCSSを記述することができます。
import React from 'react'
import { css } from '@emotion/react'
// パターン1
const hello = css`
  color: red;
`
type Props = {
  name: string
}
export const Hello: React.FC<Props> = (props) => {
  const { name } = props
  return (
    <div>
      <h1 css={hello}>Hello {name}!</h1>
      {/* パターン2 */}
      <h1
        css={css`
          color: blue;
        `}
      >
        Hello {name}!
      </h1>
    </div>
  )
}
helloという変数にバインドしてcssプロパティに指定している方法(パターン1)と、直接cssプロパティに渡している方法(パターン2)です。
この時のhelloにバインドされている値は、以下のようになっています。
const hello = {
  map: "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,... */"
  name: "1kj5occ-hello"
  styles: "color:red;label:hello;"
  toString: ƒ _EMOTION_STRINGIFIED_CSS_ERROR__()
}
このオブジェクトをcssプロパティが解釈してcssを生成し、classNameにセットしているという流れです。
labelという見慣れない項目がstylesにありますが、これはEmotion独自の機能で、このcssの生成するクラス名にlabelの文字列が結合されて、デバッグしやすくなるというものです。
特に指定しなければ、バインドした変数の名前が設定されます。
パターン2の方では、コンポーネントの名前が設定されます。
Generated class names include the name of the variable or component they were defined in.
とあるように、Babelによって変数の名前が設定されるようになっているので、デバッグがしやすいように直接cssプロパティに指定するのではなく、一度変数にバインドするようにしましょう。
labelに独自の名前を設定して、クラス名に含めることも可能なので、デバッグしやすいように使ってください。
cssの宣言は
const classes = {
  // css-8bac6k-hoge
  hoge: css``,
  // css-1m4255s-test-fuga
  fuga: css`
    label: test;
  `
}
のようにもできるので、このようにしておくと便利でしょう。
Babelの設定でファイル名やディレクトリ名を含むようにすることも可能なので、cssの場所がわかりにくいプロジェクトだと設定をしておくとデバッグがしやすくなるかもしれません。
必要に応じて設定してください。
CSSをマージする
マージと書きましたが、Compositionです。
他で定義したcssのオブジェクトをcssテンプレート文字列の中に埋め込むことができます。
const borderBase = css`
  border: 1px solid gray;
  border-radius: 5px;
`
const iconButton = css`
  ${borderBase}
  border-radius: 50%;
`
また、cssに配列で指定することで、優先度を指定してクラスを適応することができます。
<button css={[borderBase, iconButton]}>?</button>
細かくは解説しないですが、これによって解決されるCSSの問題点もあります。
気になる人は公式の解説がわかりやすいので、是非読んでみてください。
擬似要素・擬似クラスやメディアクエリを使う
メディアクエリや擬似要素・擬似クラスなども記述することができます。
sassやscssのように、&をそのクラス名として使うことができるので、以下のように記述できます。
const hoge = css`
  &:hover {
    color: red;
  }
  @media (min-width: 420px) {
    &:hover {
      color: blue;
    }
  }
`
<div css={hoge}>test</div>
参考:
https://emotion.sh/docs/nested
https://emotion.sh/docs/media-queries
グローバルなスタイルを設定する
グローバルなスタイルが必要な場合もあるでしょう。
その場合は、以下のように設定します。
import { Global, css } from '@emotion/react'
render(
  <div>
    <Global
      styles={css`
        .some-class {
          color: hotpink !important;
        }
      `}
    />
    <Global
      styles={{
        '.some-class': {
          fontSize: 50,
          textAlign: 'center'
        }
      }}
    />
    <div className="some-class">This is hotpink now!</div>
  </div>
)
しかし、これを多用してクラス名を宣言するとEmotionの全てのクラス名が一意であるという利点が失われてしまうので気をつけてください。
タグのデフォルトのCSSを打ち消す場合などには便利だと思います。
参考: https://emotion.sh/docs/globals
Themeを使う
ThemeをProviderに設定して使うことができます。
import { ThemeProvider } from '@emotion/react'
const theme = {
  colors: {
    primary: 'blue'
  }
}
render(
  <ThemeProvider theme={theme}>
    <div
      css={(theme) =>
        css`
          color: ${theme.colors.primary};
        `
      }
    >
      hoge
    </div>
  </ThemeProvider>
)
themeなんてコンスタントな変数で宣言してimportすればいいじゃんと思う人もいるかもしれませんが、ライトテーマとダークテーマを切り替えたい場合などにProviderに設定しておくことで切り替えやすく、便利にthemeを扱えます。
TypeScriptではThemeの型はデフォルトではからのオブジェクト({})になっているので、型を宣言してやらないといけません。
import '@emotion/react'
declare module '@emotion/react' {
  export interface Theme {
    color: string
    secondaryColor: string
  }
}
このようにTheme型を宣言して、読み込ませましょう。
参考:
https://emotion.sh/docs/theming
https://emotion.sh/docs/emotion-11#theme-type
Object Styleを使う
基本的にはcssをタグ付きテンプレートリテラルとして使うのが使いやすいと思いますが、オブジェクト形式でCSSを記述することもできます。
Object形式の方がスクリプトから弄りやすいなどのメリットもあるので、必要に応じて使い分けてみてください。
テンプレート文字列に大量の色を埋め込むよりかは、オブジェクト形式で記述した方が可読性が上がる場合などがあるかもしれません。
const hoge = css({
  backgroundColor: color
})
参考: https://emotion.sh/docs/object-styles
忘れがちなこと
Emotionを使っていると、コンポーネントの外部から設定したcssが効かなくて悩むことがあります。(私もこれで小一時間悩みました)
コンポーネントを実装した場合で、cssによるスタイルの拡張を許したい場合は、classNameをpropsで受け取って、対象のElementに渡すようにしましょう。
cssプロパティはcssに渡されたオブジェクトを解釈してclassNameにクラス名を渡すというような挙動をするので、コンポーネント側でclassNameをちゃんと設定しないと機能してくれません。
意外と忘れがちなので、特に変なことをしていないのにcssが効かないという時があれば、これを確認してみましょう。
参考
まとめ
最後まで読んでいただきありがとうございます。
どうだったでしょうか?
個人的に、これだけ使えればEmotionを使いこなせていると言えるんじゃないかという基準でまとめてみました。
他にも解説しきれていない機能などもありますので、是非公式ドキュメントの方も読んでEmotionを使いこなして行ってください!
