它是經過JavaScript改變CSS編寫方式的解決方案之一,從根本上解決常規CSS編寫的一些弊端。
經過JavaScript來爲CSS賦能,咱們能達到常規CSS所很差處理的邏輯複雜、函數方法、複用、避免干擾。
儘管像SASS、LESS這種預處理語言添加了不少用用的特性,可是他們依舊沒有對改變CSS的混亂有太大的幫助。所以組織工做交給了像 BEM這樣的方法,雖然比較有用,可是它徹底是自選方案,不能被強制應用在語言或者工具層面。
他搭配React可能將模塊化走向一個更高的高度,樣式書寫將直接依附在JSX上面,HTML、CSS、JS三者再次內聚。
npm install --save styled-components
除了npm安裝使用模塊化加載包以外,也支持UMD
格式直接加載腳本文件。css
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
styled-components
使用標籤模板來對組件進行樣式化。html
它移除了組件和樣式之間的映射。這意味着,當你定義你的樣式時,你實際上創造了一個正常的React組件,你的樣式也附在它上面。react
這個例子建立了兩個簡單的組件,一個容器和一個標題,並附加了一些樣式。webpack
// 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規則會自動添加瀏覽器廠商前綴,咱們沒必要考慮它。
styled-components
會透傳全部的props屬性。git
// 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,能夠據此調整咱們的樣式規則。github
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
樣式化一下。web
// 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
能夠擴展樣式和替換標籤:typescript
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> );
咱們可使用attrs
API來爲樣式組件添加一些attr屬性,它們也能夠經過標籤模板插值函數拿到props傳值。npm
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附能,而且避免了名稱衝突。api
// 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> );
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> );
一般咱們在給一個非原生樣式組件添加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()} /> ); } }
由於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
。
若是咱們打算把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; }
媒體查詢是開發響應式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了,不是嗎?
標籤模板是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
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
的解決方案。
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} { } `;
在樣式組件中,咱們支持全部CSS加嵌套。由於咱們生成一個真實的stylesheet而不是內聯樣式,因此CSS中的任何工做都在樣式組件中工做!
(&)被咱們所生成的、惟一的類名替換給樣式組件,使其具備複雜的邏輯變得容易。
Jest Styled Components,基於jest,可對styled-components
作單元測試
使用stylelint 檢查咱們的styled-components
樣式書寫規範。
在模板文本中寫入CSS時丟失的一個東西是語法高亮顯示。咱們正在努力在全部編輯器中實現正確的語法高亮顯示。支持大部分編輯器包括Visual Studio Code、WebStorm。
下面簡單總結一下 styled-components 在開發中的表現:
固然,styled-components 還有一些優秀的特性,好比服務端渲染和 React Native 的支持。
若是你歷來沒看見過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 />