Styled JSXを使ったNext.jsのスタイリング
2019年3月28日 木曜日に投稿
Styled JSXは、コンポーネントをスタイリングするためのカプセル化されたスコープ付きCSSを記述できるCSS-in-JSライブラリです。1つのコンポーネントに導入したスタイルは他のコンポーネントに影響を与えないため、意図しない副作用を心配することなくスタイルの追加、変更、削除を行うことができます。
はじめに
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要素と段落のスタイルを含めています。汎用的なセレクターを使用していますが、これらのスタイルは他のコンポーネントのcontainerクラス名を持つ要素や<p>タグには影響しません。これは、Styled JSXがスタイルをこのコンポーネントのみにスコープすることを保証しているためです(スタイル付き要素に追加の一意なクラス名を適用することで)。
<style>要素に単一のjsx属性を追加するだけで、自動プレフィックスが付与され、コンポーネントに自動的にスコープされる標準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;
一回限りのグローバルセレクター
<style jsx>を使用してコンポーネントに追加するスタイルは、そのコンポーネント内の要素のみに影響し、子コンポーネントには影響しません。時には、子コンポーネントの特定のスタイルをオーバーライドする必要がある場合があります。これを行うために、Styled JSXは:global()を提供し、一回限りのグローバルセレクターへのアクセスを提供します。
例えば、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で確認してください。