styled-components 备忘清单
此快速参考备忘单提供了使用 CSS in JS 工具的各种方法。
入门
安装
Styled Components 是增强 CSS 在 React 组件系统样式的 CSS in JS 的最佳实践。
- VSCode styled-components 有代码高亮和代码提示
- VIM styled-components 有代码高亮
- WebStorm styled-components 有代码高亮和代码提示
安装依赖和 TypeScript 类型依赖
npm install --save styled-components
快速开始
import styled from 'styled-components';
创建一个 Title 组件
// 该组件将呈现具有样式的 <h1> 标签const Title = styled.h1`font-size: 1.5em;text-align: center;`;
创建一个 Wrapper 组件
// 该组件将呈现具有某些样式的 <section> 标记const Wrapper = styled.section`padding: 4em;background: papayawhip;`;
像使用其他 React 组件一样使用 Title/Wrapper - 除了它们的样式!
function Demo() {return (<Wrapper><Title>Hello World!</Title></Wrapper>);}
根据 Props 适配
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 控制按钮样式
```jsx {5} function Demo() { return (
### 扩展样式```jsx {7}const Button = styled.button`color: palevioletred;border: 2px solid palevioletred;border-radius: 3px;`;// 基于 Button 的新组件,但具有一些覆盖样式const TomatoButton = styled(Button)`color: tomato;border-color: tomato;`;const Demo = () => (<div><Button>普通按钮</Button><TomatoButton>番茄色按钮</TomatoButton></div>);
扩展样式改变标签 (as)
``jsx {17,20}
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 = () => (
### 自定义组件(as)<!--rehype:wrap-class=row-span-2-->```jsx {20}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>);
样式化任何组件
const Link = ({ className, children }) => (<a className={className}>{children}</a>);const StyledLink = styled(Link)`color: palevioletred;font-weight: bold;`;<StyledLink className="hello" />
在 render 之外定义 Styled 组件
``jsx {3}
const Box = styled.div/ … /`;
const Wrapper = ({ message }) => {
// ⚠️ 不能在这里定义 styled 组件
return (
注意:组件 `Box` 不能放到 `Wrapper` 函数组件里面### 传入值```jsx {3,4,17}const Input = styled.input`color: ${props =>props.inputColor || "palevioletred"};background: papayawhip;`;const Demo = () => (<div><InputdefaultValue="@probablyup"type="text"/><InputdefaultValue="@geelen"type="text"inputColor="rebeccapurple"/></div>);
样式对象
```jsx {2,5} const PropsBox = styled.div(props => ({ background: props.background, height: ‘50px’, width: ‘50px’, fontSize: ‘12px’ }));
在组件中使用```jsx {5}const Example = () => {return (<div><PropsBoxbackground="blue"/></div>);}
注意:样式对象里面的样式并不是 CSS 中的写法。
CSSModules => styled
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><buttonclassName={styles.button}onClick={() => setCount(count +1)}>+</button><buttonclassName={styles.button}onClick={() => setCount(count -1)}>-</button></div>);}
👇👇 与下面 styled 写法等效 👇👇
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>);}
伪元素、伪选择器和嵌套
``jsx {3,6,9,12,15}
const Thing = styled.div.attrs((/* props */) => ({ tabIndex: 0 }))
color: blue;
&:hover { /
render(
### 改变 styled 组件样式<!--rehype:wrap-class=row-span-2-->```jsx {13,21}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
```jsx {3,11} import { styled, createGlobalStyle } from ‘styled-components’
const Thing = styled.div&& {
color: blue;
};
const GlobalStyle = createGlobalStylediv${Thing} {
color: red;
};
const Example = () => (
### className 使用```JSXconst Thing = styled.div`color: blue;/* <Thing> 中标记为“.something”的元素 */.something {border: 1px solid;}`;function Example() {return (<Thing><labelhtmlFor="foo-button"className="something">神秘按钮</label><button id="foo-button">我该怎么办?</button></Thing>)}
共享样式片段
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 组件样式定义
`jsx {5}
class NewHeader extends React.Component {
render() {
return (
<div
className={this.props.className}
/>
);
}
}
const StyledA = styled(NewHeader)
const Box = styled.div${StyledA} {
/* 变更 NewHeader 样式 */
};
### 附加额外的 Props```jsx {3,5,13,14,23}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 组件
function Example() {return (<div><Input placeholder="小文本输入" /><br /><Inputplaceholder="更大的文本输入"size="2em"/></div>)}
覆盖 .attrs
``jsx {11}
const Input = styled.input.attrs(props=>({
type: "text",
size: props.size || "1em",
}))
border: 2px solid palevioletred;
margin: ${props => props.size};
padding: ${props => props.size};
;
// Input 的attrs会先被应用,然后这个 attrs obj
const PasswordInput = styled(Input).attrs({
type: "password",
})
/ 同样,border 将覆盖 Input 的边框 /
border: 2px solid aqua;
`;
使用 `Input` 和 `PasswordInput` 组件```jsx {5,11}render(<div><Inputplaceholder="更大的文本输入"size="2em"/><br />{/*⚠️ 仍然可以使用Input中的 size attr*/}<PasswordInputplaceholder="更大的密码输入"size="2em"/></div>);
动画
创建关键帧
const rotate = keyframes`from {transform: rotate(0deg);}to {transform: rotate(360deg);}`;
我们创建一个 Rotate 组件
// 它将在两秒内旋转我们传递的所有内容const Rotate = styled.div`display: inline-block;animation: ${rotate} 2s linear infinite;padding: 2rem 1rem;font-size: 1.2rem;`;
使用 Rotate 组件
function Example() {return (<Rotate>< 💅🏾 ></Rotate>)}
isStyledComponent
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
import {ThemeConsumer} from 'styled-components'function Example() {return (<ThemeConsumer>{theme => (<div>主题色是 {theme.color}</div>)}</ThemeConsumer>);}
TypeScript
安装
Web 应用上安装 styled
npm install -D @types/styled-components
React Native 应用上安装 styled
npm install -D \@types/styled-components \@types/styled-components-react-native
如果对 TypeScript 不熟悉,参考 TypeScript 备忘清单
自定义 Props
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 类型定义
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};`;
禁止转移到子组件($)
```tsx {5} import styled from ‘styled-components’; import Header from ‘./Header’;
interface ReHeader { $customColor: string; }
const ReHeader = styled(Header)color: ${
props => props.$customColor
};;
禁止 `customColor` 属性转移到 `Header` 组件,在其前面加上美元(`$`)符号### 函数组件类型继承<!--rehype:wrap-class=col-span-2-->```tsx {8,13}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
基础实例
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
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,我们也会警告您。
高级用法
主题化
import styled, { ThemeProvider } from 'styled-components'// 定义我们的按钮,但这次使用 props.themeconst 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};`;// 我们正在为未包装在 ThemeProvider 中的按钮传递默认主题Button.defaultProps = {theme: {main: "palevioletred"}}// 定义 props.theme 的外观const theme = {main: "mediumseagreen"};render(<div><Button>Normal</Button><ThemeProvider theme={theme}><Button>Themed</Button></ThemeProvider></div>);
功能主题
import styled, { ThemeProvider } from 'styled-components'// 定义我们的按钮,但这次使用 props.themeconst 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;`;// 在主题上定义我们的`fg`和`bg`const theme = {fg: "palevioletred",bg: "white"};// 这个主题交换了`fg`和`bg`const invertTheme = ({ fg, bg }) => ({fg: bg,bg: fg});render(<ThemeProvider theme={theme}><div><Button>默认主题</Button><ThemeProvider theme={invertTheme}><Button>反转主题</Button></ThemeProvider></div></ThemeProvider>);
通过 withTheme 高阶组件
import { withTheme } from 'styled-components'class MyComponent extends React.Component {render() {console.log('Current theme: ', this.props.theme)// ...}}export default withTheme(MyComponent)
useContext 钩子
import { useContext } from 'react'import { ThemeContext } from 'styled-components'const MyComponent = () => {const themeContext = useContext(ThemeContext)console.log('Current theme: ', themeContext)// ...}
useTheme 自定义钩子
import {useTheme} from 'styled-components'const MyComponent = () => {const theme = useTheme()console.log('Current theme: ', theme)// ...}
主题 props
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"};
使用自定义主题组件
render(<div><Button theme={{ main: "royalblue" }}>特设主题</Button><ThemeProvider theme={theme}><div><Button>Themed</Button><Buttontheme={{ main: "darkorange" }}>被覆盖</Button></div></ThemeProvider></div>);
Refs
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 (<Inputref={this.inputRef}placeholder="Hover to focus!"onMouseEnter={() => {this.inputRef.current.focus()}}/>);}}
使用 Form 组件
function Example() {return (<Form />)}
特异性问题
在文件 MyComponent.js 中定义 MyComponent 组件。
const MyComponent = styled.div`background-color: green;`;
定义样式 my-component.css
.red-bg {background-color: red;}
使用 MyComponent 组件
<MyComponent className="red-bg" />
由于某种原因,这个组件仍然有绿色背景,即使你试图用 red-bg 类覆盖它!
解决方案
.red-bg.red-bg {background-color: red;}
ThemeProvider
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
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>);
