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'))
到我重新重新整理頁面,就能看見下面的效果: