它是经过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规则会自动添加浏览器厂商前缀,咱们没必要考虑它。
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,能够据此调整咱们的样式规则。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> );
咱们可使用attrs
API来为样式组件添加一些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>< 💅 ></Rotate> );
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> );
一般咱们在给一个非原生样式组件添加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()} /> ); } }
由于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
。
若是咱们打算把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; }
媒体查询是开发响应式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了,不是吗?
标签模板是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
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
的解决方案。
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} { } `;
在样式组件中,咱们支持全部CSS加嵌套。由于咱们生成一个真实的stylesheet而不是内联样式,因此CSS中的任何工做都在样式组件中工做!
(&)被咱们所生成的、惟一的类名替换给样式组件,使其具备复杂的逻辑变得容易。
Jest Styled Components,基于jest,可对styled-components
作单元测试
使用stylelint 检查咱们的styled-components
样式书写规范。
在模板文本中写入CSS时丢失的一个东西是语法高亮显示。咱们正在努力在全部编辑器中实现正确的语法高亮显示。支持大部分编辑器包括Visual Studio Code、WebStorm。
下面简单总结一下 styled-components 在开发中的表现:
固然,styled-components 还有一些优秀的特性,好比服务端渲染和 React Native 的支持。
若是你历来没看见过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 />