1. 程式人生 > 實用技巧 >react 樣式衝突解決方案 styled-components

react 樣式衝突解決方案 styled-components

前置

在 react 中解決元件樣式衝突的方案中,如果您喜歡將 css 與 js 分離,可能更習慣於 CSS-Modules;如果習慣了 Vue.js 那樣的單檔案元件,可能習慣於使用 styled-components 來解決這個問題。使用 CSS-Modules 從老專案遷移過來可能更容易。

安裝

npm i styled-components

基本用法

import React from 'react'
import { render } from 'react-dom'
import styled from 'styled-components'

const items = [
  {
    title: 'title1',
    type: 'primary',
    desc: 'Lorem ipsum dolor sit amet consectetur '
  },
  {
    title: 'title2',
    type: 'other',
    desc: 'Lorem ipsum dolor sit amet consectetur ',
  },
]

function App() {
  return (
    <div>
      {items.map(renderItem)}
    </div>
  )
}

function renderItem(item) {
    return (
      <Wrap>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Wrap>
    )
}

const Wrap = styled.div`
  margin: 10px auto 10px;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
  background: #eee
`

render(<App />, document.getElementById('app'))

實際渲染結果:


我們需要在 JavaScript 模板字串內部書寫 css 樣式,為了得到 css 語法高亮,可以使用 vscode 擴充套件。

巢狀

const Wrap = styled.div`
  margin: 10px auto;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
  background: #eee;

+ h2 {
+   color: red
+ }
`

props

function renderItem(item) {
    return (
+     <Wrap type={item.type} >
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Wrap>
    )
}

const Wrap = styled.div`
  margin: 10px auto;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
+ background: ${props => props.type === 'primary' ? '#202234' : '#eee'};
`

在模板括號中可使用任意 JavaScript 表示式,這裡使用箭頭函式,通過 props 接收引數。

繼承

使用繼承實現上文功能:

import React from 'react'
import { render } from 'react-dom'
import styled from 'styled-components'

const items = [...]

function App() {
  return (...)
}

function renderItem(item) {
+ const Container = item.type === 'primary' ? primaryContainer : OrdinaryContainer
    return (
+     <Container>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Container>
    )
}

const Wrap = styled.div`
  margin: 10px auto 10px;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
- background: ${props => props.type === 'primary' ? '#202234' : '#eee'};
`

+ const OrdinaryContainer = styled(Wrap)`
+   background: #eee;
+ `

+ const primaryContainer = styled(Wrap)` 
+   background: #202234;
+ `

render(<App />, document.getElementById('app'))

我們得到同樣的效果:

繼承的語法

// 如上所示,您應該這樣寫來實現繼承
const OrdinaryContainer = styled(Wrap)``
// 現在已經不支援extend關鍵字
const OrdinaryContainer = Wrap.extend``

下面給它們共同繼承的 Wrap 新增一個border:

const Wrap = styled.div`
  margin: 10px auto 10px;
  padding: 10px;
  width: 90%;
  border-radius: 5px;
+ border: 2px solid red;
`

將影響所有繼承過 Wrap 的樣式變數:

attrs

封裝一個文字輸入框元件 src/components/Input.jsx

import styled from "styled-components";

const Input = styled.input.attrs({
    type: 'text',
    padding: props => props.size || "0.5em",
    margin: props => props.size || "0.5em"
})`
 border: 2px solid #eee;
 color: #555;
 border-radius: 4px;
 margin: ${props=>props.margin};
 padding: ${props=>props.padding};
`

export default Input

使用

// ..
import Input from './components/Input'
 
function App() {
  return (
    <div>
      <Input></Input>
      <Input size="2em"></Input>
    </div>
  )
}

render(<App />, document.getElementById('app'))

createGlobalStyle

在 V4 版本已經將 injectGlobal 移除,使用 createGlobalStyle 代替它設定全域性樣式,

import React from 'react'
import { render } from 'react-dom'
import styled, {createGlobalStyle} from 'styled-components'

// ...

function App() {
  return (
    <div>
      <GlobalStyle></GlobalStyle>
      {items.map(renderItem)}
    </div>
  )
}

function renderItem(item) {
  const Container = item.type === 'primary' ? primaryContainer : OrdinaryContainer
    return (
      <Container>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Container>
    )
}

const GlobalStyle = createGlobalStyle`
  *{
    margin: 0;
    padding: 0;
   }
   body {
     min-height: 100%;
     background: #ffb3cc;
   }
`
// ...

render(<App />, document.getElementById('app'))

ThemeProvider

通過上下文 API 將主題注入元件樹中位於其下方任何位置的所有樣式元件中。

import React from 'react'
import { render } from 'react-dom'
import styled, {createGlobalStyle, ThemeProvider} from 'styled-components'
import Input from './components/Input'

const items = [...]

function App() {
  return (
    <div>
      <GlobalStyle></GlobalStyle>
      {items.map(renderItem)}
      <Input></Input>
      <Input size="2em"></Input>
    </div>
  )
}

function renderItem(item) {
  const Container = item.type === 'primary' ? primaryContainer : OrdinaryContainer
    return (
      <Container>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Container>
    )
}

// ...

const primaryContainer = styled(Wrap)` 
  background: ${props=>props.theme.primary};
`

const theme = {
  primary: '#202234'
}

render(<ThemeProvider theme={theme}><App /></ThemeProvider>, document.getElementById('app'))

我們需要先匯入 ThemeProvider,然後用標籤將 <App /> 包裹,需要注意的是必須為 ThemeProvider 標籤提供一個 theme 屬性,接下來在所有子元件中都可以通過 props.theme.xxx 獲取 theme 下的屬性。我們將 Input 元件的背景色同樣改為 theme 下的 primary

import styled from "styled-components";

const Input = styled.input.attrs({
    // ...
})`
 // ...
 background: ${props=>props.theme.primary};
`

export default Input

keyframes

使用 styled-components 時,無法直接在模板字串中建立 keyframes,需要先匯入 styled-components 下的 keframes 物件來建立它。下面看一個簡單的例項:

import React from 'react'
import styled, { keyframes } from 'styled-components'
import { render } from 'react-dom'

const items = [...]

function App() {
  return (
    <div>
      {items.map(renderItem)}
    </div>
  )
}

function renderItem(item) {
  const Container = item.type === 'primary' ? primaryContainer : OrdinaryContainer
    return (
      <Container>
        <h2>{item.title}</h2>
        <p>{item.desc}</p>
      </Container>
    )
}

// ...

const fadeIn = keyframes`
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
`

const Wrap = styled.div`
  // ...
  animation: 1.5s ${fadeIn} ease-out;
`
// ..

render(<App />, document.getElementById('app'))

到我重新重新整理頁面,就能看見下面的效果: