Styled-Components

原文鏈接:https://segmentfault.com/a/1190000014682665?utm_source=tag-newest

它是通過JavaScript改變CSS編寫方式的解決方案之一,從根本上解決常規CSS編寫的一些弊端。
通過JavaScript來爲CSS賦能,我們能達到常規CSS所不好處理的邏輯複雜、函數方法、複用、避免干擾。
儘管像SASS、LESS這種預處理語言添加了很多用用的特性,但是他們依舊沒有對改變CSS的混亂有太大的幫助。因此組織工作交給了像 BEM這樣的方法,雖然比較有用,但是它完全是自選方案,不能被強制應用在語言或者工具層面。
他搭配React可能將模塊化走向一個更高的高度,樣式書寫將直接依附在JSX上面,HTML、CSS、JS三者再次內聚。

基本

安裝

npm install --save styled-components

除了npm安裝使用模塊化加載包之外,也支持UMD格式直接加載腳本文件。

<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>

入門

styled-components使用標籤模板來對組件進行樣式化。

它移除了組件和樣式之間的映射。這意味着,當你定義你的樣式時,你實際上創造了一個正常的React組件,你的樣式也附在它上面。

這個例子創建了兩個簡單的組件,一個容器和一個標題,並附加了一些樣式。

// 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屬性。

// 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,可以據此調整我們的樣式規則。

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樣式化一下。

// 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可以擴展樣式和替換標籤:

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傳值。

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>
);

動畫

帶有@keyframesCSS animations,一般來說會產生複用。styled-components暴露了一個keyframesAPI,我們使用它產生一個可以複用的變量。這樣,我們在書寫css樣式的時候使用JavaScript的功能,爲CSS附能,並且避免了名稱衝突。

// 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節點插入到documenthead內。

注意的一個小地方:

// 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 CodeWebStorm

總結

下面簡單總結一下 styled-components 在開發中的表現:

  • 提出了 containercomponents 的概念,移除了組件和樣式之間的映射關係,符合關注度分離的模式;
  • 可以在樣式定義中直接引用到 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 />
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章