基礎

動機

styled-components 是我們思考如何增強 CSS 以用於樣式化 React 組件系統的結果。 通過專注於單一用例,我們設法為開發人員以及最終用戶的輸出優化了體驗。

除了改善開發人員的體驗外,styled-components 還提供

  • 自動關鍵 CSS:styled-components 會追蹤頁面上渲染的組件,並自動注入它們的樣式,而不會注入其他任何東西。結合程式碼分割,這意味著您的用戶只需載入必要的程式碼。
  • 無類別名稱錯誤:styled-components 會為您的樣式生成唯一的類別名稱。您永遠不必擔心重複、重疊或拼寫錯誤。
  • 更容易刪除 CSS:很難知道程式碼庫中某處是否使用了類別名稱。styled-components 使這一點變得顯而易見,因為每一個樣式都與特定的組件綁定在一起。如果組件未使用(工具可以檢測到)並被刪除,則其所有樣式都將隨之刪除。
  • 簡單的動態樣式:根據組件的 props 或全域主題調整組件的樣式既簡單又直觀,無需手動管理數十個類別。
  • 輕鬆維護:您永遠不必跨不同的檔案搜尋影響組件的樣式,因此無論程式碼庫有多大,維護都輕而易舉。
  • 自動供應商前綴:將您的 CSS 撰寫為目前的標準,讓 styled-components 處理其餘部分。

您可以在撰寫您熟悉和喜愛的 CSS 的同時獲得所有這些好處,只需將其綁定到個別組件即可。

安裝

安裝 styled-components 只需要一個指令,您就可以開始使用了

# with npm
npm install styled-components

# with yarn
yarn add styled-components

如果您使用像 yarn 這樣支援 "resolutions" package.json 欄位的套件管理器,我們也強烈建議您在其中加入一個對應主要版本範圍的項目。這有助於避免因專案中安裝了多個版本的 styled-components 而產生的所有問題。

package.json

{
  "resolutions": {
    "styled-components": "^5"
  }
}
注意事項

強烈建議(但非強制)也使用 Babel 插件。它提供了許多好處,例如更清晰的類別名稱、伺服器端渲染相容性、更小的捆綁包等等。

點擊此處查看其他 CDN 安裝說明

如果您沒有使用模組打包器或套件管理器,我們也在 unpkg CDN 上託管了一個全域("UMD")建置版本。只需將以下 <script> 標籤新增到 HTML 檔案的底部

<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>

新增 styled-components 後,您將可以存取全域 window.styled 變數。

const Component = window.styled.div`
  color: red;
`
注意事項

這種使用方法需要頁面上也包含 react CDN 捆綁包react-is CDN 捆綁包(在 styled-components 腳本之前)。

開始使用

styled-components 利用標記模板字面值來設定組件的樣式。

它消除了組件和樣式之間的映射。這意味著當您定義樣式時,實際上是在建立一個普通的 React 組件,並且您的樣式附加到該組件上。

此範例建立了兩個簡單的組件,一個包裝器和一個標題,並附加了一些樣式

Hello World!

這是一個線上編輯器,您可以修改程式碼來體驗使用 styled-components 的感覺!

注意事項

CSS 規則會自動添加供應商前綴,styled-components 會為您處理這一切!

Styled components 使用 stylis.js 套件來為 css 規則添加前綴。

有關 stylis.js 中支援的前綴的其他資訊,請造訪他們的 儲存庫

根據 props 調整樣式

您可以將函式(「插值」)傳遞給 styled component 的模板字面值,以根據其 props 調整樣式。

此按鈕組件具有一個可以更改其顏色的 primary 狀態。當將 $primary prop 設定為 true 時,我們將交換其背景和文字顏色。

擴展樣式

您可能經常想要使用一個組件,但針對單一情況略微更改它。現在,您可以傳入一個插值函式並根據某些 props 更改它們,但覆蓋樣式一次需要付出很大的努力。

要輕鬆建立一個繼承另一個組件樣式的新組件,只需將其包裝在 styled() 建構函式中即可。在這裡,我們使用上一節中的按鈕並建立一個特殊的按鈕,使用一些與顏色相關的樣式來擴展它

我們可以看到新的 TomatoButton 仍然與 Button 相似,而我們只添加了兩個新規則。

在某些情況下,您可能想要更改 styled component 渲染的標籤或組件。這在構建導覽列時很常見,例如,其中混合了錨點連結和按鈕,但它們的樣式應該相同。

針對這種情況,我們有一個逃生艙口。您可以使用 "as" 多態 prop 來動態交換接收您撰寫樣式的元素

這也適用於自定義組件!

為任何組件設定樣式

styled 方法完美適用於您自己的或任何第三方組件,只要它們將傳遞的 className prop 附加到 DOM 元素即可。

注意事項

如果您正在使用 react-native,請記住使用 style 而不是 className

注意事項

您也可以將標籤名稱傳遞到 styled() 工廠呼叫中,例如:styled("div")。事實上,styled.tagname 輔助函式只是執行相同操作的別名。

傳遞的 props

如果 styled 的目標是一個簡單的元素(例如 styled.div),styled-components 會將任何已知的 HTML 屬性傳遞給 DOM。如果它是一個自定義 React 組件(例如 styled(MyComponent)),styled-components 會傳遞所有 props。

此範例顯示如何將 Input 組件的所有 props 傳遞到已掛載的 DOM 節點,就像 React 元素一樣。

請注意 $inputColor prop 沒有傳遞給 DOM,但 typedefaultValue 有傳遞?styled 函式足夠聰明,可以自動為您過濾掉非標準屬性。

從 CSS 轉換過來

樣式化組件如何在組件中運作?

如果您熟悉將 CSS 導入組件(例如像 CSSModules),您應該習慣這樣做

import React from 'react';
import styles from './styles.css';


export default class Counter extends React.Component {
  state = { count: 0 };


  increment = () => this.setState({ count: this.state.count + 1 });
  decrement = () => this.setState({ count: this.state.count - 1 });


  render() {
    return (
      <div className={styles.counter}>
        <p className={styles.paragraph}>{this.state.count}</p>
        <button className={styles.button} onClick={this.increment}>
          +
        </button>
        <button className={styles.button} onClick={this.decrement}>
          -
        </button>
      </div>
    );
  }
}

因為樣式化組件是元素和樣式規則的組合,我們會像這樣編寫 Counter

import React from 'react';
import styled from 'styled-components';


const StyledCounter = styled.div`
  /* ... */
`;
const Paragraph = styled.p`
  /* ... */
`;
const Button = styled.button`
  /* ... */
`;


export default class Counter extends React.Component {
  state = { count: 0 };


  increment = () => this.setState({ count: this.state.count + 1 });
  decrement = () => this.setState({ count: this.state.count - 1 });


  render() {
    return (
      <StyledCounter>
        <Paragraph>{this.state.count}</Paragraph>
        <Button onClick={this.increment}>+</Button>
        <Button onClick={this.decrement}>-</Button>
      </StyledCounter>
    );
  }
}

請注意,我們在 StyledCounter 前面添加了「Styled」前綴,以便 React 組件 Counter 和樣式化組件 StyledCounter 不會名稱衝突,但在 React 開發者工具和網頁檢查器中仍然易於識別。

在 render 方法之外定義樣式化組件

將樣式化組件定義在 render 方法之外非常重要,否則它會在每次渲染過程中重新創建。在 render 方法中定義樣式化組件會阻礙緩存並大幅降低渲染速度,應避免這種做法。

以推薦的方式編寫您的樣式化組件

const StyledWrapper = styled.div`
  /* ... */
`;


const Wrapper = ({ message }) => {
  return <StyledWrapper>{message}</StyledWrapper>;
};

不要這樣寫

const Wrapper = ({ message }) => {
  // WARNING: THIS IS VERY VERY BAD AND SLOW, DO NOT DO THIS!!!
  const StyledWrapper = styled.div`
    /* ... */
  `;


  return <StyledWrapper>{message}</StyledWrapper>;
};

推薦閱讀Talia Marcassa樣式化組件:使用或不使用? 中撰寫了一篇關於實際使用的精彩評論,其中包含許多可靠的實務見解和與替代方案的比較。

偽元素、偽選擇器和嵌套

我們使用的預處理器 stylis 支援類似 scss 的語法,可自動嵌套樣式。

通過此預處理,樣式化組件支援一些進階選擇器模式

  • & 單個「&」符號指的是組件的所有實例;它用於應用廣泛的覆蓋

    Hello world!
    How ya doing?
    The sun is shining...
    Pretty nice day today.
    Don't you think?
    Splendid.
  • && 雙「&」符號指的是組件的一個實例;如果您正在執行條件樣式覆蓋,並且不希望樣式應用於特定組件的所有實例,這將非常有用

  • && 單獨的雙「&」符號具有一種稱為「優先級提升」的特殊行為;如果您正在處理混合樣式化組件和原生 CSS 的環境,其中可能存在樣式衝突,這將非常有用

    I'm blue, da ba dee da ba daa

如果您在沒有「&」符號的情況下放入選擇器,它們將引用組件的子項。

附加額外的 props
v2

為了避免不必要的包裝器僅將某些屬性傳遞給渲染的組件或元素,您可以使用 .attrs 建構函式。它允許您將額外的屬性附加到組件。

例如,您可以將靜態屬性附加到元素,或將第三方屬性(如 activeClassName)傳遞給 React Router 的 Link 組件。此外,您還可以將更多動態屬性附加到組件。.attrs 物件也接受函數,這些函數接收組件接收的屬性。返回值也將合併到結果屬性中。

這裡我們渲染一個 Input 組件,並將一些動態和靜態屬性附加到它


如您所見,我們可以在插值中訪問我們新創建的屬性,並且 type 屬性會向下傳遞給元素。

覆蓋 .attrs

請注意,在包裝樣式化組件時,.attrs 會從最內部的樣式化組件應用到最外層的樣式化組件。

這允許每個包裝器覆蓋嵌套的 .attrs 使用,類似於樣式表中稍後定義的 CSS 屬性如何覆蓋先前的聲明。

Input.attrs 會先套用,然後是 PasswordInput.attrs


這就是為什麼 PasswordInput 的類型是 'password',但仍然使用 Input$size 屬性。

動畫

使用 @keyframes 的 CSS 動畫不會限定在單個組件的範圍內,但您仍然不希望它們是全局的,以避免名稱衝突。這就是為什麼我們導出一個 keyframes 輔助函數,它將生成一個您可以在整個應用程序中使用的唯一實例

< 💅🏾 >
注意事項

react-native 不支援 Keyframes。請改用 ReactNative.Animated API

Keyframes 在使用時會延遲注入,這就是它們可以進行程式碼分割的方式,因此您必須使用 css 輔助函數 來處理共用的樣式片段

const rotate = keyframes``


// ❌ This will throw an error!
const styles = `
  animation: ${rotate} 2s linear infinite;
`


// ✅ This will work as intended
const styles = css`
  animation: ${rotate} 2s linear infinite;
`
注意事項

這在 v3 及更低版本中可以運作,在這些版本中我們沒有對 keyframes 進行程式碼分割。如果您從 v3 升級,請確保所有共用的樣式片段都使用 css 輔助函數!

React Native

樣式化組件可以與 React Native 以相同的方式和相同的導入方法一起使用。使用 Expo 的 Snack 嘗試此範例。

import React from 'react'
import styled from 'styled-components/native'


const StyledView = styled.View`
  background-color: papayawhip;
`


const StyledText = styled.Text`
  color: #BF4F74;
`


class MyReactNativeComponent extends React.Component {
  render() {
    return (
      <StyledView>
        <StyledText>Hello World!</StyledText>
      </StyledView>
    )
  }
}

由於 css-to-react-native,我們也支援更複雜的樣式(例如 transform),這通常是一個陣列,以及速記(例如 margin)!

注意事項

請注意,flex 屬性與 CSS 速記類似,而不是 React Native 中的舊版 flex 屬性。設定 flex: 1 會將 flexShrink 設定為 1,此外還會將 flexGrow 設定為 1,並將 flexBasis 設定為 0

想像一下您如何在 React Native 中編寫屬性,猜猜您如何將其轉換為 CSS,您可能是對的

const RotatedBox = styled.View`
  transform: rotate(90deg);
  text-shadow-offset: 10px 5px;
  font-variant: small-caps;
  margin: 5px 7px 2px;
`

與網頁版本的一些差異是,您不能使用 keyframescreateGlobalStyle 輔助函數,因為 React Native 不支援 keyframes 或全局樣式。如果您使用媒體查詢或嵌套 CSS,我們也會發出警告。

注意事項

在 v2 中,我們支援百分比。為了實現這一點,我們需要對所有速記強制執行單位。如果您要遷移到 v2,可以使用程式碼修改工具

使用 Metro Bundler 更簡潔

如果您只想導入 styled-components 而不是 styled-components/native,您可以添加一個包含 "react-native"resolverMainFields 設定。這在 Metro 中預設支援(目前在 Haul 中有效),但在某些時候似乎已被移除。

繼續閱讀下一頁

進階