コンテンツにスキップ
ブログに戻る

2019年3月28日木曜日

Styled JSX を使った Next.js のスタイリング

投稿者

Styled JSX は、コンポーネントをスタイルするためにカプセル化され、スコープされた CSS を記述できる CSS-in-JS ライブラリです。あるコンポーネントに追加したスタイルは他のコンポーネントに影響を与えないため、意図しない副作用を心配することなく、スタイルを追加、変更、削除できます。

はじめに

Next.js には Styled JSX がデフォルトで含まれているため、既存の React 要素に <style jsx> タグを追加し、その中に CSS を記述するだけで簡単に始められます。

pages/index.js
function Home() {
  return (
    <div className="container">
      <h1>Hello Next.js</h1>
      <p>Let's explore different ways to style Next.js apps</p>
      <style jsx>{`
        .container {
          margin: 50px;
        }
        p {
          color: blue;
        }
      `}</style>
    </div>
  );
}
 
export default Home;

この例では、コンポーネントのコンテナ要素と段落のスタイルを含めています。汎用的なセレクターを使用しているにもかかわらず、スタイルは他のコンポーネントの container クラス名や <p> タグを持つ要素には影響しません。これは、Styled JSX がスタイルをこのコンポーネントのみにスコープしているためです(スタイルされた要素に追加の一意のクラス名を適用することにより)。

<style> 要素に jsx 属性を 1 つ追加するだけで、自動的にプレフィックスが付けられ、コンポーネントに自動的にスコープされる標準 CSS を記述できます。<style jsx> 要素は、コンポーネントのルート要素内に配置する必要があります。

グローバルスタイルの追加

ほとんどのプロジェクトでは、body 要素をスタイルしたり、CSS リセットを提供するために、いくつかのグローバルスタイルが必要です。Styled JSX では、<style jsx global> を使用してグローバルスタイルを追加できます。例えば

pages/index.js
function Home() {
  return (
    <div className="container">
      <h1>Hello Next.js</h1>
      <p>Let's explore different ways to style Next.js apps</p>
      <style jsx>{`
        .container {
          margin: 50px;
        }
        p {
          color: blue;
        }
      `}</style>
      <style jsx global>{`
        p {
          font-size: 20px;
        }
      `}</style>
    </div>
  );
}
 
export default Home;

これにより、この特定のページのすべての <p> タグに 20px のフォントサイズが適用されます。

アプリ内のすべてのページにグローバルスタイルを適用する場合、最初にグローバルスタイルを持つレイアウトコンポーネントを作成し、すべてのページをそれでラップするのが良い方法です。

レイアウトコンポーネントを使用すると、一部のページに特定のスタイルのセットを適用し、他のページには異なるスタイルを適用できる柔軟性が得られます。

components/Layout.js
function Layout(props) {
  return (
    <div className="page-layout">
      {props.children}
      <style jsx global>{`
        body {
          margin: 0;
          padding: 0;
          font-size: 18px;
          font-weight: 400;
          line-height: 1.8;
          color: #333;
          font-family: sans-serif;
        }
        h1 {
          font-weight: 700;
        }
        p {
          margin-bottom: 10px;
        }
      `}</style>
    </div>
  );
}
 
export default Layout;

Next.js では、pages/_app.js 内にカスタムの App コンポーネントを作成し、Layout コンポーネントをインポートして、次のように render メソッドに追加することにより、すべてのページに対してレイアウトを一度だけ読み込むことができます。

pages/_app.js
import React from 'react';
import App, { Container } from 'next/app';
import Layout from '../components/Layout';
 
class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props;
 
    return (
      <Container>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </Container>
    );
  }
}
 
export default MyApp;

外部ファイルでのスタイルの記述

コンポーネントの外部にある外部ファイルにスタイルを記述することもできます。

たとえば、Layout コンポーネントからグローバルスタイルを次のように別のファイルに移動できます。

styles/global.js
import css from 'styled-jsx/css';
 
export default css.global`
  body {
    margin: 0;
    padding: 0;
    font-size: 18px;
    font-weight: 400;
    line-height: 1.8;
    color: #333;
    font-family: sans-serif;
  }
  h1 {
    font-weight: 700;
  }
  p {
    margin-bottom: 10px;
  }
`;

次に、スタイルを Layout コンポーネントにインポートして戻すことができます。

components/Layout.js
import globalStyles from '../styles/global.js';
 
function Layout(props) {
  return (
    <div className="page-layout">
      {props.children}
      <style jsx global>
        {globalStyles}
      </style>
    </div>
  );
}
 
export default Layout;

1 回限りのグローバルセレクター

<style jsx> を使用してコンポーネントに追加するスタイルは、そのコンポーネント内の要素のみに影響し、子コンポーネントには影響しません。

場合によっては、子コンポーネントの特定のスタイルをオーバーライドする必要がある場合があります。これを行うために、Styled JSX は :global() を提供し、*1 回限りのグローバルセレクター*へのアクセスを可能にします。

たとえば、クラス名 btn のボタンを含む <Widget> コンポーネントがあるとします。ウィジェットがホームページにインポートされた場合にのみ、このボタンの色を変更する場合は、次のように行うことができます。

pages/index.js
import Widget from '../components/Widget';
 
function Home() {
  return (
    <div className="container">
      <h1>Hello Next.js</h1>
      <Widget />
      <style jsx>{`
        .container {
          margin: 50px;
        }
        .container :global(.btn) {
          background: #000;
          color: #fff;
        }
      `}</style>
    </div>
  );
}
 
export default Home;

動的なスタイル

他のソリューションと比較して、コンポーネントのスタイルを props に基づいて調整できることは、CSS-in-JS ライブラリの大きな利点です。

Styled JSX では、次のように行うことができます。

components/Alert.js
function Alert(props) {
  return (
    <div className="alert">
      {props.children}
      <style jsx>{`
        .alert {
          display: inline-block;
          padding: 20px;
          border-radius: 8px;
        }
      `}</style>
      <style jsx>{`
        .alert {
          background: ${props.type == 'warning' ? '#fff3cd' : '#eee'};
        }
      `}</style>
    </div>
  );
}
 
export default Alert;

Alert コンポーネントに warning 値を持つ type prop が渡された場合

<Alert type="warning">This is an important message</Alert>

コンポーネントの背景はオレンジ色になります。type prop を指定しない場合、背景はデフォルトのグレー色に戻ります。

動的スタイルを別の <style jsx> タグに配置したことに注意してください。これは必須ではありませんが、props が変更されたときに動的な部分のみが再計算されるように、静的なスタイルと動的なスタイルを分割することを**お勧め**します。

props に基づいてスタイルを調整する別の方法は、以下に示すように、prop 値に基づいて異なるクラス名を適用することです。

components/Alert.js
function Alert(props) {
  return (
    <div className={props.type == 'warning' ? 'alert warning' : 'alert'}>
      {props.children}
      <style jsx>{`
        .alert {
          display: inline-block;
          padding: 20px;
          border-radius: 8px;
          background: #eee;
        }
        .alert.warning {
          background: #fff3cd;
        }
      `}</style>
    </div>
  );
}
 
export default Alert;

サイトテーマの作成

テーマは、アプリで必要になる可能性のある最も一般的な変数を含む単純なオブジェクトにすることができます。

styles/theme.js
const theme = {
  fontFamily: {
    sansSerif: '-apple-system, "Helvetica Neue", Arial, sans-serif',
    mono: 'Menlo, Monaco, monospace',
  },
  colors: {
    text: '#333',
    background: '#fff',
    link: '#1eaaf1',
    linkHover: '#0d8ecf',
    border: '#ddd',
    warning: '#fff3cd',
    success: '#d4edda',
  },
};
 
export default theme;

次に、このテーマファイルをコンポーネントにインポートし、*ハードコーディングされた*値を変数に置き換えます。

components/Layout.js
import theme from '../styles/theme';
 
function Layout(props) {
  return (
    <div className="page-wrapper">
      {props.children}
      <style jsx global>{`
        body {
          background: ${theme.colors.background};
          color: ${theme.colors.text};
          font-family: ${theme.fontFamily.sansSerif};
        }
      `}</style>
      <style jsx global>{`
        body {
          margin: 0;
          padding: 0;
          font-size: 18px;
          font-weight: 400;
          line-height: 1.8;
        }
        h1 {
          font-weight: 700;
        }
        p {
          margin-bottom: 10px;
        }
      `}</style>
    </div>
  );
}
 
export default Layout;
components/Alert.js
import theme from '../styles/theme';
 
function Alert(props) {
  return (
    <div className="alert">
      {props.children}
      <style jsx>{`
        .alert {
          display: inline-block;
          padding: 20px;
          border-radius: 8px;
        }
      `}</style>
      <style jsx>{`
        .alert {
          background: ${props.type == 'warning'
            ? theme.colors.warning
            : theme.colors.light};
        }
      `}</style>
    </div>
  );
}
 
export default Alert;

このブログ記事では、Styled JSX を使い始める方法について説明しました。追加機能の詳細については、GitHub でご確認ください