previously:css
在愈來愈組件化開發的今天,咱們經過JSX
已經將js好html/xml很好的柔和在了一塊兒,那麼css呢?html
雖然在vue這樣的框架裏咱們能在.vue
文件裏將css、js、html寫在一塊兒,但實際上它們的聯繫很弱,特別是js和css,它們徹底沒法溝通。vue
而styled-components
很好的解決了這個問題,經過它,咱們能讓整個css架構跟着組件走,而再也不僅僅是貌合神離的被放置在一個文件中。能夠這麼說,styled-components讓一個組件變得更加得完整,更加得像一個組件!react
styled-compnents
,正如其名,就是有樣式的react-component
,是對react組件的再封裝,它不只能夠往<Component/>
添加了固定的css樣式,還能夠經過組件的屬性讓css和一個組件緊密的聯繫起來。es6
除此以外它支持幾乎全部sass/less
等css預處理器具備的功能,嵌套、&
、變量、插值,甚至更增強大!typescript
咱們先來看一個基本使用栗子react-native
// 把一個React-Component包裝成Styled-Component
import React,{Component}from 'react';
import styled from 'styled-components';
class Xxx extends React.Component{
render(){
return (
<div className={this.props.className}>
container
<h2>title</h2>
<div>body</div>
</div>
)
}
}
const StyledComponent = styled(Xxx)`
&{
color:red;
h2{
color:blue;
}
div{
font-size:${props=>props.fontSize};
color:pink;
}
}
`;
export default StyledComponent;
複製代碼
styled()
是style-components
中最重要的方法,它能將一個React組件包裝成一個具備樣式的<StyleComponent/>
,而且它還會往本來的React組件中傳遞一個className
屬性,這個屬性的值是一串hash值(防止命名衝突),咱們須要將它放置到它應該被放置的元素身上。api
經過styled()
,咱們已經將一個React組件包裝成了一個Styled組件並導出,接下來咱們去渲染這個導出的組件sass
import React from 'react';
import ReactDOM from 'react-dom';
import StyledComponent from './test.js';
ReactDOM.render(
<StyledComponent fontSize='30px'/>
,window.root
)
複製代碼
渲染結果長這樣: bash
能夠發現,在使用上,一個StyledComponent和ReactComponent徹底木有區別,emmm,應該說仍是有一點的,咱們能過給一個組件傳遞屬性來控制該組件的css樣式,So,StyledComponent實際上是ReactComponent的超集。
嗯,是否是有那麼一點興趣了耶,接下來讓咱們一塊兒更加深刻的學習style-components
吧!
上慄中咱們已經知道了styled能幹什麼,這一回讓咱們來完整的分析下這個API。
首先它的格式是這樣的
const StyledCompoent = styled()``
複製代碼
它接收兩次傳參(這實際上是es6中的標籤函數的寫法,這裏再也不展開),並最終返回一個包裝後的React組件,即具備樣式的React組件。
()
能夠接收一個React-Component
也能夠接收一個tagName
。
上栗子中咱們演示了第一種狀況,So,其實它還能接收一個tagName,好比div
const StyledCompoent = styled('div')``
複製代碼
其實就至關於
let ReactComponent = (props,context)=><div className={props.className}></div>; //上慄中咱們說過當咱們調用styed()時,react組件中會自動傳入一個由hash組成的className屬性
const StyledCompoent = styled(ReactComponent)``
複製代碼
除此以外它還有一種快捷寫法
const StyledCompoent = styled.div``
複製代碼
嗯,除了上面兩種大狀況,還有一種狀況就是()
中也能夠接收一個StyledComponent,這種狀況大多出如今一個StyledComponent的樣式須要繼承自另一個StyledComponent時。
const StyledCompoent2 = styled(StyledCompoent1)`
color:'orange'
`
複製代碼
emmm...這貨怎麼翻譯?標籤模板字符串?帶有標籤的模板字面量?
無論啦~反正就是指括號(()
)後的 `` 裏的內容。
在通過styled()
後,咱們已經確保將一個有效的react組件初始化爲了styled組件,接下來咱們只須要往這個組件中添加樣式。
const StyledCompoent = styled.div`
/* all declarations will be prefixed */
//全部css樣式會自動添加兼容性前綴
padding: 2em 1em;
background: papayawhip;
/* pseudo selectors work as well */
//支持僞類選擇器
&:hover {
background: palevioletred;
}
/* media queries are no problem */
//支持媒體查詢
@media (max-width: 600px) {
background: tomato;
/* nested rules work as expected */
//支持嵌套
&:hover {
background: yellow;
}
}
> p {
/* descendant-selectors work as well, but are more of an escape hatch */
//支持後代選擇器
text-decoration: underline;
}
/* Contextual selectors work as well */
//支持環境選擇器
html.test & {
display: none;
}
`;
複製代碼
以上示例出自官方文檔,可見它無鴨梨支持:嵌套、前綴自動補全、各種選擇器、媒體查詢...
除此以外,Of Course,它也支持變量,而且有兩種可選
let color1 = 'orange';
const StyledCompoent = styled.div`
color:${color1} //支持接收js變量做爲css屬性值
,fontSize:${props=>props.fontSize}; //支持接收組件的props中的某個值來做爲css屬性值
`
//--- --- ---
// somewhere
...
<StyledComponent fontSize='30px'/>
...
複製代碼
其中的${}
被稱之爲interpolations
,嗯,插值表達式,應該叫這名?
須要注意的是${}中能夠放一個js變量,也能夠放一個函數,若是是函數,它會接受一個props
屬性(即React組件初始化時包裝而成的props對象)做爲參數。
哎嘿,還有種可能,${}也能接收一個css對象,like this
...
${{
position:'absolute'
,left:'100px'
,top:'100px'
}}
...
複製代碼
styled-components中也容許咱們使用像sass中@mixin同樣的東東
import React,{Component}from 'react';
import styled,{css} from 'styled-components';
class Xxx extends React.Component{
render(){
return (
<div className={this.props.className}>
container
<h2 className='title'>title</h2>
<div className='content'>body</div>
</div>
)
}
}
let mixin = css`
&{
color:red;
${{
position:'absolute'
,left:'100px'
,top:'100px'
}}
.title{
color:blue;
}
.content{
font-size:${props=>props.someCondition.fontSize};
color:pink;
}
}
`
const StyledComponent = styled(Xxx)`
${props=>props.someCondition?mixin:null}
`;
export default StyledComponent;
// --- --- ---
ReactDOM.render(
<StyledComponent someCondition={{fontSize:'30px'}}/>
,window.root
)
複製代碼
其中咱們用到了styled-components中的另一個方法css
,這個方法其實就是建立一個mixin
,使咱們能夠在任何<StyledComponent>
中複用這份樣式。
須要注意的是, props
屬性能夠透傳給mixin
使其在內部使用(要不咱們怎麼說這貨是一個mixin呢)
最終的渲染結果長這樣
經過上節中的styled()
方法能將一個react組件包裝成一個具備樣式的react組件,咱們將它稱之爲StyledComponent
,它除了在樣式上和組件強耦合外,還具備一些它獨有的特性。
前面咱們說過,咱們能經過styled(StyledCompoent1)
一個StyleComponent來建立一個繼承自StyledCompoent1的StyledComponent2組件。
let StyledCompoent2 = styled(StyledCompoent1)`
color:xxx
...
`
複製代碼
但這樣繼承內部實際上是一個工廠模式,StyledComponent2實際上是一個全新的class。
若是咱們想要作到真正的繼承,須要使用style-components提供的extend
方法,它是StyleComponent下的一個屬性方法。
let StyledCompoent2 =StyledCompoent1.extend`
color:xxx
...
`
複製代碼
withComponent一樣是StyleComponent下的一個屬性方法,它能幫助咱們將本來的Styled組件中的標籤給替換成另一種標籤
//會將本來的<button>替換成<a>
const Link = Button.withComponent('a');
複製代碼
[danger] 注意: 若本來的Styled組件是一個具備複合標籤的組件,那麼它的整個DOM都會被替換掉,這可能並非你所指望的結果。
styled-components容許咱們在tagged template literal
中使用一個StyledComponent變量做爲css選擇器,咱們將它稱之爲component-selector
。
注意: 依然須要手動定位className的起始位置
let ReactComponent = (props,context)=>{
<div className={props.className}>
<h2>hello</h2>
</div>
}
let StyledComponent1 = styled(ReactComponent)``
let StyledComponent2 = styled.div`
${StyledComponent1}{
background:orange;
h2{
color:red;
}
&:after{
content:'';
display:block;
width:10px;
height:10px;
border:1px solid black;
}
}
`
//--- --- ---
...
ReactDOM.render(
<StyledComponent2>
<StyledComponent1/>
</StyledComponent2>
,window.root
)
複製代碼
在styled-components中,咱們要想獲取到一個StyledComponent的真實入口DOM,須要使用innerRef而不是ref(做用和用法都是同樣的)。
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: palevioletred;
background: papayawhip;
border: none;
border-radius: 3px;
${{color:'red'}}
`;
export default class Form extends React.Component {
render() {
return (
<Input
placeholder="Hover here..."
innerRef={x => { this.input = x }}
onMouseEnter={() => this.input.focus()}
/>
);
}
}
複製代碼
上慄中使用的是styled.input
這種快捷建立styledComponent的方式,
若是咱們改爲使用styled(原生React組件)
的方式,那麼像上面那樣咱們是沒法獲取到dom的,獲取的是styled()
括號中傳入的原生React組件對象
class _B extends React.Component{
render(){
return <div></div>
}
}
const B = styled(_B)``;
export default class A extends React.Component{
componentDidMount(){
console.log('this.dom', this.dom);
}
render(){
return <B innerRef={x => this.dom = x}></B>;
}
}
複製代碼
解決辦法是在_B
內再使用原生的ref
掛載一次,把dom掛載在_B
上,這樣咱們就能夠經過往下再深一層訪問的方式拿到dom。
有些時候咱們須要判斷一個組件是否是StyledComponent,咱們纔好運用只有StyledComponent才具備的特性,好比component-selector
import React from 'react';
import styled, { isStyledComponent } from 'styled-components';
import MaybeStyledComponent from './somewhere-else';
let TargetedComponent =
isStyledComponent(MaybeStyledComponent)
? MaybeStyledComponent
: styled(MaybeStyledComponent)``;
const ParentComponent = styled.div`
color: cornflowerblue;
${TargetedComponent} {
color: tomato;
}
`
複製代碼
注意: isStyledComponent方法須要從styled-components中額外導入
attr方法接收一個對象,它容許咱們爲一個StyledComponent添加默認屬性和默認樣式值
此方法也是私認爲是styled-components中最爲重要的方法之一。
const Input = styled.input.attrs({
// 定義一些靜態屬性
type: 'password',
// 給css屬性動態賦予初始值
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};
`;
export default class xxx extends React.Component{
render(){
return (
<div>
<Input placholder='A small text input' size='1em'/>
<br/>
<Input placholder='A bigger text input' size='2em'/>
</div>
)
}
}
複製代碼
最終的渲染結果長這樣
經過styled-components爲咱們提供的ThemeProvider
組件(沒錯,是一個React組件),咱們能爲咱們的StyledComponent訂製主題。
import React from 'react';
import styled,{ThemeProvider} from 'styled-components';
// 定製主題
const theme = {
main:'mediumseagreen'
}
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};
`
export default class xxx extends React.Component{
render(){
return(
<div>
<Button>Normal</Button>
<ThemeProvider theme={theme}>
<Button>Themed</Button>
</ThemeProvider>
</div>
)
}
}
複製代碼
上慄中,咱們定製了一個theme
主題對象,並將這個對象傳遞給<ThemeProvider>
組件,這樣在被這個組件包裹的任何子組件中咱們就能獲取到這個theme
對象(不管嵌套多少層)。
在上慄中其實有一個bug,那就是沒有被<ThemeProvider>
包裹住的<Button/>
實際上是沒有props.theme屬性對象的,那麼它就會報錯。
So,這個時候咱們須要給這個Button組件設置一個默認值
...
// 設置默認屬性,
Button.defaultProps = {
theme:{
main:'palevioletred'
}
}
const theme = {
main:'mediumseagreen'
}
...
複製代碼
其實咱們除了在組件外部定義一個theme對象,並經過<ThemeProvider theme={theme}>
來傳遞外,咱們也能夠直接在一個StyledComponent上定義theme對象
...
const theme = {
main: 'mediumseagreen'
};
...
<ThemeProvider theme={theme}>
<div>
<Button>Themed</Button>
<Button theme={{ main: 'darkorange' }}>Overidden</Button>
</div>
</ThemeProvider>
...
複製代碼
當ThemeProvider
嵌套時,被嵌套的當ThemeProvider的theme屬性此時不只能夠接收一個對象也能夠接收一個函數,若是是個函數,那麼這個函數會接受到一個參數,這個參數則是上一級ThemeProvide接收到的theme對象。
...
const theme = {
fg:'palevioletred'
,bg:'white'
};
const invertTheme = ({fg,bg})=>({
fg:bg
,bg:fg
})
...
<ThemeProvider theme={theme}>
<div>
<ThemeProvider theme={invertTheme}>
<Button>Themed</Button>
</ThemeProvider>
</div>
</ThemeProvider>
...
複製代碼
若是你想要在React組件中獲取theme,styled-compnents也爲咱們提供了一個withTheme
的方法,通過它包裝後,咱們就能在一個React組件中獲取到props.theme
import { withTheme } from 'styled-components'
class MyComponent extends React.Component {
render() {
console.log('Current theme: ', this.props.theme);
// ...
}
}
export default withTheme(MyComponent)
複製代碼
首先它是styled-components額外提供的一個的方法。
import { injectGlobal } from 'styled-components';
injectGlobal`
@font-face {
font-family: 'Operator Mono';
src: url('../fonts/Operator-Mono.ttf');
}
body {
margin: 0;
}
`;
複製代碼
嗯,官方推薦你最好只在font-face和body方面使用它。
每每和interpolation
一塊兒使用
import styled, { keyframes } from 'styled-components';
const fadeIn = keyframes`
0% {
opacity: 0;
}
100% {
opacity: 1;
}
`;
const FadeInButton = styled.button`
animation: 1s ${fadeIn} ease-out;
`;
複製代碼
關於服務端渲染
關於TypeScript
關於ReactNative
若是有一個新的狀態傳入致使須要添加新的cssText,那麼會往style
標籤中追加cssText,
注意是往裏追加,並不會刪除style裏以前的cssText。(即便當前的props已經不知足以前css文本的生成條件也不會刪除)
參考