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能干什么,这一回让咱们来完整的分析下这个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'
`
复制代码
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;
}
`;
复制代码
以上示例出自官方文档,可见它无鸭梨支持:嵌套、前缀自动补全、各种选择器、媒体查询...
除此以外,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'
}}
...
复制代码
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呢)
最终的渲染结果长这样
经过上节中的styled()
方法能将一个react组件包装成一个具备样式的react组件,咱们将它称之为StyledComponent
,它除了在样式上和组件强耦合外,还具备一些它独有的特性。
前面咱们说过,咱们能经过styled(StyledCompoent1)
一个StyleComponent来建立一个继承自StyledCompoent1的StyledComponent2组件。
let StyledCompoent2 = styled(StyledCompoent1)`
color:xxx
...
`
复制代码
但这样继承内部实际上是一个工厂模式,StyledComponent2实际上是一个全新的class。
若是咱们想要作到真正的继承,须要使用style-components提供的extend
方法,它是StyleComponent下的一个属性方法。
let StyledCompoent2 =StyledCompoent1.extend`
color:xxx
...
`
复制代码
withComponent一样是StyleComponent下的一个属性方法,它能帮助咱们将本来的Styled组件中的标签给替换成另一种标签
//会将本来的<button>替换成<a>
const Link = Button.withComponent('a');
复制代码
[danger] 注意: 若本来的Styled组件是一个具备复合标签的组件,那么它的整个DOM都会被替换掉,这可能并非你所指望的结果。
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
)
复制代码
在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>;
}
}
复制代码
解决办法是在_B
内再使用原生的ref
挂载一次,把dom挂载在_B
上,这样咱们就能够经过往下再深一层访问的方式拿到dom。
有些时候咱们须要判断一个组件是否是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方法接收一个对象,它容许咱们为一个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
对象(不管嵌套多少层)。
在上栗中其实有一个bug,那就是没有被<ThemeProvider>
包裹住的<Button/>
实际上是没有props.theme属性对象的,那么它就会报错。
So,这个时候咱们须要给这个Button组件设置一个默认值
...
// 设置默认属性,
Button.defaultProps = {
theme:{
main:'palevioletred'
}
}
const theme = {
main:'mediumseagreen'
}
...
复制代码
其实咱们除了在组件外部定义一个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
嵌套时,被嵌套的当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,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)
复制代码
首先它是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方面使用它。
每每和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
关于ReactNative
若是有一个新的状态传入致使须要添加新的cssText,那么会往style
标签中追加cssText,
注意是往里追加,并不会删除style里以前的cssText。(即便当前的props已经不知足以前css文本的生成条件也不会删除)
参考