此快速参考备忘单提供了使用 CSS in JS 工具的各种方法
入门
安装
Styled Components 是增强 CSS 在 React 组件系统样式的 CSS in JS 的最佳实践。
安装依赖和 TypeScript 类型依赖
1
| npm install --save styled-components
|
快速开始
1
| import styled from 'styled-components';
|
创建一个 Title 组件
1 2 3 4 5
| const Title = styled.h1` font-size: 1.5em; text-align: center; `;
|
创建一个 Wrapper 组件
1 2 3 4 5
| const Wrapper = styled.section` padding: 4em; background: papayawhip; `;
|
像使用其他 React 组件一样使用 Title/Wrapper - 除了它们的样式!
1 2 3 4 5 6 7 8 9
| function Demo() { return ( <Wrapper> <Title> Hello World! </Title> </Wrapper> ); }
|
根据 Props 适配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import styled from 'styled-components';
const Button = styled.button` /* 根据主要 props 调整颜色 */ background: ${ props => props.primary ? "blue" : "white" }; color: ${ props => props.primary ? "white" : "blue" }; font-size: 1em; margin: 1em; padding: 0.25em 1em; border: 2px solid blue; border-radius: 3px; `;
|
使用 primary
props 控制按钮样式
{5}1 2 3 4 5 6 7 8
| function Demo() { return ( <div> <Button>Normal</Button> <Button primary>Primary</Button> </div> ); }
|
扩展样式
{7}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const Button = styled.button` color: palevioletred; border: 2px solid palevioletred; border-radius: 3px; `;
const TomatoButton = styled(Button)` color: tomato; border-color: tomato; `; const Demo = () => ( <div> <Button>普通按钮</Button> <TomatoButton>番茄色按钮</TomatoButton> </div> );
|
扩展样式改变标签 (as)
{17,20}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const Button = styled.button` color: palevioletred; padding: 0.25em 1em; border: 2px solid palevioletred; border-radius: 3px; display: block; `;
const TomatoButton = styled(Button)` color: tomato; border-color: tomato; `;
const Demo = () => ( <div> <Button>普通按钮</Button> <Button as="a" href="#"> 按钮样式的链接 </Button> <TomatoButton as="a" href="#"> 番茄按钮样式的链接 </TomatoButton> </div> );
|
自定义组件(as)
{20}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const Button = styled.button` color: palevioletred; font-size: 1em; border: 2px solid palevioletred; display: block; `;
const ReversedButton = props => ( <Button {...props} children={ props.children.split('').reverse() } /> );
render( <div> <Button>普通按钮</Button> <Button as={ReversedButton}> 具有普通按钮样式的自定义按钮 </Button> </div> );
|
样式化任何组件
1 2 3 4 5 6 7 8 9 10
| const Link = ({ className, children }) => ( <a className={className}> {children} </a> ); const StyledLink = styled(Link)` color: palevioletred; font-weight: bold; `; <StyledLink className="hello" />
|
在 render 之外定义 Styled 组件
{3}1 2 3 4 5 6 7 8 9
| const Box = styled.div`/* ... */`; const Wrapper = ({ message }) => { return ( <Box> {message} </Box> ); };
|
注意:组件 Box
不能放到 Wrapper
函数组件里面
传入值
{3,4,17}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const Input = styled.input` color: ${ props => props.inputColor || "palevioletred" }; background: papayawhip; `; const Demo = () => ( <div> <Input defaultValue="@probablyup" type="text" /> <Input defaultValue="@geelen" type="text" inputColor="rebeccapurple" /> </div> );
|
样式对象
{2,5}1 2 3 4 5 6
| const PropsBox = styled.div(props => ({ background: props.background, height: '50px', width: '50px', fontSize: '12px' }));
|
在组件中使用
{5}1 2 3 4 5 6 7 8 9
| const Example = () => { return ( <div> <PropsBox background="blue" /> </div> ); }
|
注意:样式对象里面的样式并不是 CSS 中的写法。
CSSModules => styled
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React, { useState } from 'react'; import styles from './styles.css';
function ExampleCounter() { const [count, setCount] = useState(0) return ( <div className={styles.counter}> <p className={styles.paragraph}> {count} </p> <button className={styles.button} onClick={() => setCount(count +1)} > + </button> <button className={styles.button} onClick={() => setCount(count -1)} > - </button> </div> ); }
|
👇👇 与下面 styled 写法等效 👇👇
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import styled from 'styled-components';
const StyledCounter = styled.div` /* ... */ `; const Paragraph = styled.p` /* ... */ `; const Button = styled.button` /* ... */ `; function ExampleCounter() { const [count, setCount] = useState(0); const increment = () => { setCount(count +1); } const decrement = () => { setCount(count -1); } return ( <StyledCounter> <Paragraph>{count}</Paragraph> <Button onClick={increment}> + </Button> <Button onClick={decrement}> - </Button> </StyledCounter> ); }
|
伪元素、伪选择器和嵌套
{3,6,9,12,15}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const Thing = styled.div.attrs(() => ({ tabIndex: 0 }))` color: blue; &:hover { /* <Thing> 悬停时 */ color: red; } & ~ & { /* <Thing> 作为 <Thing> 的兄弟,但可能不直接在它旁边 */ background: tomato; } & + & { /* <Thing> 旁边的 <Thing> */ background: lime; } &.something { /* <Thing> 标记有一个额外的 CSS 类 “.something” */ background: orange; } .something-else & { /* <Thing> 在另一个标记为 “.something-else” 的元素中 */ border: 1px solid; } `;
render( <React.Fragment> <Thing>Hello world!</Thing> <Thing>你怎么样?</Thing> <Thing className="something"> 艳阳高照... </Thing> <div>今天真是美好的一天。</div> <Thing>你不觉得吗?</Thing> <div className="something-else"> <Thing>灿烂</Thing> </div> </React.Fragment> );
|
改变 styled 组件样式
{13,21}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import { css } from 'styled-components' import styled from 'styled-components'
const Input = styled.input.attrs({ type: "checkbox" })``; const LabelText = styled.span` ${(props) => { switch (props.$mode) { case "dark": return css` color: white; ${Input}:checked + && { color: blue; } `; default: return css` color: black; ${Input}:checked + && { color: red; } `; } }} `;
function Example() { return ( <React.Fragment> <Label> <Input defaultChecked /> <LabelText>Foo</LabelText> </Label> <Label> <Input /> <LabelText $mode="dark"> Foo </LabelText> </Label> </React.Fragment> ); }
|
全局样式 createGlobalStyle
{3,11}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { styled, createGlobalStyle } from 'styled-components'
const Thing = styled.div` && { color: blue; } `; const GlobalStyle = createGlobalStyle` div${Thing} { color: red; } `;
const Example = () => ( <React.Fragment> <GlobalStyle /> <Thing> 我是蓝色的 </Thing> </React.Fragment> );
|
className 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const Thing = styled.div` color: blue; /* <Thing> 中标记为“.something”的元素 */ .something { border: 1px solid; } `;
function Example() { return ( <Thing> <label htmlFor="foo-button" className="something" > 神秘按钮 </label> <button id="foo-button"> 我该怎么办? </button> </Thing> ) }
|
共享样式片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const rotate = keyframes` from {top:0px;} to {top:200px;} `;
const styles = ` animation: ${rotate} 2s linear infinite; `;
const styles = css` animation: ${rotate} 2s linear infinite; `;
|
Class 组件样式定义
{5}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class NewHeader extends React.Component { render() { return ( <div className={this.props.className} /> ); } } const StyledA = styled(NewHeader)`` const Box = styled.div` ${StyledA} { /* 变更 NewHeader 样式 */ } `;
|
附加额外的 Props
{3,5,13,14,23}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const Input = styled.input.attrs(props=>({ type: "text", size: props.size || "1em", }))` color: palevioletred; font-size: 1em; border: 2px solid palevioletred; border-radius: 3px;
/* 这里我们使用动态计算的 props */ margin: ${props => props.size}; padding: ${props => props.size}; `;
|
使用 Input
组件
1 2 3 4 5 6 7 8 9 10 11 12
| function Example() { return ( <div> <Input placeholder="小文本输入" /> <br /> <Input placeholder="更大的文本输入" size="2em" /> </div> ) }
|
覆盖 .attrs
{11}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const Input = styled.input.attrs(props=>({ type: "text", size: props.size || "1em", }))` border: 2px solid palevioletred; margin: ${props => props.size}; padding: ${props => props.size}; `;
const PasswordInput = styled(Input).attrs({ type: "password", })` /* 同样,border 将覆盖 Input 的边框 */ border: 2px solid aqua; `;
|
使用 Input
和 PasswordInput
组件
{5,11}1 2 3 4 5 6 7 8 9 10 11 12 13 14
| render( <div> <Input placeholder="更大的文本输入" size="2em" /> <br /> {/*⚠️ 仍然可以使用Input中的 size attr*/} <PasswordInput placeholder="更大的密码输入" size="2em" /> </div> );
|
动画
创建关键帧
1 2 3 4 5 6 7 8 9
| const rotate = keyframes` from { transform: rotate(0deg); }
to { transform: rotate(360deg); } `;
|
我们创建一个 Rotate
组件
1 2 3 4 5 6 7
| const Rotate = styled.div` display: inline-block; animation: ${rotate} 2s linear infinite; padding: 2rem 1rem; font-size: 1.2rem; `;
|
使用 Rotate
组件
1 2 3 4 5
| function Example() { return ( <Rotate>< 💅🏾 ></Rotate> ) }
|
isStyledComponent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import React from 'react' import styled, { isStyledComponent } from 'styled-components' import MaybeStyledComponent from './my'
let TargetedComponent = isStyledComponent(MaybeStyledComponent) ? MaybeStyledComponent : styled(MaybeStyledComponent)``;
const ParentComponent = styled.div` color: cornflowerblue;
${TargetedComponent} { color: tomato; } `;
|
ThemeConsumer
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { ThemeConsumer } from 'styled-components'
function Example() { return ( <ThemeConsumer> {theme => ( <div>主题色是 {theme.color}</div> )} </ThemeConsumer> ); }
|
TypeScript
安装
Web 应用上安装 styled
1
| npm install -D @types/styled-components
|
React Native 应用上安装 styled
1 2 3
| npm install -D \ @types/styled-components \ @types/styled-components-react-native
|
如果对 TypeScript 不熟悉,参考 TypeScript 备忘清单
自定义 Props
1 2 3 4 5 6 7 8 9 10 11 12 13
| import styled from 'styled-components';
interface TitleProps { readonly isActive: boolean; }
const Title = styled.h1<TitleProps>` color: ${(props) => ( props.isActive ? props.theme.colors.main : props.theme.colors.secondary )}; `;
|
简单的 Props 类型定义
1 2 3 4 5 6 7 8 9 10 11 12
| import styled from 'styled-components'; import Header from './Header';
const Header = styled.header` font-size: 12px; `;
const NewHeader = styled(Header)<{ customColor: string; }>` color: ${(props) => props.customColor}; `;
|
禁止转移到子组件($)
{5}1 2 3 4 5 6 7 8 9 10 11 12
| import styled from 'styled-components'; import Header from './Header';
interface ReHeader { $customColor: string; }
const ReHeader = styled(Header)<ReHeader>` color: ${ props => props.$customColor }; `;
|
禁止 customColor
属性转移到 Header
组件,在其前面加上美元($
)符号
函数组件类型继承
{8,13}1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { FC, PropsWithRef, DetailedHTMLProps, ImgHTMLAttributes } from 'react'; import styled from 'styled-components';
const Img = styled.img` height: 32px; width: 32px; `; export interface ImageProps extends DetailedHTMLProps< ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement > { text?: string; }; export const Image: FC<PropsWithRef<ImageProps>> = (props) => ( <Img src="" alt="" {...props} /> );
|
React Native
基础实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from 'react' import styled from 'styled-components/native'
const StyledView = styled.View` background-color: papayawhip; `; const StyledText = styled.Text` color: palevioletred; `;
class MyReactNativeComponent extends React.Component { render() { return ( <StyledView> <StyledText>Hello World!</StyledText> </StyledView> ); } }
|
React Native 中写 CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import styled from 'styled-components/native'
const RotatedBox = styled.View` transform: rotate(90deg); text-shadow-offset: 10px 5px; font-variant: small-caps; margin: 5px 7px 2px; `;
function Example() { return ( <RotatedBox /> ) }
|
与 web 版本的一些区别是,您不能使用关键帧(keyframes
)和 createGlobalStyle
助手,因为 React Native 不支持关键帧或全局样式。如果您使用媒体查询或嵌套 CSS,我们也会警告您。
高级用法
主题化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import styled, { ThemeProvider } from 'styled-components'
const Button = styled.button` font-size: 1em; margin: 1em; padding: 0.25em 1em; border-radius: 3px;
/* 使用 theme.main 为边框和文本着色 */ color: ${props => props.theme.main}; border: 2px solid ${props => props.theme.main}; `;
Button.defaultProps = { theme: { main: "palevioletred" } }
const theme = { main: "mediumseagreen" };
render( <div> <Button>Normal</Button>
<ThemeProvider theme={theme}> <Button>Themed</Button> </ThemeProvider> </div> );
|
功能主题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import styled, { ThemeProvider } from 'styled-components'
const Button = styled.button` color: ${props => props.theme.fg}; border: 2px solid ${props => props.theme.fg}; background: ${props => props.theme.bg};
font-size: 1em; margin: 1em; padding: 0.25em 1em; border-radius: 3px; `;
const theme = { fg: "palevioletred", bg: "white" };
const invertTheme = ({ fg, bg }) => ({ fg: bg, bg: fg });
render( <ThemeProvider theme={theme}> <div> <Button>默认主题</Button> <ThemeProvider theme={invertTheme}> <Button>反转主题</Button> </ThemeProvider> </div> </ThemeProvider> );
|
通过 withTheme 高阶组件
1 2 3 4 5 6 7 8 9 10
| import { withTheme } from 'styled-components'
class MyComponent extends React.Component { render() { console.log('Current theme: ', this.props.theme) } }
export default withTheme(MyComponent)
|
useContext 钩子
1 2 3 4 5 6 7 8 9
| import { useContext } from 'react' import { ThemeContext } from 'styled-components'
const MyComponent = () => { const themeContext = useContext(ThemeContext) console.log('Current theme: ', themeContext) }
|
useTheme 自定义钩子
1 2 3 4 5 6 7 8
| import {useTheme} from 'styled-components'
const MyComponent = () => { const theme = useTheme()
console.log('Current theme: ', theme) }
|
主题 props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { ThemeProvider, styled } from 'styled-components';
const Button = styled.button` font-size: 1em; margin: 1em; padding: 0.25em 1em; /* 使用 theme.main 为边框和文本着色 */ color: ${props => props.theme.main}; border: 2px solid ${props => props.theme.main}; `;
const theme = { main: "mediumseagreen" };
|
使用自定义主题组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| render( <div> <Button theme={{ main: "royalblue" }}> 特设主题 </Button> <ThemeProvider theme={theme}> <div> <Button>Themed</Button> <Button theme={{ main: "darkorange" }} > 被覆盖 </Button> </div> </ThemeProvider> </div> );
|
Refs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { ThemeProvider, styled } from 'styled-components';
const Input = styled.input` border: none; border-radius: 3px; `;
class Form extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); }
render() { return ( <Input ref={this.inputRef} placeholder="Hover to focus!" onMouseEnter={() => { this.inputRef.current.focus() }} /> ); } }
|
使用 Form
组件
1 2 3 4 5
| function Example() { return ( <Form /> ) }
|
特异性问题
在文件 MyComponent.js
中定义 MyComponent
组件。
1 2 3
| const MyComponent = styled.div` background-color: green; `;
|
定义样式 my-component.css
1 2 3
| .red-bg { background-color: red; }
|
使用 MyComponent
组件
1
| <MyComponent className="red-bg" />
|
由于某种原因,这个组件仍然有绿色背景,即使你试图用 red-bg
类覆盖它!
解决方案
1 2 3
| .red-bg.red-bg { background-color: red; }
|
ThemeProvider
1 2 3 4 5 6 7 8 9 10 11
| import styled, { ThemeProvider } from 'styled-components'
const Box = styled.div` color: ${props => props.theme.color}; `;
const Example = () => ( <ThemeProvider theme={{ color: 'mediumseagreen' }}> <Box>I'm mediumseagreen!</Box> </ThemeProvider> );
|
shouldForwardProp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const Comp = styled('div').withConfig({ shouldForwardProp: (prop, defaultValidatorFn) => !['hidden'].includes(prop) && defaultValidatorFn(prop), }).attrs({ className: 'foo' })` color: red; &.foo { text-decoration: underline; } `;
const Example = () => ( <Comp hidden draggable="true"> Drag Me! </Comp> );
|