styled-components 库的实践用法总结
前言
前段时间开发了一个 NiceTab 欣赏器插件,并写了一篇介绍文章,新开发了一款欣赏器Tab管理插件,OneTab 的升级更换品, 欢迎品尝!。
在插件中用到了 styled-components 这个库,于是做一个基本的介绍和分享。
在开发 NiceTab 插件时,只是一些粗浅的利用,整理完这篇利用条记后,我准备优化一波了。
styled-components 库介绍
什么是 styled-components
styled-components 是一个流行的 CSS-in-JS 库, 它允许您在 JavaScript 中编写 CSS 样式,是 CSS-in-JS 方案中的一种实现方式。
styled-components is the result of wondering how we could enhance CSS for styling React component systems. By focusing on a single use case we managed to optimize the experience for developers as well as the output for end users.
- 官方文档: 官方文档
- Github: Github
为什么要利用 styled-components
我们看看官网介绍:
- Automatic critical CSS - 主动关键CSS: 完全主动地跟踪页面上呈现的组件并注入它们的样式。结合代码分割,按需加载。
- No class name bugs - 不存在类名冲突问题: 生成唯一的类名。不消担心类名重复、冲突、覆盖以及拼写错误等问题。
- Easier deletion of CSS - 便于样式剔除: 传统的编写样式方式很难检测样式和类名是否被利用。而 styled-components 会与特定的组件关联,在工程化项目中,构建工具可以轻易的检测到样式组件是否被利用,未被利用的样式组件会被剔除,避免无用代码打包到构建产物中。
- Simple dynamic styling - 易于编写动态样式: styled-components 样式组件可以根据 props 和全局主题来动态调解样式,无需手动管理众多样式类。
- Painless maintenance - 便于维护: 不须要查找众多文件来排查组件样式,降低维护本钱。
- Automatic vendor prefixing - 主动添加厂商前缀: 您只须要编写标准的 CSS 样式,其他的事情 styled-components 主动帮您处理。
传统 CSS 方式的缺点:
- 缺乏作用域,全局样式污染,容易出现类名冲突,样式覆盖问题,出现问题难以排查。须要通过 namespace 命名空间、书写顺序、优先级等方式来缓解。
- 原生 CSS 在没有 scss,less 等预处理器的情况下,编写起来非常痛苦。
- 实现动态样式,须要预先界说多个类名,为不同类目编写对应的样式,然后根据情况动态绑定不同的类名。
开发中感受:
- 编写一个功能组件,大概只须要添加少许样式 (现在的项目大多都会利用 UI 组件库,自带各种样式),为此而创建一个 css 文件不划算,还大概会有全局样式污染。只须要在该功能组件中界说一个 styled-components 样式组件即可。
- 在 html 标签元素书写行内样式大概绑定 style 变量都难以界说伪类样式和复杂选择器(如::hover, :before, :after, :first-child, &选择器, +选择器等等)
- 如果编写的功能组件须要编写的样式比较多,可以抽取到一个 js 或 ts 文件中,然后引入即可,由于 styled-components 界说的都是 js 对象。
- 支持 scss 风格的语法。
- 快速定位样式界说代码:传统的方式,须要全局搜索元素类名,才华定位到样式代码。而 js 方式的界说,编辑器能快速跳转定位。
styled-components 的缺点:
- 生成的类名是一串 hash 值, 会绑定到对应的 dom 标签上,难以定位元素,建议给 styled-components 样式组件都添加一个 class 样式名,便于辨认。
其他 css-in-js 方案
利用 css-in-js 方案的库,比较常见的另有 emotion 和 jss, 各人可以自行实验利用。
styled-components 的基本利用
安装
- npm 安装:npm install styled-components
- pnpm 安装:pnpm add styled-components
- yarn 安装:yarn add styled-components
如果您利用像 yarn 这种支持 package.json 的 resolutions 字段的包管理器,剧烈建议您向此中添加一个与主版本对应的入口配置。这有助于避免因项目中安装了多个版本的 styled-components 而引起的一系列问题。
基本用法
参考链接:getting-started
先上一个示例:
- import styled from "styled-components";
- // 创建一个 Title 样式组件,使用 <h1> 标签
- const Title = styled.h1`
- font-size: 24px;
- text-align: center;
- color: #fff;
- `;
- // 创建一个 Wrapper 样式组件,使用 <section> 标签
- const Wrapper = styled.section`
- padding: 30px;
- background: #000;
- `;
- // 在 jsx 中使用
- export default function App() {
- return (
- <Wrapper>
- <Title>Hello World!</Title>
- </Wrapper>
- );
- }
复制代码 利用起来挺简单的,除了上面示例中的 h1 和 section 标签,你还可以根据情况利用其他 html 标签。
SCSS 风格语法
你可以像编写 SCSS 样式一样,编写 styled-components 的样式, 比如选择器嵌套、伪类样式、各种选择器语法等
- import styled from "styled-components";
- // 创建一个 Wrapper 样式组件,使用 <section> 标签
- const Wrapper = styled.section`
- padding: 30px;
- background: #000;
- .text {
- color: #fff;
- &.red {
- color: red;
- }
- }
- button {
- color: #333;
- &:hover {
- color: blue;
- }
- & + button {
- margin-left: 10px;
- }
- }
- `;
- // 在 jsx 中使用
- export default function App() {
- return (
- <Wrapper>
- <div className="text"> Hello World! </div>
- <div className="text red"> Good good study, day day up! </div>
- <button>取消</button>
- <button>确定</button>
- </Wrapper>
- );
- }
复制代码 另有一个比较厉害的功能 &&,它可以指向当前样式组件的实例,这在复杂的样式逻辑中非常实用。这个各人可以直接参考官方示例-&&会比较清晰。
- const Input = styled.input.attrs({ type: "checkbox" })``;
- const Label = styled.label`
- align-items: center;
- display: flex;
- gap: 8px;
- margin-bottom: 8px;
- `
- const LabelText = styled.span`
- ${(props) => {
- switch (props.$mode) {
- case "dark":
- return css`
- background-color: black;
- color: white;
- ${Input}:checked + && {
- color: blue;
- }
- `;
- default:
- return css`
- background-color: white;
- color: black;
- ${Input}:checked + && {
- color: red;
- }
- `;
- }
- }}
- `;
- export default function App() {
- return (
- <React.Fragment>
- <Label>
- <Input defaultChecked />
- <LabelText>Foo</LabelText>
- </Label>
- <Label>
- <Input />
- <LabelText $mode="dark">Foo</LabelText>
- </Label>
- <Label>
- <Input defaultChecked />
- <LabelText>Foo</LabelText>
- </Label>
- <Label>
- <Input defaultChecked />
- <LabelText $mode="dark">Foo</LabelText>
- </LabelText>
- </React.Fragment>
- )
- }
复制代码 上面代码示例摘取自官方示例,此中的 && 就代表 LabelText 组件实例
${Input}:checked + && { color: red; } 表示 选中状态的 checkbox 紧邻的 LabelText 的文字颜色为红色。
命名规范建议
为了统一代码规范,让代码逻辑一目了然。提供几点建议:
- styled-components 创建的样式组件以 StyledXXX 格式举行命名,以便和常规的功能组件区分开来,比如 StyledTodoList。
- 一个功能组件中,styled-components 样式组件不多的话,可以直接在当前功能组件中界说并利用。如果界说的样式组件比较多,建议将样式组件单独提取到一个文件。
- 提取的样式组件的文件名统一格式为 xxx.styled.js, typescript 项目则利用 xxx.styled.ts, 比如 TodoList.tsx 功能组件, 对应的样式文件则为 TodoList.styled.ts。
固然,不一定非要上面的格式命名,只要制定一个统一的规范即可。
- // TodoList 组件
- import styled from "styled-components";
- // TodoListItem 是 React 功能组件
- import TodoListItem from './TodoListItem';
- const StyledTodoList = styled.ul`
- broder: 1px solid #999;
- // ...
- `;
- export default function TodoList() {
- return (
- <StyledTodoList>
- <TodoListItem />
- <TodoListItem />
- </StyledTodoList>
- )
- }
复制代码 统一规范之后,一眼就能分辨出,StyledTodoList 是界说的样式组件(界说样式),而 TodoListItem 是功能组件,处理功能和交互逻辑。
另外须要注意的是:记得在 React 组件渲染方法之外界说样式组件,否则将会在每次 render 时重新创建。
剧烈推荐:
- // 推荐方式
- const StyledButton = styled.button`
- color: #333;
- `;
- const App = ({ text }) => {
- return <StyledButton>{text}</StyledButton>
- };
复制代码 不建议:
- const App = ({ text }) => {
- const StyledButton = styled.button`
- color: #333;
- `;
- return <StyledButton>{text}</StyledButton>
- };
复制代码 传递 props 参数
参考链接:adapting-based-on-props
styled-components 创建的组件就是个组件对象,你可以像利用 React 组件一样利用它们。
对于组件来说,props 传参至关重要,可以动态调解逻辑和样式。
- import styled from "styled-components";
- // 基础按钮
- const StyledBaseButton = styled.button`
- background: ${props => props.$bgColor || '#fff'};
- color: ${props => props.$color || '#333'};
- `;
- export default function BaseButton() {
- return (
- <div>
- <StyledBaseButton>取消</StyledBaseButton>
- <StyledBaseButton $bgColor="blue" $color="#fff">确定</StyledBaseButton>
- </div>
- )
- }
复制代码 下面我们看一个 typescript 的示例:
- import styled from "styled-components";
- // 基础按钮
- const StyledBaseButton = styled.button<{ $bgColor?: string; $color?: string; }>`
- background: ${props => props.$bgColor || '#fff'};
- color: ${props => props.$color || '#333'};
- `;
复制代码 在 typescript 项目中,会根据类型界说检测组件上绑定的 props 属性,错传、漏传、多传都会举行提示。
样式继承和扩展
参考链接:extending-styles
我们想要在一个样式组件的底子上扩展其他样式,只须要利用 styled() 包裹,然后添加所需样式即可,比方:
- import styled from "styled-components";
- // 基础按钮
- const StyledBaseButton = styled.button<{ $bgColor?: string; $color?: string; }>`
- background: ${props => props.$bgColor || '#fff'};
- color: ${props => props.$color || '#333'};
- `;
- // 边框按钮const StyledBorderButton = styled(StyledBaseButton)<{$borderColor?: string}>` border: 1px solid ${props => props.$borderColor || '#ccc'};`;export default function App { return ( <StyledBorderButton $borderColor="#999"></StyledBorderButton> )}
复制代码 如许就可以在底子样式组件上扩展其他样式,用来承接业务逻辑。
有些时候,我们想复用底子样式组件,但是 底子样式组件的 html 元素标签类型又不适用,这种情况可以利用 as 属性来动态更换底子样式组件的元素标签。
下面示例,是复用 StyledBaseButton 的样式,并将 button 标签更换为 a 标签。
- import styled from "styled-components";
- // 基础按钮
- const StyledBaseButton = styled.button<{ $bgColor?: string; $color?: string; }>`
- background: ${props => props.$bgColor || '#fff'};
- color: ${props => props.$color || '#333'};
- `;
- export default function App { return ( <StyledBorderButton as="a" href="https://example.com" $bgColor="blue" $color="#fff"></StyledBorderButton> )}
复制代码 界说公共样式
我们经常会有利用公共样式的场景,比如 ellipsis 来实现文字超长省略。
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
复制代码 这种场景非常多,没须要在每个地方都写一遍上面的这一段代码,这时候可以利用 styled-components 导出的 css 函数可以生成一个公共样式组件, 建议单独创建一个样式文件存放公共样式。
我们创建一个 Common.styled.ts 文件:
- // Common.styled.ts
- import styled, { css } from "styled-components";
- export const StyledCommonEllipsis = css`
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- `;
- // ......
复制代码 然后在 Title.tsx 组件中利用:
- // Title.tsx
- import styled from "styled-components";
- import { StyledCommonEllipsis } from '@/common/styled/Common.styled.ts'
- const StyledTitle = styled.div`
- width: 300px;
- color: #333;
- ${StyledEllipsis};
- `;
- export default function App() {
- return <StyledTitle>这是很长的一段文字标题这是很长的一段文字标题这是很长的一段文字标题</StyledTitle>
- }
复制代码 上面这种方式是通过引用公共样式组件实现的,还可以直接利用原始的方式来界说一个公共class样式,全局引入公共样式,然后在元素标签上利用class名即可。
- /* common.css */
- .ellipsis {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
复制代码- /* Title.tsx */
- export default function App() {
- return <StyledTitle className="ellipsis">这是很长的一段文字标题</StyledTitle>
- }
复制代码 全局样式
参考链接:createglobalstyle
注意:版本要求 v4 以上,web-only.
通过 createGlobalStyle 方法也可以注入全局公共样式。
- import { createGlobalStyle } from 'styled-components';
- const GlobalStyle = createGlobalStyle<{ $whiteColor?: boolean; }>`
- body {
- color: ${props => (props.$whiteColor ? 'white' : 'black')};
- }
- .ellipsis {
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- `;
- export default function App() {
- return <React.Fragment>
- <GlobalStyle $whiteColor />
- <Navigation />
- </React.Fragment>
- };
复制代码 适用 React 组件
参考链接:styling-any-component
styled 方法可以在任何 React 组件上完美运行,只须要给组件传递 className 属性,并在组件内的 dom 元素上绑定该 className 属性即可。
- import styled from "styled-components";
- // TodoList 组件
- const TodoList = ({ className, children }) => (
- <ul className={className}>
- <li>
- <checkbox></checkbox>
- <span>待办项</span>
- </li>
- </ul>
- );
- // 横向 inline 布局
- const StyledTodoList = styled(TodoList)`
- display: flex;
- align-items: center;
- li {
- display: flex;
- align-items: center;
- padding: 8px 16px;
- }
- `;
- export default function App {
- return (
- <TodoList></TodoList>
- <StyledTodoList></StyledTodoList>
- )
- }
复制代码 如许就可以改变组件的内部样式了。
attrs 属性设置
参考链接:attaching-additional-props
有些元素标签自带部分属性,比如 input 元素 的 type 属性有 text|email|color|file|radio|checkbox 等等类型,我们在利用时须要手动设置这些属性。
再比如常用的 button 按钮的 type 属性有 button|submit|reset 这些类型。
利用 .attrs 方法,我们可以预设一些属性,并且根据情况动态修改这些属性。
- import styled from "styled-components";
- const StyledInput = styled.input.attrs(props => ({
- type: props.type || 'text'
- }))`
- color: '#333';
- font-size: 14px;
- `;
- const StyledFileInput = styled.input.attrs({ type: 'file' })`
- width: 40px;
- height: 40px;
- border-radius: 50%;
- `;
- const StyledButton = styled.button.attrs(props => ({
- type: props.type || 'button'
- }))`
- border: none;
- outline: none;
- background: blue;
- color: #fff;
- `;
- export default function App {
- return (
- <div>
- <StyledInput></StyledInput>
- <StyledInput type="email"></StyledInput>
- <StyledInput type="password"></StyledInput>
- <StyledFileInput></StyledFileInput>
- <StyledButton></StyledButton>
- <StyledButton type="submit"></StyledButton>
- </div>
- )
- }
复制代码 动画特性
参考链接:animations
我们可以在某个样式组件中利用 @keyframes 来界说动画帧,来避免全局冲突。
[code]import { keyframes } from 'styled-components';
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
const StyledRotate = styled.div`
display: inline-block;
animation: ${rotate} 2s linear infinite;
padding: 2rem 1rem;
font-size: 1.2rem;
`;
export function App() {
return (
<StyledRotate>< |