2019年3月28日木曜日
Styled JSX による Next.js のスタイリング
投稿者Styled JSX は、コンポーネントのスタイリングをカプセル化してスコープできる CSS-in-JS ライブラリです。あるコンポーネントに導入したスタイルは他のコンポーネントに影響を与えず、意図しない副作用を心配することなく、スタイルの追加、変更、削除が可能です。
はじめに
Next.js にはデフォルトで Styled JSX が含まれているため、既存の React 要素に <style jsx> タグを追加し、その中に CSS を記述するだけで簡単に始めることができます。
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 属性を追加するだけで、標準的な CSS を記述でき、自動的にプレフィックスが付けられ、コンポーネントに自動的にスコープされます。<style jsx> 要素は、コンポーネントのルート要素内に配置する必要があります。
グローバルスタイルの追加
ほとんどのプロジェクトでは、body 要素のスタイリングや CSS リセットを提供するために、いくつかのグローバルスタイルが必要です。Styled JSX では、<style jsx global> を使用してグローバルスタイルを追加できます。例えば、
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 のフォントサイズを適用します。
アプリのすべてのページにグローバルスタイルを適用するには、まずグローバルスタイルを持つレイアウトコンポーネントを作成し、すべてのページをそれでラップするのが良いアプローチです。
レイアウトコンポーネントを使用することで、一部のページに特定のスタイルセットを適用しながら、他のページに異なるスタイルを適用できる柔軟性が得られます。
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 コンポーネントをインポートし、それをレンダリングメソッドに追加することで、すべてのページでレイアウトを一度にロードできます。
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 コンポーネントのグローバルスタイルを別のファイルに移動させることができます。
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 コンポーネントにインポートします。
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> コンポーネントがあるとします。ウィジェットがホームページにインポートされたときにのみこのボタンの色を変更したい場合、次のように行うことができます。
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;動的なスタイル
他のソリューションと比較して、コンポーネントのスタイリングをプロップに基づいて適応させることができることは、CSS-in-JS ライブラリの大きな利点です。
Styled JSX では、次のように行うことができます。
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 プロップが渡された場合、例えば
<Alert type="warning">This is an important message</Alert>コンポーネントはオレンジ色の背景になります。type プロップを指定しない場合、背景はデフォルトのグレー色にフォールバックします。
動的なスタイルは別の <style jsx> タグに配置しました。これは必須ではありませんが、静的スタイルと動的スタイルを分割することが推奨されており、プロップが変更されたときに動的な部分のみが再計算されます。
プロップに基づいてスタイルを適応させるための別のアプローチは、プロップ値に基づいて異なるクラス名を適用することです。
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;サイトテーマの作成
テーマは、アプリで必要になる可能性のある最も一般的な変数をすべて含んだ単純なオブジェクトにすることができます。
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;次に、このテーマファイルをコンポーネントにインポートし、ハードコードされた値を変数に置き換えます。
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;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 で確認してください。