高级用法

主题化(Theming)

¥Theming

styled-components 通过导出 <ThemeProvider> 封装器组件来提供完整的主题支持。该组件通过 context API 为自身下面的所有 React 组件提供一个主题。在渲染树中,所有样式组件都可以访问提供的主题,即使它们是多个级别的深度。

¥styled-components has full theming support by exporting a <ThemeProvider> wrapper component. This component provides a theme to all React components underneath itself via the context API. In the render tree all styled-components will have access to the provided theme, even when they are multiple levels deep.

为了说明这一点,让我们创建 Button 组件,但这次我们将传递一些变量作为主题。

¥To illustrate this, let's create our Button component, but this time we'll pass some variables down as a theme.

功能主题(Function themes)

¥Function themes

你还可以为 theme 属性传递一个函数。该函数将接收父主题,即来自树上更高的另一个 <ThemeProvider> 的主题。这样主题本身就可以与上下文相关。

¥You can also pass a function for the theme prop. This function will receive the parent theme, that is from another <ThemeProvider> higher up the tree. This way themes themselves can be made contextual.

此示例渲染我们上面的主题按钮和第二个使用第二个 ThemeProvider 反转背景和前景色的按钮。函数 invertTheme 接收上层主题并创建一个新主题。

¥This example renders our above themed Button and a second one that uses a second ThemeProvider to invert the background and foreground colors. The function invertTheme receives the upper theme and creates a new one.

获取没有样式组件的主题(Getting the theme without styled components)

¥Getting the theme without styled components

通过 withTheme 高阶组件(via withTheme higher-order component)

¥via withTheme higher-order component

如果你需要在样式组件外部(例如在大组件内部)使用当前主题,你可以使用 withTheme 高阶组件。

¥If you ever need to use the current theme outside styled components (e.g. inside big components), you can use the withTheme higher order component.

import { withTheme } from 'styled-components'


class MyComponent extends React.Component {
  render() {
    console.log('Current theme: ', this.props.theme)
    // ...
  }
}


export default withTheme(MyComponent)

通过 useContext React 钩子(via useContext React hook)

¥via useContext React hook

| v4

使用 React Hooks 时,你还可以使用 useContext 在样式组件之外访问当前主题。

¥You can also use useContext to access the current theme outside of styled components when working with React Hooks.

import { useContext } from 'react'
import { ThemeContext } from 'styled-components'


const MyComponent = () => {
  const themeContext = useContext(ThemeContext)


  console.log('Current theme: ', themeContext)
  // ...
}

通过 useTheme 自定义钩子(via useTheme custom hook)

¥via useTheme custom hook

| v5

使用 React Hooks 时,你还可以使用 useTheme 在样式组件之外访问当前主题。

¥You can also use useTheme to access the current theme outside of styled components when working with React Hooks.

import { useTheme } from 'styled-components'


const MyComponent = () => {
  const theme = useTheme()


  console.log('Current theme: ', theme)
  // ...
}

theme 属性(The theme prop)

¥The theme prop

还可以使用 theme 属性将主题传递给组件。

¥A theme can also be passed down to a component using the theme prop.

这对于规避缺失的 ThemeProvider 或覆盖它很有用。

¥This is useful to circumvent a missing ThemeProvider or to override it.

引用(Refs)

¥Refs

| v4

ref 属性传递给样式组件将为你提供以下两种情况之一,具体取决于样式目标:

¥Passing a ref prop to a styled component will give you one of two things depending on the styled target:

  • 底层 DOM 节点(如果针对基本元素,例如 styled.div

    ¥the underlying DOM node (if targeting a basic element, e.g. styled.div)

  • React 组件实例(如果针对自定义组件,例如从 React.Component 扩展)

    ¥a React component instance (if targeting a custom component e.g. extended from React.Component)

Note

使用旧版本的 styled-components (4.0.0 以下)或 React?请改用 innerRef 属性

¥Using an older version of styled-components (below 4.0.0) or of React? Use the innerRef prop instead.

安全(Security)

¥Security

由于样式组件允许你使用任意输入作为插值,因此你必须小心清理该输入。使用用户输入作为样式可能会导致任何 CSS 在用户浏览器中被评估,攻击者可以将其放入你的应用中。

¥Since styled-components allows you to use arbitrary input as interpolations, you must be careful to sanitize that input. Using user input as styles can lead to any CSS being evaluated in the user's browser that an attacker can place in your application.

此示例显示错误的用户输入甚至可能导致代表用户调用 API 端点。

¥This example shows how bad user input can even lead to API endpoints being called on a user's behalf.

// 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 注入可能并不明显并且会产生不良影响。某些 IE 版本甚至在 url 声明中执行任意 JavaScript。

¥Be very careful! This is obviously a made-up example, but CSS injection can be unobvious and have bad repercussions. Some IE versions even execute arbitrary JavaScript within url declarations.

有一个即将推出的标准,用于清理 JavaScript 中的 CSS,即 CSS.escape。跨浏览器的支持还不是很好,因此我们建议在你的应用中使用 填充由 Mathias Bynens 设计

¥There is an upcoming standard to sanitize CSS from JavaScript, CSS.escape. It's not very well supported across browsers yet, so we recommend using the polyfill by Mathias Bynens in your app.

现有的 CSS(Existing CSS)

¥Existing CSS

如果你选择将样式组件与现有 CSS 一起使用,则应该注意一些实现细节。

¥There are a couple of implementation details that you should be aware of, if you choose to use styled-components together with existing CSS.

styled-components 生成带有类的实际样式表,并通过 className 属性将这些类附加到样式组件的 DOM 节点。它在运行时将生成的样式表注入到文档头部的末尾。

¥styled-components generates an actual stylesheet with classes, and attaches those classes to the DOM nodes of styled components via the className prop. It injects the generated stylesheet at the end of the head of the document during runtime.

正常 React 组件的样式(Styling normal React components)

¥Styling normal React components

如果你使用 styled(MyComponent) 表示法并且 MyComponent 不渲染传入的 className 属性,则不会应用任何样式。为了避免此问题,请确保你的组件将传入的 className 连接到 DOM 节点:

¥If you use the styled(MyComponent) notation and MyComponent does not render the passed-in className prop, then no styles will be applied. To avoid this issue, make sure your component attaches the passed-in className to a DOM node:

class MyComponent extends React.Component {
  render() {
    // Attach the passed-in className to the DOM node
    return <div className={this.props.className} />
  }
}

如果你的类中已有样式,则可以将全局类与传入的类合并:

¥If you have pre-existing styles with a class, you can combine the global class with the passed-in one:

class MyComponent extends React.Component {
  render() {
    // Attach the passed-in className to the DOM node
    return <div className={`some-global-class ${this.props.className}`} />
  }
}

特殊性问题(Issues with specificity)

¥Issues with specificity

如果你将全局类与样式化组件类一起应用,结果可能不是你所期望的。如果在两个类中定义的属性具有相同的特异性,则最后一个将获胜。

¥If you apply a global class together with a styled component class, the result might not be what you're expecting. If a property is defined in both classes with the same specificity, the last one will win.

// 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" />

在上面的示例中,样式化组件类优先于全局类,因为样式化组件默认在运行时在 <head> 末尾注入其样式。因此,它的样式胜过其他单一类名选择器。

¥In the above example the styled component class takes precedence over the global class, since styled-components injects its styles during runtime at the end of the <head> by default. Thus its styles win over other single classname selectors.

一种解决方案是提高样式表中选择器的特异性:

¥One solution is to bump up the specificity of the selectors in your stylesheet:

/* my-component.css */
.red-bg.red-bg {
  background-color: red;
}

避免与第三方样式和脚本发生冲突(Avoiding conflicts with third-party styles and scripts)

¥Avoiding conflicts with third-party styles and scripts

如果你在不完全控制的页面上部署样式组件,则可能需要采取预防措施以确保组件样式不会与主页的样式冲突。

¥If you deploy styled-components on a page you don't fully control, you may need to take precautions to ensure that your component styles don't conflict with those of the host page.

最常见的问题是特异性不够。例如,考虑具有以下样式规则的主页:

¥The most common problem is insufficient specificity. For example, consider a host page with this style rule:

body.my-body button {
  padding: 24px;
}

由于该规则包含一个类名和两个标签名,因此它比此样式组件生成的单个类名选择器具有更高的特异性:

¥Since the rule contains a classname and two tag names, it has higher specificity than the single classname selector generated by this styled component:

styled.button`
  padding: 16px;
`

没有办法让你的组件完全不受主机页样式的影响,但你至少可以使用 babel-plugin-styled-components-css-namespace 提高其样式规则的特异性,它允许你为所有样式组件指定 CSS 命名空间。如果所有样式组件都在具有 id="my-widget" 的容器中渲染,那么一个好的命名空间将类似于 #my-widget,因为 ID 选择器比任意数量的类名具有更高的特异性。

¥There's no way to give your components complete immunity from the host page's styles, but you can at least boost the specificity of their style rules with babel-plugin-styled-components-css-namespace, which allows you to specify a CSS namespace for all of your styled components. A good namespace would be something like #my-widget, if all of your styled-components render in a container with id="my-widget", since ID selectors have more specificity than any number of classnames.

一个更罕见的问题是页面上两个样式组件实例之间的冲突。你可以通过在带有样式组件实例的代码包中定义 process.env.SC_ATTR 来避免这种情况。该值覆盖默认的 <style> 标签属性 data-styled(v3 及更低版本中的 data-styled-components),允许每个样式组件实例识别自己的标签。

¥A rarer problem is conflicts between two instances of styled-components on the page. You can avoid this by defining process.env.SC_ATTR in the code bundle with your styled-components instance. This value overrides the default <style> tag attribute, data-styled (data-styled-components in v3 and lower), allowing each styled-components instance to recognize its own tags.

标记模板字面量(Tagged Template Literals)

¥Tagged Template Literals

标记模板字面量是 ES6 中的一项新功能。它们允许你定义自定义字符串插值规则,这就是我们创建样式组件的方式。

¥Tagged Template Literals are a new feature in ES6. They let you define custom string interpolation rules, which is how we're able to create styled components.

如果不传递插值,则函数接收的第一个参数是一个包含字符串的数组。

¥If you pass no interpolations, the first argument your function receives is an array with a string in it.

// These are equivalent:
fn`some string here`;
fn(['some string here']);

传递插值后,数组将包含传递的字符串,并在插值的位置处拆分。其余参数将按顺序进行插值。

¥Once you pass interpolations, the array contains the passed string, split at the positions of the interpolations. The rest of the arguments will be the interpolations, in order.

const aVar = 'good';


// These are equivalent:
fn`this is a ${aVar} day`;
fn(['this is a ', ' day'], aVar);

这使用起来有点麻烦,但这意味着我们可以在样式组件中接收变量、函数或 mixin(css 助手),并且可以将其扁平化为纯 CSS。

¥This is a bit cumbersome to work with, but it means that we can receive variables, functions, or mixins (css helper) in styled components and can flatten that into pure CSS.

说到这里,在展平期间,styled-components 会忽略计算结果为 undefinednullfalse 或空字符串 ("") 的插值,这意味着你可以自由使用 短路评估 有条件地添加 CSS 规则。

¥Speaking of which, during flattening, styled-components ignores interpolations that evaluate to undefined, null, false, or an empty string (""), which means you're free to use short-circuit evaluation to conditionally add CSS rules.

const Title = styled.h1<{ $upsideDown?: boolean; }>`
  /* Text centering won't break if props.$upsideDown is falsy */
  ${props => props.$upsideDown && 'transform: rotate(180deg);'}
  text-align: center;
`;

如果你想了解有关标记模板字面量的更多信息,请查看 Max Stoiber 的文章:💅🏾 styled-components 背后的魔力

¥If you want to learn more about tagged template literals, check out Max Stoiber's article: The magic behind 💅🏾 styled-components

服务器端渲染(Server Side Rendering)

¥Server Side Rendering

| v2+

styled-components 支持并发服务器端渲染,并具有样式表补水功能。基本思想是,每次在服务器上渲染应用时,你都可以创建一个 ServerStyleSheet 并将提供程序添加到你的 React 树中,该提供程序通过上下文 API 接受样式。

¥styled-components supports concurrent server side rendering, with stylesheet rehydration. The basic idea is that everytime you render your app on the server, you can create a ServerStyleSheet and add a provider to your React tree, that accepts styles via a context API.

这不会干扰全局样式,例如 keyframescreateGlobalStyle,并允许你将样式组件与 React DOM 的各种 SSR API 一起使用。

¥This doesn't interfere with global styles, such as keyframes or createGlobalStyle and allows you to use styled-components with React DOM's various SSR APIs.

工具设置(Tooling setup)

¥Tooling setup

为了可靠地执行服务器端渲染并让客户端包顺利获取,你需要使用我们的 babel 插件。它通过向每个样式组件添加确定性 ID 来防止校验和不匹配。请参阅 工具文档 了解更多信息。

¥In order to reliably perform server side rendering and have the client side bundle pick up without issues, you'll need to use our babel plugin. It prevents checksum mismatches by adding a deterministic ID to each styled component. Refer to the tooling documentation for more information.

对于 TypeScript 用户,我们的常驻 TS 大师 Igor Oleinikov 为 webpack ts-loader / Awesome-typescript-loader 工具链整理了一个 TypeScript 插件,可以完成一些类似的任务。

¥For TypeScript users, our resident TS guru Igor Oleinikov put together a TypeScript plugin for the webpack ts-loader / awesome-typescript-loader toolchain that accomplishes some similar tasks.

如果可能的话,我们绝对推荐使用 babel 插件,因为它更新最频繁。现在可以实现 使用 Babel 编译 TypeScript,因此可能值得关闭 TS 加载器并使用纯 Babel 实现来获得生态系统的好处。

¥If possible, we definitely recommend using the babel plugin though because it is updated the most frequently. It's now possible to compile TypeScript using Babel, so it may be worth switching off TS loader and onto a pure Babel implementation to reap the ecosystem benefits.

示例(Example)

¥Example

基本的 API 如下:

¥The basic API goes as follows:

import { renderToString } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';


const sheet = new ServerStyleSheet();
try {
  const html = renderToString(sheet.collectStyles(<YourApp />));
  const styleTags = sheet.getStyleTags(); // or sheet.getStyleElement();
} catch (error) {
  // handle error
  console.error(error);
} finally {
  sheet.seal();
}

collectStyles 方法将你的元素封装在提供程序中。你也可以选择直接使用 StyleSheetManager 提供程序,而不是使用此方法。只要确保不要在客户端使用它即可。

¥The collectStyles method wraps your element in a provider. Optionally you can use the StyleSheetManager provider directly, instead of this method. Just make sure not to use it on the client-side.

import { renderToString } from 'react-dom/server';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';


const sheet = new ServerStyleSheet();
try {
  const html = renderToString(
    <StyleSheetManager sheet={sheet.instance}>
      <YourApp />
    </StyleSheetManager>
  );
  const styleTags = sheet.getStyleTags(); // or sheet.getStyleElement();
} catch (error) {
  // handle error
  console.error(error);
} finally {
  sheet.seal();
}

sheet.getStyleTags() 返回多个 <style> 标签的字符串。将 CSS 字符串添加到 HTML 输出时需要考虑到这一点。

¥The sheet.getStyleTags() returns a string of multiple <style> tags. You need to take this into account when adding the CSS string to your HTML output.

另外,ServerStyleSheet 实例还有一个 getStyleElement() 方法,该方法返回 React 元素数组。

¥Alternatively the ServerStyleSheet instance also has a getStyleElement() method that returns an array of React elements.

如果由于任何原因渲染失败,最好使用 try...catch...finally 来确保 sheet 对象始终可用于垃圾回收。确保仅在调用 sheet.getStyleTags()sheet.getStyleElement() 之后调用 sheet.seal(),否则将引发不同的错误。

¥If rendering fails for any reason it's a good idea to use try...catch...finally to ensure that the sheet object will always be available for garbage collection. Make sure sheet.seal() is only called after sheet.getStyleTags() or sheet.getStyleElement() have been called otherwise a different error will be thrown.

Note

sheet.getStyleTags()sheet.getStyleElement() 只能在元素渲染后调用。因此,sheet.getStyleElement() 中的组件无法与 <YourApp /> 组合成更大的组件。

¥sheet.getStyleTags() and sheet.getStyleElement() can only be called after your element is rendered. As a result, components from sheet.getStyleElement() cannot be combined with <YourApp /> into a larger component.

Next.js(Next.js)

使用 Babel(With Babel)

¥With Babel

基本上你需要添加一个自定义 pages/_document.js(如果你没有)。然后 复制逻辑 为样式组件将服务器端渲染的样式注入到 <head> 中。

¥Basically you need to add a custom pages/_document.js (if you don't have one). Then copy the logic for styled-components to inject the server side rendered styles into the <head>.

请参阅 Next.js 存储库中的 我们的例子 以获取最新的使用示例。

¥Refer to our example in the Next.js repo for an up-to-date usage example.

使用 SWC(With SWC)

¥With SWC

从版本 12 开始,Next.js 使用名为 SWC 的 Rust 编译器。如果你没有使用任何 babel 插件,你应该参考 这个例子

¥Since version 12, Next.js uses a Rust compiler called SWC. If you're not using any babel plugin, you should refer to this example instead.

在此版本中,你可以在 next.config.js 文件中的编译器选项中进行 只需要添加 styledComponents: true, 并使用 getInitialProps 修改 _document 文件(如本 example 中所示)以支持 SSR。

¥On this version, you only need to add styledComponents: true, at the compiler options in the next.config.js file and modify _document file with getInitialProps as in this example to support SSR.

应用目录(App directory)

¥App directory

对于在 app/ 目录中定义的路由,在 Next.js v13+ 中,你需要将样式组件注册表放入布局文件之一中,如 Next.js 文档中描述。请注意,这取决于 styled-components v6+。另请注意,使用了 'use client' 指令 - 因此,虽然你的页面将在服务器端渲染,但样式组件仍将出现在你的客户端包中。

¥For routes defined in the app/ directory, in Next.js v13+, you'll need to put a styled-components registry in one of your layout files, as described in Next.js docs. Note that this depends on styled-components v6+. Also note that the 'use client' directive is used - so while your page will be server-side rendered, styled-components will still appear in your client bundle.

Gatsby(Gatsby)

Gatsby 有一个官方插件,可以为样式组件启用服务器端渲染。

¥Gatsby has an official plugin that enables server-side rendering for styled-components.

请参阅 插件页面 了解设置和使用说明。

¥Refer to the plugin's page for setup and usage instructions.

流式渲染(Streaming Rendering)

¥Streaming Rendering

styled-components 提供了一个与 ReactDOMServer.renderToNodeStream() 一起使用的流 API。流实现有两个部分:

¥styled-components offers a streaming API for use with ReactDOMServer.renderToNodeStream(). There are two parts to a streaming implementation:

在服务器上:

¥On the server:

ReactDOMServer.renderToNodeStream 触发一个由 styled-components 封装的 "readable" 流。当整个 HTML 块被推送到流上时,如果任何相应的样式准备好渲染,样式块就会被添加到 React 的 HTML 中并转发到客户端浏览器。

¥ReactDOMServer.renderToNodeStream emits a "readable" stream that styled-components wraps. As whole chunks of HTML are pushed onto the stream, if any corresponding styles are ready to be rendered, a style block is prepended to React's HTML and forwarded on to the client browser.

import { renderToNodeStream } from 'react-dom/server';
import styled, { ServerStyleSheet } from 'styled-components';


// if you're using express.js, you'd have access to the response object "res"


// typically you'd want to write some preliminary HTML, since React doesn't handle this
res.write('<html><head><title>Test</title></head><body>');


const Heading = styled.h1`
  color: red;
`;


const sheet = new ServerStyleSheet();
const jsx = sheet.collectStyles(<Heading>Hello SSR!</Heading>);
const stream = sheet.interleaveWithNodeStream(renderToNodeStream(jsx));


// you'd then pipe the stream into the response object until it's done
stream.pipe(res, { end: false });


// and finalize the response with closing HTML
stream.on('end', () => res.end('</body></html>'));

在客户端:

¥On the client:

import { hydrate } from 'react-dom';


hydrate();
// your client-side react implementation

客户端补水完成后,样式组件将照常接管,并在重新定位的流式样式之后注入任何进一步的动态样式。

¥After client-side rehydration is complete, styled-components will take over as usual and inject any further dynamic styles after the relocated streaming ones.

引用其他组件(Referring to other components)

¥Referring to other components

Note

这是一个特定于 Web 的 API,你将无法在 React-Native 中使用它。

¥This is a web-specific API and you won't be able to use it in react-native.

有多种方法可以将上下文覆盖应用于组件的样式。话虽这么说,如果不装配众所周知的目标的 CSS 选择器范例,然后使它们可以在插值中使用,那么这一切都不容易。

¥There are many ways to apply contextual overrides to a component's styling. That being said, it rarely is easy without rigging up a well-known targeting CSS selector paradigm and then making them accessible for use in interpolations.

styled-components 通过 "组件选择器" 模式干净地解决了这个用例。每当 styled() 工厂函数创建或封装组件时,它也会被分配一个稳定的 CSS 类以用于定位。这允许极其强大的组合模式,而不必为命名和避免选择器冲突而大惊小怪。

¥styled-components solves this use case cleanly via the "component selector" pattern. Whenever a component is created or wrapped by the styled() factory function, it is also assigned a stable CSS class for use in targeting. This allows for extremely powerful composition patterns without having to fuss around with naming and avoiding selector collisions.

一个实际的例子:在这里,我们的图标组件定义了它对悬停的父链接的响应:

¥A practical example: here, our Icon component defines its response to the parent Link being hovered:

我们可以将颜色更改规则嵌套在 Link 组件中,但随后我们必须考虑两组规则才能理解 Icon 的行为原因。

¥We could have nested the color-changing rule within our Link component, but then we'd have to consider both sets of rules to understand why Icon behaves as it does.

警告(Caveat)

¥Caveat

此行为仅在样式化组件的上下文中受支持:在以下示例中尝试安装 B 将失败,因为组件 AReact.Component 的实例而不是样式组件。

¥This behaviour is only supported within the context of Styled Components: attempting to mount B in the following example will fail because component A is an instance of React.Component not a Styled Component.

class A extends React.Component {
  render() {
    return <div />
  }
}


const B = styled.div`
  ${A} {
  }
`

抛出的错误 - Cannot call a class as a function - 发生这种情况是因为样式化组件尝试将该组件作为插值函数调用。

¥The error thrown - Cannot call a class as a function - occurs because the styled component is attempting to call the component as an interpolation function.

然而,将 A 封装在 styled() 工厂中使其符合插值的条件 - 只需确保封装的组件沿着 className 传递即可。

¥However, wrapping A in a styled() factory makes it eligible for interpolation -- just make sure the wrapped component passes along className.

class A extends React.Component {
  render() {
    return <div className={this.props.className} />
  }
}


const StyledA = styled(A)``


const B = styled.div`
  ${StyledA} {
  }
`

样式对象(Style Objects)

¥Style Objects

styled-components 可以选择支持将 CSS 编写为 JavaScript 对象而不是字符串。当你拥有现有样式对象并希望逐渐转向样式化组件时,这特别有用。

¥styled-components optionally supports writing CSS as JavaScript objects instead of strings. This is particularly useful when you have existing style objects and want to gradually move to styled-components.

继续下一页

API 参考