❝
一周是一年的2%
❞
前言
最近在做项目梳理,然后无意中在一些国外的UI库中发现如下的代码示例。
大家仔细观察上面的代码,其实就是对常规的布局做了封装,我们可以通过通过
{chilren}
将对应的组件进行包裹。
这样做的好处就是
-
见名知意,通过组件的名称我们就可以知晓该页面使用了何种布局
-
-
然后,它背后用的技术就是我们在
CSS-in-JS
。针对
CSS-in-JS
业界是褒贬不一。
上面列举了
CSS-in-JS
的各种利弊。这其实就是仁者见仁,智者见智。但是,我更看中它在抽离公共布局方面的应用。就像最开头的截图所示,我们可以不把现有项目中所有组件都
css-in-js
处理,但是我们可以对系统种常规布局进行抽离,这样我们项目就层级就更加清晰明了。
既然,它是有用的,那么我们今天就来聊聊
CSS-in-JS
。因为,
CSS-in-JS
有很多解决方案。(emotion
[1]
/styled-components
[2]
)。
下面,我们就挑业界比较受欢迎的
styled-components
来进行讲解。
好了,天不早了,干点正事哇。
我们能所学到的知识点
❝
-
-
-
-
-
-
-
-
-
-
-
❞
❝
Styled-components
[3]
是一个库,它允许你在构建
Reactjs
自定义组件时,使用
JavaScript
写
CSS
。
❞
1. 初始化项目
由于我们这里是一个技术讲解的文章,不需要额外的配置,所以我们就不用我们的f_cli
[4]
来构建项目了,我们就用最简单的方式(
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
中,
function App() {
return (
<div>
<h1>Front789h1>
<p>专注于前端开发技术/Rust及AI应用知识分享的Coderp>
<button>我是按钮!button>
div>
);
}
export default App;
我们需要在
app.js
文件中导入
styled-components
:
import styled from "styled-components";
然后我们将创建我们的
自定义组件 H1
,并使用它代替
标签,并添加自定义样式。
const H1 = styled.h1`
color: red;
font-size: 4rem;
`;
-
-
然后,我们将从
styled.
开始,并用
「反引号」
括起样式。
现在,当我们使用这个
自定义组件
时,它将具有带有样式的
属性。
import styled from "styled-components";
const H1 = styled.h1`
color: red;
font-size: 4rem;
`;
function App() {
return (
<div>
<H1>Front789H1>
<p>专注于前端开发技术/Rust及AI应用知识分享的Coderp>
<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>Front789H1>
<p>专注于前端开发技术/Rust及AI应用知识分享的Coderp>
<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>Front789H1>
<p>专注于前端开发技术/Rust及AI应用知识分享的Coderp>
<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>Front789H1>
<p>专注于前端开发技术/Rust及AI应用知识分享的Coderp>
<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.
来定义自定义组件,而进行样式扩展的话,我们可以使用
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>Front789H1>
<p>专注于前端开发技术/Rust及AI应用知识分享的Coderp>
<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>Front789h1>
<p>专注于前端开发技术/Rust及AI应用知识分享的Coderp>
<button>我是按钮!button>
Wrapper>
div>
);
}
export default App;
当我们在页面中使用了
Wrapper
后,内部的所有
标签都将具有 40px 的字体大小,
将具有粉色的背景颜色、指定的填充和无边框。
6. 扩展 React 组件
我们使用
styled components
还可以处理用常规方式构建的
React
组件。此时,我们只需要将之前的组件放到
styled(xx)
中即可。(需要做一点小的变更)
假如我们有如下的
react
组件
const
Oldcom = () => {
return (
<div>
<h2>Front789h2>
<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>Front789h2>
<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 {
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应用知识分享的Coderp>
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
使用该动画。