styled-components不完全手册

一周是一年的2%

大家好,我是柒八九 。一个专注于前端开发技术/RustAI应用知识分享Coder

前言

最近在做项目梳理,然后无意中在一些国外的UI库中发现如下的代码示例。

大家仔细观察上面的代码,其实就是对常规的布局做了封装,我们可以通过通过<XXX >{chilren}</XXX>将对应的组件进行包裹。

这样做的好处就是

  1. 见名知意,通过组件的名称我们就可以知晓该页面使用了何种布局
  2. 布局样式和组件内部样式进行分割
  3. 统一管理

然后,它背后用的技术就是我们在CSS-in-JS。针对CSS-in-JS业界是褒贬不一。

上面列举了CSS-in-JS的各种利弊。这其实就是仁者见仁,智者见智。但是,我更看中它在抽离公共布局方面的应用。就像最开头的截图所示,我们可以不把现有项目中所有组件都css-in-js处理,但是我们可以对系统种常规布局进行抽离,这样我们项目就层级就更加清晰明了。

既然,它是有用的,那么我们今天就来聊聊CSS-in-JS。因为,CSS-in-JS有很多解决方案。(emotion/styled-components)。

下面,我们就挑业界比较受欢迎的styled-components来进行讲解。

好了,天不早了,干点正事哇。

我们能所学到的知识点

  1. 初始化项目
  2. 基本用法
  3. 使用 Props
  4. 扩展样式
  5. 嵌套样式
  6. 扩展 React 组件
  7. CSS变量
  8. 添加主题
  9. 处理动画
  10. 使用 as 属性
  11. 默认属性

Styled-components 是一个库,它允许你在构建 Reactjs 自定义组件时,使用 JavaScriptCSS

1. 初始化项目

由于我们这里是一个技术讲解的文章,不需要额外的配置,所以我们就不用我们的f_cli来构建项目了,我们就用最简单的方式(cra)来构建项目(当然也可以使用vite)

shell 复制代码
npx create-react-app styled_demo

由于每个脚手架都有自己内置的逻辑,我们需要删除一些默认的逻辑。在初始化后,我们只保留index.jsapp.js。并且对其做一些简单的修改,使其更适合我们的需求。

App.js

jsx 复制代码
function App() {
  return <h1>Hello, Front789!</h1>;
}

export default App;

index.js

jsx 复制代码
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <>
    <App />
  </>
);

安装 styled-components

安装 styled-components 的命令如下:

shell 复制代码
npm install styled-components

虽然我们这个项目就寥寥几个文件,但是它已经支持了styled-components的功能了。下面,我们就来学习一下它是如何工作的。


2. 基本用法

app.js 中,

  • 使用 <h1> 标签创建一个标题
  • 使用 <p> 标签创建一个段落
  • 使用 <button> 标签创建一个按钮
jsx 复制代码
function App() {
  return (
    <div>
      <h1>Front789</h1>
      <p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
      <button>我是按钮!</button>
    </div>
  );
}

export default App;

我们需要在 app.js 文件中导入 styled-components

jsx 复制代码
import styled from "styled-components";

然后我们将创建我们的自定义组件 H1,并使用它代替 <h1> 标签,并添加自定义样式。

jsx 复制代码
const H1 = styled.h1`
  color: red;
  font-size: 4rem;
`;
  • 首先,我们需要给它一个自定义的名称(H1)。
  • 然后,我们将从 styled.<HTML 标签名称> 开始,并用反引号括起样式。

现在,当我们使用这个自定义组件时,它将具有带有样式的 <HTML 标签名称> 属性。

jsx 复制代码
import styled from "styled-components";

const H1 = styled.h1`
  color: red;
  font-size: 4rem;
`;

function App() {
  return (
    <div>
      <H1>Front789</H1>
      <p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
      <button>我是按钮!</button>
    </div>
  );
}

export default App;

我们在页面中就会看多对应的效果图。

上面有几个点需要注意

  1. 我们使用了 styled.h1 来创建 H1,此时H1就是一个自定义组件,在 React 中, 始终使用大写字母来自定义组件名称
  2. 我们在浏览器DevTool->Elements种看到,与H1对应的h1元素自动添加了一个class,并且其值是一组hash值,这样做是为了避免命名冲突

现在让我们为我们的按钮组件添加样式:

jsx 复制代码
const DefaultButton = styled.button`
  background-color: #645cfc;
  border: none;
  padding: 10px;
  color: white;
`;

DefaultButton 是我们的自定义组件名称。在我们给它样式之后,我们可以给它任何我们想要的 HTML 标签,以便这个自定义组件将拥有该标签。

现在我们将使用上面创建的 DefaultButton 作为我们的自定义组件在 React.js 中使用。

jsx 复制代码
import styled from "styled-components";

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

const DefaultButton = styled.button`
  background-color: #645cfc;
  border: none;
  padding: 10px;
  color: white;
`;

function App() {
  return (
    <div>
      <H1>Front789</H1>
      <p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
      <DefaultButton>我是按钮!</DefaultButton>
    </div>
  );
}

export default App;

我们也可以通过为每个不同的组件在 styled-components 中创建一个不同的文件来保持我们的文件清晰。

我们将在 src 中创建一个名为 components 的新文件夹,并创建文件 Title.jsButtons.js 来分离标题和按钮的样式。

我们将 H1 样式复制并粘贴到 Title.js 中,并将 DefaultButton 样式复制并粘贴到 Buttons.js 中。

现在我们的App.js 看起来是这样的:

jsx 复制代码
import H1 from "./components/Title";
import { DefaultButton } from "./components/Buttons";

function App() {
  return (
    <div>
      <H1>Front789</H1>
      <p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
      <DefaultButton>我是按钮!</DefaultButton>
    </div>
  );
}

export default App;

3. 使用 Props

对于React组件来讲,Props是一个至关重要的特性,通过Props我们可以从组件调用处向组件内部传入对应的运行时参数,然后基于运行时的逻辑进行展示操作。

使用styled components定义的组件也可以接受props

jsx 复制代码
import H1 from './components/Title'
import {DefaultButton} from './components/Buttons'


function App() {
  return (
    <div>
      <H1>Front789</H1>
      <p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
      <DefaultButton>我是按钮!</DefaultButton>
      <DefaultButton red>带属性的按钮!</DefaultButton>
    </div>
  );
}

export default App;

在上面的代码中,我创建了另一个 DefaultButton。但是相较于之前的DefaultButton,第二个DefaultButton拥有了额外的属性red

也就是说,我们希望第二个DefaultButton在运行时执行额外的展示逻辑。

jsx 复制代码
import styled from 'styled-components'

export const DefaultButton = styled.button`
background-color:  ${(props) => (props.red && 'red') || '#645cfc'};
border: none;
padding: 10px;
color: white;
`

我们在使用styled-components定义组件时,使用了模板字面量也就意味着可以在其中写 JavaScript。在这些大括号中,我们声明了一个箭头函数,它有一个 props 参数,可以访问自定义组件的属性。箭头函数表示如果给定了 red 属性,则背景颜色应为红色,否则应为蓝莓色。

当然,我们还可以通过对props进行解构处理,通过 {} 和属性名称来解构 props

与其使用 props.red 进行访问,我们可以。

jsx 复制代码
import styled from 'styled-components'

export const DefaultButton = styled.button`
background-color:  ${({red}) => (red && 'red') || '#645cfc'};
border: none;
padding: 10px;
color: white;
`

现在我们可以直接使用 red 而不是 props.red


4. 扩展样式

通过上述的操作,我们已经拥有了一定样式封装能力的自定义组件了。此时,我们想在之前组件的基础上进行二次封装。从语言开发的角度来讲,就是我们想继承之前的样式,并且做额外的操作。此时我们可以使用在 styled components 中扩展样式来实现。

我们只需要简单一步操作即可完成。之前我们是用styled.<html 标签>来定义自定义组件,而进行样式扩展的话,我们可以使用styled(xx)

我们以DefaultButton为例,想要在DefaultButton样式的基础上做额外的扩展,我们可以通过styled(DefaultButton)来重新定义一个新的组件,并且在实现过程中,它拥有除了DefaultButton之外的样式逻辑。

ExtendedButton定义

jsx 复制代码
export const ExtendedButton = styled(DefaultButton)`
display: block;
width: 100vw;

App.js

jsx 复制代码
import H1 from "./components/Title";
import { DefaultButton,ExtendedButton } from "./components/Buttons";

function App() {
  return (
   <div>
      <H1>Front789</H1>
      <p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
      <DefaultButton>我是按钮!</DefaultButton>
      <ExtendedButton red>扩展按钮!</ExtendedButton>
  </div>
  );
}

export default App;

这样,我们就可以拥有一个在DefaultButton样式基础上,扩展了width:100vw的新组件。


5. 嵌套样式

当然,现在的前端样式已经不满足之前介绍的针对单个元素的样式封装。我们还可以拥有像less/scss一样的样式嵌套。这样我们就可以在一个样式逻辑种处理其内部的多个子元素。实现更好的封装。

jsx 复制代码
import styled from 'styled-components';

const Wrapper = styled.div`
h1{
  text-align: center;
  color: violet;
}

p{
  font-size: 40px;
}

button{
  background-color: pink;
  padding: 4px 8px;
  border: none;  
}
`
function App() {
  return (
    <div>
      <Wrapper>
        <h1>Front789</h1>
        <p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
        <button>我是按钮!</button>
      </Wrapper>
  </div>
  );
}

export default App;

当我们在页面中使用了Wrapper后,内部的所有<p>标签都将具有 40px 的字体大小,<button>将具有粉色的背景颜色、指定的填充和无边框。


6. 扩展 React 组件

我们使用styled components还可以处理用常规方式构建的React组件。此时,我们只需要将之前的组件放到styled(xx)中即可。(需要做一点小的变更)

假如我们有如下的 react 组件

jsx 复制代码
const Oldcom = () => {
  return (
    <div>
        <h2>Front789</h2>
        <button>按钮!</button>
    </div>
  )
}

如果我们想通过styled components对其处理,我们需要对其做一下改造。需要在props中接受className,并且讲其放置到组件的根元素上,然后就可以利用styled components嵌套样式对其内部的元素进行样式处理。

jsx 复制代码
import React from 'react'
import styled from 'styled-components'

export const Oldcom = ({className}) => {
  return (
    <div className={className}>
        <h2>Front789</h2>
        <button>按钮!</button>
    </div>
  )
}

export const NewCom = styled(Oldcom)`
h2{
    color: green;
    text-align: center;
}

button{
    padding: 4px 10px;
    background-color: violet;
    border: none;
}
`

7. CSS变量

使用styled components构建的组件,还支持使用css变量。这样,我们在组件内部接收一些团队定义的变量,来处理指定的样式逻辑。

让我们来看看它是如何实现的。

现在在 src 文件夹中创建一个 index.css 文件,该文件中编写一些 CSS 变量,这些变量是从任何地方都可以访问的全局样式

index.css

css 复制代码
:root{
--primary-color: #8F00FF
}

现在 :root{} 就像在 CSS 中选择 html{} 一样。但是 :root{} 的优先级比 html{} 更高。

CSS有两种方式来选择HTML文档的根元素

  1. :root 伪类
  2. html 选择器

选择器的特异性

:root 选择器的优先级高于 html 选择器。这是因为 :root 是一个伪类选择器,而 html 是一个类型选择器

css 复制代码
:root {
 background-color: red;
}
html {
 background-color: blue;
}
/* HTML 文档的根元素将具有红色的背景颜色。 */

目标化根元素

除了 HTML 外,CSS 还可以用于样式化其他类型的文档。这就是 :root 元素发挥作用的地方,它允许你样式化文档的根元素。当样式化 SVG 文档时,这可能特别重要,因为 html 选择器不起作用。

然后,我们可以在styled components定义的组件种使用这个css变量。(当然,别忘了在index.js中导入index.css

jsx 复制代码
const Newcom = styled(Oldcom)`
h2{
    color: green;
    text-align: center;
}

button{
    padding: 4px 10px;
    background-color: var(--primary-color);
    border: none;
}

8. 添加主题

有些网站还需要一些明暗主题的切换。使用styled components可以轻松实现这一点。

  1. 首先,我们需要从 styled components 中导入 ThemeProvider
  2. 然后将整个应用程序包装在 ThemeProvider 中,并在其中提供我们的主题。
  3. 使用styled component定义一个组件(Container),在其内部可以访问主题及其属性,并帮助用户更改背景颜色和文本颜色
  4. 我们可以定义一个操作(按钮点击)来更换theme变量 具体实现代码如下:
jsx 复制代码
import styled, { ThemeProvider } from 'styled-components'
import React, { useState } from 'react'
import {DefaultButton } from './components/Buttons'

const lightTheme = {
  background: '#fff',
  color: '#222',
}
const darkTheme = {
  background: '#222',
  color: '#fff',
}

const Container = styled.div`
color: ${(props) => props.theme.color};
background-color: ${(props) => props.theme.background};
height: 100vh;
`
function App() {
  const [theme,setTheme] = useState(lightTheme)
  return (
    <ThemeProvider theme={theme}>
      <DefaultButton onClick={() => 
        setTheme(theme === lightTheme ? darkTheme : lightTheme)
      }>切换主题</DefaultButton>
      <Container>
     	  <p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
      </Container>
    </ThemeProvider>
  );
}

export default App;

9. 处理动画

styled components还支持动画,我们可以从 styled-components 中导入 keyframes,用它来创建动画。

jsx 复制代码
import styled, {keyframes} from 'styled-components'

const spinner = keyframes`
to{
 transform: rotate(360deg);
}
`

const Loading = styled.div`
width: 6rem;
height: 6rem;
border: 5px solid #ccc;
border-radius: 50%;
border-top-color: black;
animation: ${spinner} 0.6s linear infinite;
`

export default Loading

在上面的代码中,我们定义了 spinner 动画,spinner 是动画的名称。在 Loading 中,我们使用了 spinner 动画名称,以便 Loading 使用该动画。


10. 使用 as 属性

如果我有一个按钮,并给它一个 href 属性,我们用它来跳转到另一个网站,它将不起作用。这是因为 href<a>标签的特有属性。

jsx 复制代码
import React from 'react'
import {DefaultButton} from './components/Buttons'

const App = () => {
  return (
    <div>
      <DefaultButton as="a" href="https://www.google.com">跳转到指定网站</DefaultButton>
    </div>
  )
}

export default App

运行代码后,我们发现是跳转不了的,但是呢,通过styled-components定义的组件,我们可以使用as ="x",使其给定的组件能够拥有x的功能。

jsx 复制代码
import React from 'react'
import {DefaultButton} from './components/Buttons'

const App = () => {
  return (
    <div>
      <DefaultButton 
        as='a' 
        href="https://www.google.com"
      >
        跳转到指定网站
      </DefaultButton>
    </div>
  )
}

export default App

现在 DefaultButton 是一个<a>

  • 它将具有 text-decoration,因为在默认锚标签中它具有 text-decoration
  • 当我们点击我们的 DefaultButton 时,它将打开 Google

11. 默认属性

HTML 的某些元素上我们有属性。例如在按钮上,我们有 type="submit"type="button"。但是每次我们都必须手动设置它们。

jsx 复制代码
import React from 'react'
import styled from 'styled-components'

const Button = styled.button`
border: none;
padding: 5px 10px;
background-color: #87CD11;
margin: 10px;
`

const App = () => {
  return (
    <div>
      <Button type="button">点击!</Button>
      <Button type="submit">提交</Button>
    </div>
  )
}

export default App

使用styled-components定义的组件,设置默认属性非常方便。我们可以将对象或函数传递给它们。但是,如果我们将对象传递给属性,那么它们将是静态的。为了具有动态控制,我们将一个函数传递给属性。

jsx 复制代码
import React from 'react'
import styled from 'styled-components'

const Button = styled.button.attrs((props) => {
  return {type: props.type || "button"}
})`
border: none;
padding: 5px 10px;
background-color: #87CD11;
margin: 10px;
`

const App = () => {
  return (
    <div>
      <Button>点击!</Button>
      <Button type="submit">提交</Button>
    </div>
  )
}

export default App

在上面的代码中,我们为按钮设置了属性。这意味着

  • 如果给定了 type,则它将将该 type 设置为给定的 props
  • 如果没有给定 props,则默认将其设置为按钮

后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和"在看"吧。

相关推荐
mldong5 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排5 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60616 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角7 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!7 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰7 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi9 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen9 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马10 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu10 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss