🎨

ポスト scss には Styled Components と css function の併用がおすすめ

2020/12/24に公開

こんにちは。猫飼いエンジニアの sotszk と申します。
この記事は YAMAP エンジニア Advent Calendar 2020 の 25 日目になります。締めです。

https://qiita.com/advent-calendar/2020/yamap-engginers

対象読者

  • css を長年 scss で書いている人
  • css in JS をどう書くか模索中の人
  • css in JS を書くならできる限り scss と同じ風に書きたい人

この記事で書くこと

  • scss で書いていたコードを Styled Components, css function で書くとどうなるか

この記事で書かないこと

  • StyledComponents, css function とは何か
  • StyledComponents, css function のセットアップ方法
  • 内部的にどういうことが行われているか
  • 実践的なコード(あくまでこんな風に書くよ、的なコードのみ)

本題

scss で書いていたコードを Styled Components, css function で書くとどうなるか

スタイルを当てる

scss で書く場合、例えば以下のような html があるとします。

<div class="navbar">
  <ul class="navbar__link-list">
    <li class="navbar__link-list-item">
      <a class="navbar__link" href="">りんく</a>
    </li>
  </ul>
</div>

scss でスタイルを当てるとこうなります。

.navbar {
  border: 1px solid #666;
}
.navbar__link-list {
  display: flex;
}
.navbar__link-list-item {
  list-style-type: none;
  &:not(:first-child) {
    margin-left: 4px;
  }
  @media screen and (min-width: 768px) {
    &:not(:first-child) {
      margin-left: 8px;
    }
  }
}
.navbar__link {
  text-decoration: none;
}

これを Styled Components, css function を使って置き換えてみます。

まずは Styled Components です。

const Navbar = () => (
  <Wrapper>
    <LinkList>
      <LinkListItem>
        <Link href="">りんく</Link>
      </LinkListItem>
    </LinkList>
  </Wrapper>
);

const Wrapper = styled.div`
  border: 1px solid #666;
`
const LinkList = styled.ul`
  display: flex;
`
const LinkListItem = styled.li`
  list-style-type: none;
  &:not(:first-child) {
    margin-left: 4px;
  }
  @media screen and (min-width: 768px) {
    &:not(:first-child) {
      margin-left: 8px;
    }
  }
`
const Link = styled.a`
  text-decoration: none;
`

次は css function です。

const Navbar = () => (
  <div css={styles.navbar}>
    <ul css={styles.linkList}>
      <li css={styles.linkListItem}>
        <a css={styles.link} href="">りんく</a>
      </li>
    </ul>
  </div>
);

const styles = {
  navbar: css`
    border: 1px solid #666;
  `,
  linkList: css`
    display: flex;
  `,
  linkListItem: css`
    list-style-type: none;
    &:not(:first-child) {
      margin-left: 4px;
    }
    @media screen and (min-width: 768px) {
      &:not(:first-child) {
        margin-left: 8px;
      }
    }
  `,
  link: css`
    text-decoration: none;
  `,
}

css 部分はまんま scss ですね。

Styled Components と css function の 2パターン書きましたが、実際は状況に応じてこれらを使い分けることになる場合がほとんどでしょう。

variables

css でも変数、定数を使うことがあると思います。
CSS in JS の場合、theme オブジェクトに一括で定義しておき、context API を使いアプリケーション全体に注入するやり方もあります。

ですが、今回は単に scss の $color-primary: red のようなもの値の使いまわしをする場合 Styled Components, css function ではどうするか、を紹介するに留めます。

ちなみに theme を注入することは theming といって、近年では様々なライブラリでサポートされています。

では scss から。

$color-primary: red;

Styled Components, css function の場合はこうです。

/* variables/colors.js */
export const colors = {
  primary: 'red',
}

/* navbar.jsx */
import { colors } from './variables/colors';
// ...
const Link = styled.a`
  color: ${colors.primary};
`
// theme を使うならこんな感じ
const LinkWithTheme = styled.a(({ theme }) => css`
  color: ${theme.colors.primary};
`);

mixins

たとえばこんな scss mixin があるとします。

@mixin buttonBase {
  display : inline-flex;
  white-space: nowrap;
  align-items: center;
  justify-content: center;
  padding: space(1) space(2);
  text-align: center;
  font-size: 1.4rem;
  border-radius: 4px;
  font-weight: bold;
  text-decoration: none;
  border: none;
  box-sizing: border-box;
  line-height: 1.6;
  background: transparent;
  cursor: pointer;
  transition-property: background, color;
  transition-duration: 100ms;
  transition-timing-function: ease-in;
}

@mixin buttonPrimary {
  @include btnBase;
  background: red;
  color: white;
  border: solid 1px red;
}

@mixin screenMax($break-point) {
  @media screen and (max-width: $break-point - 1) {
    @content;
  }
}

@mixin screenMin($break-point) {
  @media screen and (min-width: $break-point) {
    @content;
  }
}

.login-button {
  @include btnPrimary;
}
.ad {
  @include screenMin(768px) {
    display: none;
  }
}

上記を Styled Components, css function を使って書くとこうなります。

const buttonBase = css`
  display : inline-flex;
  white-space: nowrap;
  align-items: center;
  justify-content: center;
  padding: space(1) space(2);
  text-align: center;
  font-size: 1.4rem;
  border-radius: 4px;
  font-weight: bold;
  text-decoration: none;
  border: none;
  box-sizing: border-box;
  line-height: 1.6;
  background: transparent;
  cursor: pointer;
  transition-property: background, color;
  transition-duration: 100ms;
  transition-timing-function: ease-in;
`

const buttonPrimary = css`
  ${btnBase};
  background: red;
  color: white;
  border: solid 1px red;
`

const screenOver = (px) => {
  return `@media (min-width: ${px}px)`;
}

const screenUnder = (px) => {
  return `@media (max-width: ${px}px)`;
}
`

export const mixins = {
  buttonBase,
  buttonPrimary,
  screenOver,
  screenUnder,
}

そして、これを適用したコンポーネントは以下のようになります。

import { mixins } from './mixins';

const Navbar = () => (
  <Wrapper>
    <LinkList>
      <LinkListItem>
        <Link href="">りんく</Link>
      </LinkListItem>
    </LinkList>
  </Wrapper>
);

// ...
const LinkList = styled.ul`
  display: flex;
  ${mixins.screenUnder(767)} {
    display: block;
  }
`
const LinkListItem = styled.li`
  list-style-type: none;
  &:not(:first-child) {
    margin-left: 4px;
  }
  ${mixin.screenOver(768)} {
    &:not(:first-child) {
      margin-left: 8px;
    }
  }
`
const Link = styled.a`
  ${mixin.buttonPrimary};
`

蛇足

html タグに直接 css attribute を指定する場合、これを配列や関数で指定することも可能です。
後者の場合は (theme) => { ... } のように theme を受け取ることが可能です(theming をしている場合)。

const LoginButton = () => (
  <button css={[mixins.buttonPrimary, css`
    font-size: 2em;
  `]}>Login</button>
)

おわりに

以上です。少しでもみなさまのお役に立てば幸いです。

また、YAMAP では各種エンジニアを絶賛募集中です。少しでも興味がございましたら、お気軽に相談して頂ければと思います!

https://www.wantedly.com/projects/517777

https://www.wantedly.com/projects/536882

福岡はご飯が美味しいので、移住するのもおすすめですよ〜 :+1:
それでは良いお年を。

Discussion