💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
- 初体验 - 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方法:使用帧动画 - 其它 [TOC] ## pre-notify >previously: - [JSX,了解一下?](https://juejin.im/post/5ab065716fb9a028c5230164) - [React文档精读(上篇)](https://juejin.im/post/5ab20d67f265da237c68c949) 在越来越组件化开发的今天,我们通过`JSX`已经将js好html/xml很好的柔和在了一起,那么css呢? 虽然在vue这样的框架里我们能在`.vue`文件里将css、js、html写在一起,但实际上它们的联系很弱,特别是js和css,它们完全无法沟通。 而`styled-components`很好的解决了这个问题,通过它,我们能让整个css架构跟着组件走,而不再仅仅是貌合神离的被放置在一个文件中。可以这么说,styled-components让一个组件变得更加得完整,更加得像一个组件! ## 初体验 `styled-compnents`,正如其名,就是有样式的`react-component`,是对react组件的再封装,它不仅可以往`<Component/>`添加了固定的css样式,还可以通过组件的属性让css和一个组件紧密的联系起来。 除此之外它支持几乎所有`sass/less`等css预处理器具有的功能,嵌套、`&`、变量、插值,甚至更加强大! 我们先来看一个基本使用栗子 ``` // 把一个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值(防止命名冲突),我们需要**将它放置到它应该被放置的元素身上**。 通过`styled()`,我们已经将一个React组件包装成了一个Styled组件并导出,接下来我们去渲染这个导出的组件 ``` import React from 'react'; import ReactDOM from 'react-dom'; import StyledComponent from './test.js'; ReactDOM.render( <StyledComponent fontSize='30px'/> ,window.root ) ``` 渲染结果长这样: ![](https://box.kancloud.cn/ad1611df2ad5ebff5d9b2b9c2ebea0f1_484x205.png) 可以发现,在使用上,一个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呢) 最终的渲染结果长这样 ![](https://box.kancloud.cn/fd27757a1c87b24d36e103805e462eff_216x299.png) ## 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`。 ![](https://box.kancloud.cn/698fa4c9e641e6841ca56df3e4142743_610x142.png) > **注意:** 依然需要手动定位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 ) ``` ![](https://box.kancloud.cn/076e999d96beddedaed32359ffc6fd75_197x158.png) ### 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()} /> ); } } ``` [点我查看官方示例](https://www.styled-components.com/docs/advanced#refs) 上栗中使用的是`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>; } } ``` ![](https://box.kancloud.cn/629f5489f59915553028fc2cbeb49af0_701x245.png) (获取到的不是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> ) } } ``` 最终的渲染结果长这样 ![](https://box.kancloud.cn/13c2d9dd03b5e3359d6abf0150d1a85a_538x279.png) ## 主题组件 通过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> ) } } ``` [点我查看官方示例](https://www.styled-components.com/docs/advanced#theming) 上栗中,我们定制了一个`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> ... ``` [点击查看官方示例](https://www.styled-components.com/docs/advanced#function-themes) ### 在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; `; ``` [点我查看官方示例](https://www.styled-components.com/docs/basics#animations) ## 其它 ### 关于服务端渲染 > [服务端渲染](https://www.styled-components.com/docs/advanced#server-side-rendering) ### 关于TypeScript > [如何在TypeScript中使用styled-components](https://www.styled-components.com/docs/api#typescript) ### 关于ReactNative > [在ReactNative中使用styled-components需要注意的事情](https://www.styled-components.com/docs/basics#react-native) ### 关于styledComponent的更新 如果有一个新的状态传入导致需要添加新的cssText,那么会往`style`标签中追加cssText, 注意是往里追加,并不会删除style里之前的cssText。(即使当前的props已经不满足之前css文本的生成条件也不会删除) ![](https://box.kancloud.cn/b71027f935849364fcac5c8cc07ab426_675x387.png) ### 关于className 给一个styled-component直接添加一个`className`,那么这个className也会作为`props.className`中的一员,且作为第一个classname而存在(优先于 两个有styled-component随即生成的类名) ![](https://box.kancloud.cn/2eb654b89485e465d4db1edf5368ee7b_500x313.png) ### 关于css属性 如果你在一个组件的 ``` styled` width:2px; ` ``` 中设置了一个css属性,比如像上面这样 然后,如果个组件的入口元素也是一个Styled组件,并且给这个组件设置一个同样的css属性 ``` width:5px; ``` 那么此时2px会生效而不是5px ![](https://box.kancloud.cn/624b25aa61d43cb24a8959b09ccb5f7a_319x108.png) 因为css解析是按照类名的 从右往左解析 故若两个类名之间存在同名属性,取左边的那个 --- 参考 - [www.styled-components.com](https://www.styled-components.com/)