它是通過JavaScript改變CSS編寫方式的解決方案之一,從根本上解決常規CSS編寫的一些弊端。
通過JavaScript來爲CSS賦能,我們能達到常規CSS所不好處理的邏輯複雜、函數方法、複用、避免干擾。
儘管像SASS、LESS這種預處理語言添加了很多用用的特性,但是他們依舊沒有對改變CSS的混亂有太大的幫助。因此組織工作交給了像 BEM這樣的方法,雖然比較有用,但是它完全是自選方案,不能被強制應用在語言或者工具層面。
他搭配React可能將模塊化走向一個更高的高度,樣式書寫將直接依附在JSX上面,HTML、CSS、JS三者再次內聚。
基本
安裝
npm install --save styled-components
除了npm安裝使用模塊化加載包之外,也支持UMD格式直接加載腳本文件。
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
入門
styled-components
使用標籤模板來對組件進行樣式化。
它移除了組件和樣式之間的映射。這意味着,當你定義你的樣式時,你實際上創造了一個正常的React組件,你的樣式也附在它上面。
這個例子創建了兩個簡單的組件,一個容器和一個標題,並附加了一些樣式。
// Create a Title component that'll render an <h1> tag with some styles
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
// Create a Wrapper component that'll render a <section> tag with some styles
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
// Use Title and Wrapper like any other React component – except they're styled!
render(
<Wrapper>
<Title>
Hello World, this is my first styled component!
</Title>
</Wrapper>
);
注意
CSS規則會自動添加瀏覽器廠商前綴,我們不必考慮它。
透傳props
styled-components
會透傳所有的props
屬性。
// Create an Input component that'll render an <input> tag with some styles
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: palevioletred;
background: papayawhip;
border: none;
border-radius: 3px;
`;
// Render a styled text input with a placeholder of "@mxstbr", and one with a value of "@geelen"
render(
<div>
<Input placeholder="@mxstbr" type="text" />
<Input value="@geelen" type="text" />
</div>
);
基於props做樣式判斷
模板標籤的函數插值能拿到樣式組件的props,可以據此調整我們的樣式規則。
const Button = styled.button`
/* Adapt the colours based on primary prop */
background: ${props => props.primary ? 'palevioletred' : 'white'};
color: ${props => props.primary ? 'white' : 'palevioletred'};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
render(
<div>
<Button>Normal</Button>
<Button primary>Primary</Button>
</div>
);
樣式化任意組件
// This could be react-router's Link for example
const Link = ({ className, children }) => (
<a className={className}>
{children}
</a>
)
const StyledLink = styled(Link)`
color: palevioletred;
font-weight: bold;
`;
render(
<div>
<Link>Unstyled, boring Link</Link>
<br />
<StyledLink>Styled, exciting Link</StyledLink>
</div>
);
擴展樣式
我們有時候需要在我們的樣式組件上做一點擴展,添加一些額外的樣式:
需要注意的是.extend
在對樣式組件有效,如果是其他的React
組件,需要用styled
樣式化一下。
// The Button from the last section without the interpolations
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// We're extending Button with some extra styles
const TomatoButton = Button.extend`
color: tomato;
border-color: tomato;
`;
render(
<div>
<Button>Normal Button</Button>
<TomatoButton>Tomato Button</TomatoButton>
</div>
);
在極少特殊情況下,我們可能需要更改樣式組件的標籤類型。我們有一個特別的API,withComponent
可以擴展樣式和替換標籤:
const Button = styled.button`
display: inline-block;
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// We're replacing the <button> tag with an <a> tag, but reuse all the same styles
const Link = Button.withComponent('a')
// Use .withComponent together with .extend to both change the tag and use additional styles
const TomatoLink = Link.extend`
color: tomato;
border-color: tomato;
`;
render(
<div>
<Button>Normal Button</Button>
<Link>Normal Link</Link>
<TomatoLink>Tomato Link</TomatoLink>
</div>
);
添加attr
我們可以使用attrsAPI
來爲樣式組件添加一些attr
屬性,它們也可以通過標籤模板插值函數拿到props
傳值。
const Input = styled.input.attrs({
// we can define static props
type: 'password',
// or we can define dynamic ones
margin: props => props.size || '1em',
padding: props => props.size || '1em'
})`
color: palevioletred;
font-size: 1em;
border: 2px solid palevioletred;
border-radius: 3px;
/* here we use the dynamically computed props */
margin: ${props => props.margin};
padding: ${props => props.padding};
`;
render(
<div>
<Input placeholder="A small text input" size="1em" />
<br />
<Input placeholder="A bigger text input" size="2em" />
</div>
);
動畫
帶有@keyframes
的CSS animations
,一般來說會產生複用。styled-components
暴露了一個keyframes
的API
,我們使用它產生一個可以複用的變量。這樣,我們在書寫css
樣式的時候使用JavaScript
的功能,爲CSS
附能,並且避免了名稱衝突。
// keyframes returns a unique name based on a hash of the contents of the keyframes
const rotate360 = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
// Here we create a component that will rotate everything we pass in over two seconds
const Rotate = styled.div`
display: inline-block;
animation: ${rotate360} 2s linear infinite;
padding: 2rem 1rem;
font-size: 1.2rem;
`;
render(
<Rotate>< 💅 ></Rotate>
);
支持 React Native
高級特性
Theming
styled-components
暴露了一個<ThemeProvider>
容器組件,提供了設置默認主題樣式的功能,他類似於react-rudux
的頂層組件Provider
,通過context
實現了從頂層到底層所有樣式組件的默認主題共用。
const Button = styled.button`
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border-radius: 3px;
/* Color the border and text with theme.main */
color: ${props => props.theme.main};
border: 2px solid ${props => props.theme.main};
`;
Button.defaultProps = {
theme: {
main: 'palevioletred'
}
}
// Define what props.theme will look like
const theme = {
main: 'mediumseagreen'
};
render(
<div>
<Button>Normal</Button>
<ThemeProvider theme={theme}>
<Button>Themed</Button>
</ThemeProvider>
</div>
);
Refs
通常我們在給一個非原生樣式組件添加ref
屬性的時候,其指向都是該組件實例的索引,我們通過用innerRef
可以直接拿到裏面的DOM
節點。
const AutoFocusInput = styled.input`
background: papayawhip;
border: none;
`;
class Form extends React.Component {
render() {
return (
<AutoFocusInput
placeholder="Hover here..."
innerRef={x => { this.input = x }}
onMouseEnter={() => this.input.focus()}
/>
);
}
}
Security
因爲styled-components
允許我們使用任意輸入作爲CSS
屬性值,一旦意識到這一點,我們馬上明白要對輸入做安全性校驗了,因爲使用用戶外部的輸入樣式可以導致用戶的瀏覽器被CSS
注入攻擊。CSS
注入攻擊可能不明顯,但是我們還是得小心一點,某些IE
瀏覽器版本甚至允許在URL
聲明中執行任意的JS
。
這個例子告訴我們外部的輸入甚至可能在CSS內調用一個API網絡請求。
// Oh no! The user has given us a bad URL!
const userInput = '/api/withdraw-funds';
const ArbitraryComponent = styled.div`
background: url(${userInput});
/* More styles here... */
`;
CSS.escape 這個未來API標準可淨化JS中的CSS的問題。但是瀏覽器兼容性目前還不是太好,所以我們建議在項目中使用 polyfill by Mathias Bynens。
CSS共存
如果我們打算把styled-components
和現有的css
共存的話,我們需要注意兩個實現的細節問題:
styled-components
也會生成真實的樣式表,並通過className
屬性鏈接生成的樣式表內容。在JS
運行時,他會生成一份真實的style
節點插入到document
的head
內。
注意的一個小地方:
// MyComponent.js
const MyComponent = styled.div`background-color: green;`;
// my-component.css
.red-bg {
background-color: red;
}
// For some reason this component still has a green background,
// even though you're trying to override it with the "red-bg" class!
<MyComponent className="red-bg" />
我們styled-components
生成的style
樣式表一般是在head頭部的最底下,同等CSS
優先級條件下是會覆蓋默認前者css
文件的樣式的。這個插入順序使用webpack
來調整是比較難得。所以,我們一般都這樣通過調整css
優先級來改變顯示:
/* my-component.css */
.red-bg.red-bg {
background-color: red;
}
Media Templates
媒體查詢是開發響應式web
應用不可或缺的存在,這是一個簡單的例子:
const Content = styled.div`
background: papayawhip;
height: 3em;
width: 3em;
@media (max-width: 700px) {
background: palevioletred;
}
`;
render(
<Content />
);
因爲媒體查詢語句很長,並且經常在整個應用程序中重複使用,所以爲此創建一些模板來複用是很有必要的。
使用JS的功能特性,我們可以輕鬆定義一份可配置的語句,包裝媒體查詢和樣式。
const sizes = {
desktop: 992,
tablet: 768,
phone: 376
}
// Iterate through the sizes and create a media template
const media = Object.keys(sizes).reduce((acc, label) => {
acc[label] = (...args) => css`
@media (max-width: ${sizes[label] / 16}em) {
${css(...args)}
}
`
return acc
}, {})
const Content = styled.div`
height: 3em;
width: 3em;
background: papayawhip;
/* Now we have our methods on media and can use them instead of raw queries */
${media.desktop`background: dodgerblue;`}
${media.tablet`background: mediumseagreen;`}
${media.phone`background: palevioletred;`}
`;
render(
<Content />
);
這太cool了,不是嗎?
Tagged Template Literals
標籤模板是ES6
的一個新特性,這是我們styled-components
創建樣式組件的方式和規則。
const aVar = 'good';
// These are equivalent:
fn`this is a ${aVar} day`;
fn([ 'this is a ', ' day' ], aVar);
這看起來有點麻煩,但是這意味着我們可以在styled-components
生成樣式組件中接受變量、函數、minxins,並將其變爲純css
。
這篇文章可以瞭解更多:The magic behind 💅 styled-components
Server Side Rendering
styled-components
很好地支持SSR
。
一個例子:
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'
const sheet = new ServerStyleSheet()
const html = renderToString(sheet.collectStyles(<YourApp />))
const styleTags = sheet.getStyleTags() // or sheet.getStyleElement()
也可以這樣組件化包裹,只要在客戶端不這麼使用:
import { renderToString } from 'react-dom/server'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
const sheet = new ServerStyleSheet()
const html = renderToString(
<StyleSheetManager sheet={sheet.instance}>
<YourApp />
</StyleSheetManager>
)
const styleTags = sheet.getStyleTags() // or sheet.getStyleElement()
sheet.getStyleTags()
返回一個style
標籤數組。具體styled-components
關於SSR
更深入的操作,不在這裏繼續討論了,還可以告知他兼容Next.js
關於SSR
的解決方案。
Referring to other components
styled-components
提供了component selector
組件選擇器模式來代替我們以往對class
名的依賴,解決得很乾淨。這下我們不必爲命名和選擇器衝突而苦惱了。
const Link = styled.a`
display: flex;
align-items: center;
padding: 5px 10px;
background: papayawhip;
color: palevioletred;
`;
const Icon = styled.svg`
transition: fill 0.25s;
width: 48px;
height: 48px;
${Link}:hover & {
fill: rebeccapurple;
}
`;
const Label = styled.span`
display: flex;
align-items: center;
line-height: 1.2;
&::before {
content: '◀';
margin: 0 10px;
}
`;
render(
<Link href="#">
<Icon viewBox="0 0 20 20">
<path d="M10 15h8c1 0 2-1 2-2V3c0-1-1-2-2-2H2C1 1 0 2 0 3v10c0 1 1 2 2 2h4v4l4-4zM5 7h2v2H5V7zm4 0h2v2H9V7zm4 0h2v2h-2V7z"/>
</Icon>
<Label>Hovering my parent changes my style!</Label>
</Link>
);
注意:
class A extends React.Component {
render() {
return <div />;
}
}
const B = styled.div`
${A} {
}
`;
這個例子是不可以的,因爲A
繼承ReactComponent
,不是被styled
構造過的。我們的組件選擇器只支持在Styled Components
創建的樣式組件。
class A extends React.Component {
render() {
return <div className={this.props.className} />;
}
}
const StyledA = styled(A)``;
const B = styled.div`
${StyledA} {
}
`;
API文檔
基本
styled
.attrs
- ``字符模板
ThemeProvider
助手
- css
- keyframes
- injectGlobal
- isStyledComponent
- withTheme
支持CSS
在樣式組件中,我們支持所有CSS
加嵌套。因爲我們生成一個真實的stylesheet
而不是內聯樣式,所以CSS
中的任何工作都在樣式組件中工作!
(&)被我們所生成的、唯一的類名替換給樣式組件,使其具有複雜的邏輯變得容易。
支持flow和typescript
更多工具
Babel Plugin
Test Utilities
Jest Styled Components,基於jest
,可對styled-components
做單元測試
Stylelint
使用stylelint
檢查我們的styled-components
樣式書寫規範。
Styled Theming 語法高亮顯示
在模板文本中寫入CSS
時丟失的一個東西是語法高亮顯示。我們正在努力在所有編輯器中實現正確的語法高亮顯示。支持大部分編輯器包括Visual Studio Code
、WebStorm
。
總結
下面簡單總結一下 styled-components
在開發中的表現:
- 提出了
container
和components
的概念,移除了組件和樣式之間的映射關係,符合關注度分離的模式; - 可以在樣式定義中直接引用到 js 變量,共享變量,非常便利,利用js的特性爲
css
附能,帥斃了! - 支持組件之間繼承,方便代碼複用,提升可維護性;
- 兼容現有的
className
方式,升級無痛; - 這下寫
CSS
也樂趣十足了。 styled-components
的最基本思想就是通過移除樣式和組件之間的映射來執行最佳實踐- 一個讓
styled-components
很容易被接受的特性:當他被懷疑的時候,你同樣可以使用你熟悉的方法去使用它!
當然,styled-components
還有一些優秀的特性,比如服務端渲染和 React Native
的支持。
題外:styled-components
的魔法
如果你從來沒看見過styled-components,下面是一個簡單的樣式組件的例子:
const Button = styled.button`
background-color: papayawhip;
border-radius: 3px;
color: palevioletred;
`
現在可以像使用普通React
組件一樣渲染使用。
<Button>Hi Dad!</Button>
那麼,這是怎麼工作的呢?這個過程中到底發生了什麼魔法?
標籤模板
// 實際上, style.button`` 是 JavaScript 的新語法特性,屬於 ES6 的標籤模板功能。
// 本質上, styled.button`` 和 styled.button() 是一樣的。他們的差異只在傳遞參數時就變得可見了。
// styled-components 利用模板字符串的用處在於可以給內部 props 賦值。
const Button = styled.button`
font-size: ${props => props.primary ? '2em' : '1em'};
`
// font-size: 2em;
<Button primary />