BackНазад к блогу

Стилизация в Next.js с помощью Styled JSX

Styled JSX — это библиотека CSS-in-JS, позволяющая писать инкапсулированные и изолированные стили для компонентов. В этой статье вы узнаете, как начать использовать Styled JSX в Next.js.

Styled JSX — это библиотека CSS-in-JS, которая позволяет писать инкапсулированные и изолированные стили для компонентов. Стили, определённые для одного компонента, не влияют на другие компоненты, что позволяет добавлять, изменять и удалять стили без риска возникновения непредвиденных побочных эффектов.

Начало работы

Next.js включает Styled JSX по умолчанию, поэтому для начала работы достаточно добавить тег <style jsx> в существующий React-элемент и написать внутри него 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 обеспечивает изоляцию стилей только для данного компонента (добавляя уникальные классы к стилизованным элементам).

Добавив всего один атрибут jsx к элементу <style>, вы можете писать стандартный 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;

Это применит размер шрифта 20px ко всем тегам <p> на этой странице.

Чтобы применить глобальные стили ко всем страницам приложения, хорошей практикой является создание компонента макета (Layout) с глобальными стилями и оборачивание всех страниц в него.

Использование компонента макета обеспечивает гибкость: можно применять определённые стили к некоторым страницам, сохраняя возможность использовать другие стили для остальных:

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 мы можем загрузить макет один раз для всех страниц, создав кастомный компонент App в pages/_app.js, импортировав компонент 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(), дающий доступ к единичным глобальным селекторам.

Например, предположим, что у нас есть компонент <Widget>, содержащий кнопку с классом btn. Если мы хотим изменить цвет этой кнопки только при импорте виджета на главной странице, мы можем сделать это так:

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;

Динамические стили

По сравнению с другими решениями, возможность адаптации стилей компонента на основе его пропсов — это большое преимущество библиотек 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 передаётся проп type со значением warning, например:

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

то компонент получит оранжевый фон. Без указания пропа type фон останется серым по умолчанию.

Обратите внимание, что мы поместили динамический стиль в отдельный тег <style jsx>. Это не обязательно, но рекомендуется разделять статические и динамические стили, чтобы при изменении пропсов пересчитывались только динамические части.

Альтернативный подход к адаптации стилей на основе пропсов — применение разных классов в зависимости от значения пропа:

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.