Styled-Components

Styled-Components

它是經過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規則會自動添加瀏覽器廠商前綴,咱們沒必要考慮它。

透傳props

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作樣式判斷

模板標籤的函數插值能拿到樣式組件的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>
);

添加attr

咱們可使用attrsAPI來爲樣式組件添加一些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>&lt; 💅 &gt;</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作單元測試

demo

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 />
本站公眾號
   歡迎關注本站公眾號,獲取更多信息