- 初體驗
- styled方法:將React組件包裝成Styled組件
- ()的三種情況
- tagged template literal
- interpolations 插值表達式
- mixin
- StyledComponent相關特性
- extend 繼承
- withComponent
- component-selector
- innerRef
- isStyledComponent
- attr方法:給Styled組件添加默認屬性
- 主題組件
- defaultProps
- 關于theme對象
- ThemeProvider嵌套與Function theme
- 在React組件中獲取theme
- injectGlobal方法:插入全局樣式
- keyframes方法:使用幀動畫
- 其它
[TOC]
## pre-notify
>previously:
- [JSX,了解一下?](https://juejin.im/post/5ab065716fb9a028c5230164)
- [React文檔精讀(上篇)](https://juejin.im/post/5ab20d67f265da237c68c949)
在越來越組件化開發的今天,我們通過`JSX`已經將js好html/xml很好的柔和在了一起,那么css呢?
雖然在vue這樣的框架里我們能在`.vue`文件里將css、js、html寫在一起,但實際上它們的聯系很弱,特別是js和css,它們完全無法溝通。
而`styled-components`很好的解決了這個問題,通過它,我們能讓整個css架構跟著組件走,而不再僅僅是貌合神離的被放置在一個文件中。可以這么說,styled-components讓一個組件變得更加得完整,更加得像一個組件!
## 初體驗
`styled-compnents`,正如其名,就是有樣式的`react-component`,是對react組件的再封裝,它不僅可以往`<Component/>`添加了固定的css樣式,還可以通過組件的屬性讓css和一個組件緊密的聯系起來。
除此之外它支持幾乎所有`sass/less`等css預處理器具有的功能,嵌套、`&`、變量、插值,甚至更加強大!
我們先來看一個基本使用栗子
```
// 把一個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值(防止命名沖突),我們需要**將它放置到它應該被放置的元素身上**。
通過`styled()`,我們已經將一個React組件包裝成了一個Styled組件并導出,接下來我們去渲染這個導出的組件
```
import React from 'react';
import ReactDOM from 'react-dom';
import StyledComponent from './test.js';
ReactDOM.render(
<StyledComponent fontSize='30px'/>
,window.root
)
```
渲染結果長這樣:

可以發現,在使用上,一個StyledComponent和ReactComponent完全木有區別,emmm,應該說還是有一點的,我們能過給一個組件傳遞屬性來控制該組件的css樣式,So,StyledComponent其實是ReactComponent的超集。
嗯,是不是有那么一點興趣了耶,接下來讓我們一起更加深入的學習`style-components`吧!
## styled方法:將React組件包裝成Styled組件
上栗中我們已經知道了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'
`
```
### tagged template literal
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;
}
`;
```
以上示例出自官方文檔,可見它無鴨梨支持:嵌套、**前綴自動補全**、各類選擇器、媒體查詢...
#### interpolations 插值表達式
除此之外,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'
}}
...
```
#### mixin
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呢)
最終的渲染結果長這樣

## StyledComponent相關特性
通過上節中的`styled()`方法能將一個react組件包裝成一個具有樣式的react組件,我們將它稱之為`StyledComponent`,它除了在樣式上和組件強耦合外,還具有一些它獨有的特性。
### extend 繼承
前面我們說過,我們能通過`styled(StyledCompoent1)`一個StyleComponent來創建一個繼承自StyledCompoent1的StyledComponent2組件。
```
let StyledCompoent2 = styled(StyledCompoent1)`
color:xxx
...
`
```
但這樣繼承內部其實是一個**工廠模式**,StyledComponent2其實是一個全新的class。
如果我們想要做到**真正的繼承**,需要使用style-components提供的`extend`方法,它是StyleComponent下的一個屬性方法。
```
let StyledCompoent2 =StyledCompoent1.extend`
color:xxx
...
`
```
### withComponent
withComponent同樣是StyleComponent下的一個屬性方法,它能幫助我們將原本的Styled組件中的標簽給替換成另外一種標簽
```
//會將原本的<button>替換成<a>
const Link = Button.withComponent('a');
```
>[danger] **注意:** 若原本的Styled組件是一個具有復合標簽的組件,那么它的整個DOM都會被替換掉,這可能并不是你所期望的結果。
### component-selector
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
)
```

### innerRef
在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()}
/>
);
}
}
```
[點我查看官方示例](https://www.styled-components.com/docs/advanced#refs)
上栗中使用的是`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>;
}
}
```

(獲取到的不是dom,而是styled包裹之前的組件對象)
解決辦法是在`_B`內再使用原生的`ref`掛載一次,把dom掛載在`_B`上,這樣我們就可以通過往下再深一層訪問的方式拿到dom。
### isStyledComponent
有些時候我們需要判斷一個組件是不是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方法:給Styled組件添加默認屬性
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>
)
}
}
```
[點我查看官方示例](https://www.styled-components.com/docs/advanced#theming)
上栗中,我們定制了一個`theme`主題對象,并將這個對象傳遞給`<ThemeProvider>`組件,這樣在被這個組件包裹的任何子組件中我們就能獲取到這個`theme`對象(無論嵌套多少層)。
### defaultProps
在上栗中其實有一個bug,那就是沒有被`<ThemeProvider>`包裹住的`<Button/>`其實是沒有props.theme屬性對象的,那么它就會報錯。
So,這個時候我們需要給這個Button組件設置一個默認值
```
...
// 設置默認屬性,
Button.defaultProps = {
theme:{
main:'palevioletred'
}
}
const theme = {
main:'mediumseagreen'
}
...
```
### 關于theme對象
其實我們除了在組件外部定義一個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嵌套與Function theme
當`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>
...
```
[點擊查看官方示例](https://www.styled-components.com/docs/advanced#function-themes)
### 在React組件中獲取theme
如果你想要在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)
```
## injectGlobal方法:插入全局樣式
首先它是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方面使用它。
## keyframes方法:使用幀動畫
往往和`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;
`;
```
[點我查看官方示例](https://www.styled-components.com/docs/basics#animations)
## 其它
### 關于服務端渲染
> [服務端渲染](https://www.styled-components.com/docs/advanced#server-side-rendering)
### 關于TypeScript
> [如何在TypeScript中使用styled-components](https://www.styled-components.com/docs/api#typescript)
### 關于ReactNative
> [在ReactNative中使用styled-components需要注意的事情](https://www.styled-components.com/docs/basics#react-native)
### 關于styledComponent的更新
如果有一個新的狀態傳入導致需要添加新的cssText,那么會往`style`標簽中追加cssText,
注意是往里追加,并不會刪除style里之前的cssText。(即使當前的props已經不滿足之前css文本的生成條件也不會刪除)

### 關于className
給一個styled-component直接添加一個`className`,那么這個className也會作為`props.className`中的一員,且作為第一個classname而存在(優先于 兩個有styled-component隨即生成的類名)

### 關于css屬性
如果你在一個組件的
```
styled`
width:2px;
`
```
中設置了一個css屬性,比如像上面這樣
然后,如果個組件的入口元素也是一個Styled組件,并且給這個組件設置一個同樣的css屬性
```
width:5px;
```
那么此時2px會生效而不是5px

因為css解析是按照類名的 從右往左解析
故若兩個類名之間存在同名屬性,取左邊的那個
---
參考
- [www.styled-components.com](https://www.styled-components.com/)
- 空白目錄
- 01.JSX,了解一下?
- JSX與虛擬DOM
- React
- 02.React文檔精讀(上)`
- React路由
- 關于BrowserRouter
- 關于Route
- 應用
- 權限認證
- case1
- context
- 新context
- 03.React路由
- 04.Diff
- 05.styled-components
- redux設計思想與API
- redux實現1
- 06.redux2
- 06.redux3
- 關于狀態初始化
- saga
- 新版
- 使用saga進行業務邏輯開發
- react-router-redux
- React性能優化
- immutable使用
- 未整理
- FAQ
- 常用中間件
- pureComponent
- 項目相關總結
- antd分尸
- 按需加載
- ReactWithoutJSX
- 我的組件庫
- C領域
- 用戶接口
- htmlType
- style
- show
- conjure
- grid
- inject
- stop
- 內部接口
- 衍生組件
- Button
- 報錯集錦
- ReactAPI
- 類上的那些屬性
- prop-types
- React.createElement
- React.cloneElement
- React.Children和props.children
- react元素和react組件關于作為children方面的那些問題
- react組件與虛擬dom
- ref