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,這些 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> 標籤。

若要為應用中的所有頁面應用全域樣式,一個好的方法是先創建一個包含全域樣式的布局元件,然後將所有頁面包裹其中。

使用布局元件提供了靈活性,可以為某些頁面應用特定樣式,同時仍允許其他頁面使用不同的樣式:

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 上查看