一周是一年的2%
大家好,我是柒八九。一个专注于前端开发技术/Rust
及AI
应用知识分享的Coder
前言
最近在做项目梳理,然后无意中在一些国外的UI库中发现如下的代码示例。
大家仔细观察上面的代码,其实就是对常规的布局做了封装,我们可以通过通过<XXX >{chilren}</XXX>
将对应的组件进行包裹。
这样做的好处就是
- 见名知意,通过组件的名称我们就可以知晓该页面使用了何种布局
- 布局样式和组件内部样式进行分割
- 统一管理
然后,它背后用的技术就是我们在CSS-in-JS
。针对CSS-in-JS
业界是褒贬不一。
上面列举了CSS-in-JS
的各种利弊。这其实就是仁者见仁,智者见智。但是,我更看中它在抽离公共布局方面的应用。就像最开头的截图所示,我们可以不把现有项目中所有组件都css-in-js
处理,但是我们可以对系统种常规布局进行抽离,这样我们项目就层级就更加清晰明了。
既然,它是有用的,那么我们今天就来聊聊CSS-in-JS
。因为,CSS-in-JS
有很多解决方案。(emotion/styled-components)。
下面,我们就挑业界比较受欢迎的styled-components
来进行讲解。
好了,天不早了,干点正事哇。
我们能所学到的知识点
- 初始化项目
- 基本用法
- 使用 Props
- 扩展样式
- 嵌套样式
- 扩展 React 组件
- CSS变量
- 添加主题
- 处理动画
- 使用 as 属性
- 默认属性
Styled-components 是一个库,它允许你在构建
Reactjs
自定义组件时,使用JavaScript
写CSS
。
1. 初始化项目
由于我们这里是一个技术讲解的文章,不需要额外的配置,所以我们就不用我们的f_cli来构建项目了,我们就用最简单的方式(cra
)来构建项目(当然也可以使用vite
)
npx create-react-app styled_demo
由于每个脚手架都有自己内置的逻辑,我们需要删除一些默认的逻辑。在初始化后,我们只保留index.js
和app.js
。并且对其做一些简单的修改,使其更适合我们的需求。
App.js
function App() {
return <h1>Hello, Front789!</h1>;
}
export default App;
index.js
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
的命令如下:
npm install styled-components
虽然我们这个项目就寥寥几个文件,但是它已经支持了styled-components
的功能了。下面,我们就来学习一下它是如何工作的。
2. 基本用法
在 app.js
中,
- 使用
<h1>
标签创建一个标题 - 使用
<p>
标签创建一个段落 - 使用
<button>
标签创建一个按钮
function App() {
return (
<div>
<h1>Front789</h1>
<p>专注于前端开发技术/Rust及AI应用知识分享的Coder</p>
<button>我是按钮!</button>
</div>
);
}
export default App;
我们需要在 app.js
文件中导入 styled-components
:
import styled from "styled-components";
然后我们将创建我们的自定义组件 H1
,并使用它代替 <h1>
标签,并添加自定义样式。
const H1 = styled.h1`
color: red;
font-size: 4rem;
`;
- 首先,我们需要给它一个自定义的名称(
H1
)。 - 然后,我们将从
styled.<HTML 标签名称>
开始,并用反引号括起样式。
现在,当我们使用这个自定义组件
时,它将具有带有样式的 <HTML 标签名称>
属性。
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;
我们在页面中就会看多对应的效果图。
上面有几个点需要注意
- 我们使用了
styled.h1
来创建H1
,此时H1
就是一个自定义组件,在React
中,
始终使用大写字母来自定义组件名称 - 我们在浏览器
DevTool->Elements
种看到,与H1
对应的h1
元素自动添加了一个class
,并且其值是一组hash
值,这样做是为了避免命名冲突
现在让我们为我们的按钮组件添加样式:
const DefaultButton = styled.button`
background-color: #645cfc;
border: none;
padding: 10px;
color: white;
`;
DefaultButton
是我们的自定义组件名称。在我们给它样式之后,我们可以给它任何我们想要的 HTML
标签,以便这个自定义组件将拥有该标签。
现在我们将使用上面创建的 DefaultButton
作为我们的自定义组件在 React.js
中使用。
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.js
和 Buttons.js
来分离标题和按钮的样式。
我们将 H1
样式复制并粘贴到 Title.js
中,并将 DefaultButton
样式复制并粘贴到 Buttons.js
中。
现在我们的App.js
看起来是这样的:
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
。
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
在运行时执行额外的展示逻辑。
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
进行访问,我们可以。
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定义
export const ExtendedButton = styled(DefaultButton)`
display: block;
width: 100vw;
App.js
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
一样的样式嵌套。这样我们就可以在一个样式逻辑种处理其内部的多个子元素。实现更好的封装。
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
组件
const Oldcom = () => {
return (
<div>
<h2>Front789</h2>
<button>按钮!</button>
</div>
)
}
如果我们想通过styled components
对其处理,我们需要对其做一下改造。需要在props
中接受className
,并且讲其放置到组件的根元素上,然后就可以利用styled components
嵌套样式对其内部的元素进行样式处理。
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
:root{
--primary-color: #8F00FF
}
现在 :root{}
就像在 CSS
中选择 html{}
一样。但是 :root{}
的优先级比 html{}
更高。
CSS有两种方式来选择HTML文档的根元素
:root 伪类
html 选择器
选择器的特异性
:root
选择器的优先级高于html
选择器。这是因为:root
是一个伪类选择器
,而html
是一个类型选择器
。
:root { background-color: red; } html { background-color: blue; } /* HTML 文档的根元素将具有红色的背景颜色。 */
目标化根元素
除了
HTML
外,CSS
还可以用于样式化其他类型的文档。这就是:root
元素发挥作用的地方,它允许你样式化文档的根元素。当样式化SVG
文档时,这可能特别重要,因为html
选择器不起作用。
然后,我们可以在styled components
定义的组件种使用这个css变量
。(当然,别忘了在index.js
中导入index.css
)
const Newcom = styled(Oldcom)`
h2{
color: green;
text-align: center;
}
button{
padding: 4px 10px;
background-color: var(--primary-color);
border: none;
}
8. 添加主题
有些网站还需要一些明暗主题
的切换。使用styled components
可以轻松实现这一点。
- 首先,我们需要从
styled components
中导入ThemeProvider
。 - 然后将整个应用程序包装在
ThemeProvider
中,并在其中提供我们的主题。 - 使用
styled component
定义一个组件(Container
),在其内部可以访问主题及其属性,并帮助用户更改背景颜色和文本颜色 - 我们可以定义一个操作(按钮点击)来更换
theme
变量
具体实现代码如下:
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
,用它来创建动画。
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>
标签的特有属性。
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
的功能。
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"
。但是每次我们都必须手动设置它们。
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
定义的组件,设置默认属性非常方便。我们可以将对象或函数传递给它们。但是,如果我们将对象传递给属性,那么它们将是静态的。为了具有动态控制,我们将一个函数传递给属性。
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
,则默认将其设置为按钮
后记
分享是一种态度。
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。
原文链接:https://juejin.cn/post/7347962680604983350 作者:前端小魔女