Back返回博客

使用 Styled JSX 为 Next.js 添加样式

Styled JSX 是一个 CSS-in-JS 库,允许您编写封装且作用域限定的 CSS 来为组件添加样式。本文将帮助您开始在 Next.js 中使用 Styled JSX。

Styled JSX 是一个 CSS-in-JS 库,允许您编写封装且作用域限定的 CSS 来为组件添加样式。您为一个组件引入的样式不会影响其他组件,从而可以放心地添加、更改和删除样式,而不必担心产生意外的副作用。

快速开始

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 属性,您就可以编写标准的 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 组件,然后将其添加到渲染方法中,从而为所有页面一次性加载布局:

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;

动态样式

与其他解决方案相比,能够根据组件的 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 组件传递了一个值为 warningtype 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 上的文档