styled-components:一本通

  • 初體驗
  • 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方法:使用幀動畫
  • 其它

pre-notify

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方法:將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()}
      />
    );
  }
}
複製代碼

點擊查看官方示例

上慄中使用的是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>
    )
  }
}
複製代碼

點擊查看官方示例

上慄中,咱們定製了一個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>
...
複製代碼

點擊查看官方示例

在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;
`;
複製代碼

點擊查看官方示例

其它

關於服務端渲染

服務端渲染

關於TypeScript

如何在TypeScript中使用styled-components

關於ReactNative

在ReactNative中使用styled-components須要注意的事情

關於styledComponent的更新

若是有一個新的狀態傳入致使須要添加新的cssText,那麼會往style標籤中追加cssText,

注意是往裏追加,並不會刪除style裏以前的cssText。(即便當前的props已經不知足以前css文本的生成條件也不會刪除)


參考

相關文章
相關標籤/搜索