likes
comments
collection
share

最流行的css-in-js库——styled-components

作者站长头像
站长
· 阅读数 32

CSS-IN-JS

众所周知,css没有作用域的概念,存在选择器命名冲突的风险,而且css本身动态功能十分有限,灵活性和复用性不强。

而css-in-js是一种解决上述问题的方案,其顾名思义,就是用 js 的方式写css。

css-in-js目前已有几十种不同实现方案的库,结合github Stars数、社区活跃度等角度来看目前最流行的css-in-js库是styled-components

styled-components

安装

npm install styled-components

初步使用

styled-components利用标记的模板文字来设置您的组件的样式。

它删除了组件和样式之间的映射。这意味着当您定义样式时,实际上是在创建一个普通的 React 组件,该组件上附加了样式。

import { styled } from "styled-components";

const Box = styled.div`
  display: inline-block;
  background: #fc5185;
  width: 200px;
  height: 200px;
  margin: 20px;
`;

const Text = styled.p`
  color: #f5f5f5;
  text-align: center;
  line-height: 200px;
  font-size: 24px;
  margin: 0;
`;

export default function Demo() {
  return (
    <div>
      <Box>
        <Text>hello world</Text>
      </Box>
    </div>
  );
}

结果如下:

最流行的css-in-js库——styled-components

打开开发者工具可以发现

最流行的css-in-js库——styled-components

styled-components创建了对应的标签并给标签上添加了唯一的类名。如此,我们便永远不必担心类名重复、重叠或拼写错误的问题。

除了字符串的写法,styled-components 也支持将 CSS 编写为 JavaScript 对象。

下面的写法等同于上面

import { styled } from "styled-components";

const Box = styled.div({
  display: "inline-block",
  background: "#fc5185",
  width: "200px",
  height: "200px",
  margin: "20px",
});

const Text = styled.p({
  color: "#f5f5f5",
  textAlign: "center",
  lineHeight: "200px",
  fontSize: "24px",
  margin: 0,
});

export default function Demo() {
  return (
    <div>
      <Box>
        <Text>hello world</Text>
      </Box>
    </div>
  );
}

扩展

有时候在使用一个样式组件时,需要对其做一些轻微改动,可以通过创建一个继承另一个组件样式的新组件的方式,只需将其包装在styled()构造函数中即可。

import { styled } from "styled-components";

const Box = styled.div`
  display: inline-block;
  background: #fc5185;
  width: 200px;
  height: 200px;
  margin: 20px;
`;

const BorderBox = styled(Box)`
  border: 10px solid #3fc1c9;
  border-radius: 20px;
  box-sizing: border-box;
`;

export default function Demo() {
  return (
    <div>
      <Box></Box>
      <BorderBox></BorderBox>
    </div>
  );
}

最流行的css-in-js库——styled-components

可以发现BorderBox在Box样式基础上新加了几条样式。

styled()除了可以扩展样式组件外,还可以给普通组件加上样式。

import { FC, PropsWithChildren } from "react";
import { styled } from "styled-components";

type IProps = PropsWithChildren<{
  className: string;
}>;

const MyComponent: FC<IProps> = (props) => {
  return <div className={props.className}>{props.children}</div>;
}; // 这里组件必须接收className,因为 styled-components 会把样式放到这个 className 上

const MyComponentAddStyle = styled(MyComponent)`
  color: #f5f5f5;
  background: #fc5185;
`;

export default function Demo() {
  return <MyComponentAddStyle>hello world</MyComponentAddStyle>;
}

结果如下

最流行的css-in-js库——styled-components

标签名称也可以直接传递给styled()使用

import { styled } from "styled-components";

const Box = styled("div")`
  background: #fc5185;
  color: #f5f5f5;
  width: 200px;
  height: 200px;
  text-align: center;
  line-height: 200px;
`;

export default function Demo() {
  return <Box>hello world</Box>;
}

最流行的css-in-js库——styled-components

实际上,styled.tagname只是执行相同操作的别名。

as

在某些特殊情况下,可能需要更改样式化组件所呈现的标签或组件,可以用as来实现

import { styled } from "styled-components";

const Box = styled.button`
  display: block;
  color: #fc5185;
  width: 100px;
  height: 40px;
  margin: 10px;
`;

export default function Demo() {
  return (
    <div>
      <Box>hello world</Box>
      <Box as="a" href="#">
        hello world
      </Box>
    </div>
  );
}

最流行的css-in-js库——styled-components

as也可以作用自定义组件

import { FC, PropsWithChildren } from "react";
import { styled } from "styled-components";

type IProps = PropsWithChildren<{
  className: string;
}>;

const Box = styled.button`
  display: block;
  color: #fc5185;
  height: 40px;
  margin: 10px;
`;

const MyComponent: FC<IProps> = (props) => {
  return (
    <a className={props.className} href="#">
      {props.children + " link"}
    </a>
  );
};// 同上,这里组件必须接收className,因为 styled-components 会把样式放到这个 className 上

export default function Demo() {
  return (
    <div>
      <Box>hello world</Box>
      <Box as={MyComponent}>hello world</Box>
    </div>
  );
}

结果如下

最流行的css-in-js库——styled-components

传参

既然叫样式组件,那么组件当然可以传参。

import { styled } from "styled-components";

const Box = styled.div`
  display: inline-block;
  background: #fc5185;
  width: 200px;
  height: 200px;
  margin: 20px;
`;

const Text = styled.p<{ $fontColor: string }>`
  color: ${(props) => props.$fontColor};
  text-align: center;
  line-height: 200px;
  font-size: 24px;
  margin: 0;
`;

export default function Demo() {
  return (
    <div>
      <Box>
        <Text $fontColor="#f5f5f5">hello world</Text>
      </Box>
      <Box>
        <Text $fontColor="#3fc1c9">hello world</Text>
      </Box>
    </div>
  );
}

结果如下

最流行的css-in-js库——styled-components

也可以这样像下面这样传参,以样式对象的数组类型的方式传入样式

import { styled, RuleSet } from "styled-components";

const Box = styled.div<{ $otherRule: RuleSet }>`
  display: inline-block;
  margin: 20px;
  ${(props) => props.$otherRule};
`;

const Text = styled.p<{ $fontColor: string; $otherRule: RuleSet }>`
  color: ${(props) => props.$fontColor};
  text-align: center;
  margin: 0;
  ${(props) => props.$otherRule};
`;

export default function Demo() {
  return (
    <div>
      <Box
        $otherRule={[
          { width: "200px", height: "200px" },
          { background: "#fc5185" },
        ]}
      >
        <Text
          $fontColor="#f5f5f5"
          $otherRule={[{ lineHeight: "200px", fontSize: "24px" }]}
        >
          hello world
        </Text>
      </Box>
    </div>
  );
}

最流行的css-in-js库——styled-components

styled目标是react自定义组件时,由于react组件本身也可以传参,为了区分react组件的参数和样式组件的参数,一般样式组件的props会约定俗成用$开头。

import { FC, PropsWithChildren } from "react";
import { styled } from "styled-components";

type IProps = PropsWithChildren<{
  list: string[];
  className: string;
}>;

const MyComponent: FC<IProps> = (props) => {
  return (
    <ul className={props.className}>
      {props.list.map((item) => (
        <li key="item">{item}</li>
      ))}
    </ul>
  );
};

const MyComponentAddStyle = styled(MyComponent)<{
  $fontColor: string;
}>`
  color: ${(props) => props.$fontColor};
`;

export default function Demo() {
  const list = ["option 1", "option 2", "option 3", "option 4"];
  return (
    <MyComponentAddStyle $fontColor="#fc5185" list={list}></MyComponentAddStyle>
  );
}

最流行的css-in-js库——styled-components

一般情况下, styled-components 会透传所有不是它的 props 给被包装组件。

import { FC, PropsWithChildren } from "react";
import { styled } from "styled-components";

type IProps = PropsWithChildren<{
  paramText: string;
  className: string;
}>;

const StyledInput = styled.input<{ $fontColor: string }>`
  font-color: ${(props) => props.$fontColor};
`;

const MyComponent: FC<IProps> = (props) => {
  console.log("自定义组件接收到的props", props);
  return <div className={props.className}>{props.paramText}</div>;
};

const MyComponentAddStyle = styled(MyComponent)<{
  $fontColor: string;
}>`
  color: ${(props) => props.$fontColor};
`;

export default function Demo() {
  return (
    <div>
      <StyledInput $fontColor="#fc5185" placeholder="请输入"></StyledInput>
      <MyComponentAddStyle
        $fontColor="#fc5185"
        paramText="文字参数"
      ></MyComponentAddStyle>
    </div>
  );
}

最流行的css-in-js库——styled-components

从上面可以发现,styled作用普通元素(styled.input),传递的参数placeholder传递到了input元素上,而$fontColor参数则不会。同样,styled作用自定义组件时,参数$fontColor没有传递到自定义组件,而paramText则传递到了自定义组件。

attrs

通过attrs方法,可以修改传递给组件或元素的props

import { FC, PropsWithChildren } from "react";
import { styled } from "styled-components";

type IProps = PropsWithChildren<{
  list: string[];
  className: string;
}>;

const MyComponent: FC<IProps> = (props) => {
  return (
    <ul className={props.className}>
      {props.list.map((item) => (
        <li key="item">{item}</li>
      ))}
    </ul>
  );
};

const MyComponentAddStyle = styled(MyComponent).attrs<{
  $fontColor: string;
}>((props) => {
  return {
    $fontColor: "#3fc1c9",
    list: props.list.slice(0, 2).map((item) => item + " 选项"),
  };
})`
  color: ${(props) => props.$fontColor};
`;

export default function Demo() {
  const list = ["option 1", "option 2", "option 3", "option 4"];
  return (
    <MyComponentAddStyle $fontColor="#fc5185" list={list}></MyComponentAddStyle>
  );
}

最流行的css-in-js库——styled-components

attrs方法除了支持函数外,也支持对象的方式

import { FC, PropsWithChildren } from "react";
import { styled } from "styled-components";

type IProps = PropsWithChildren<{
  type: string;
  className: string;
}>;

const MyComponent: FC<IProps> = (props) => {
  return <input className={props.className} type={props.type} />;
};

const MyComponentAddStyle = styled(MyComponent).attrs<{
  $fontColor: string;
}>({
  type: "file",
})`
  color: ${(props) => props.$fontColor};
  width: 240px;
  height: 40px;
`;

export default function Demo() {
  return (
    <MyComponentAddStyle $fontColor="#fc5185" type="text"></MyComponentAddStyle>
  );
}

最流行的css-in-js库——styled-components

伪元素、伪选择器和嵌套

styled-components 也支持伪元素、伪选择器,包括高级选择器,其写法类似于scss的写法。可以用&符号指代组件的实例

import { Fragment } from "react";
import { styled } from "styled-components";

const Item = styled("div")`
  height: 40px;
  background-color: pink;
  &::before {
    content: "***";
    color: #f5f5f5;
  }
  &:hover {
    color: red;
  }
  &:nth-child(2n) {
    background-color: green;
    &::after {
      content: "%%%";
      color: #f5f5f5;
    }
  }
  &.second-item + & {
    font-size: 30px;
    font-weight: bold;
  }
  &.second-item + & ~ & {
    text-align: center;
    text-decoration: underline;
  }
  .item-wrap & {
    border: 10px solid black;
  }
`;

export default function Demo() {
  return (
    <Fragment>
      <Item>hello world</Item>
      <Item className="second-item">hello world</Item>
      <Item>hello world</Item>
      <Item>hello world</Item>
      <Item>hello world</Item>
      <Item>hello world</Item>
      <div className="item-wrap">
        <Item>hello world</Item>
      </div>
    </Fragment>
  );
}

上面例子中

  • &::before:添加了伪元素(content为'***')
  • &:hover:添加了伪类,使鼠标移上去文本颜色变红
  • &:nth-child(2n):选择第偶数个的Item样式组件设置其背景色为绿色,并添加after伪类(content为"%%%")
  • &.second-item + &:选择.second-item类名的样式组件后面相邻的一个Item样式组件,设置文字大小和粗细
  • &.second-item + & ~ &:选择.second-item类名的样式组件后面相邻的一个Item样式组件后面的所有Item样式组件,设置其文字样式
  • .item-wrap &:选择.item-wrap类名的样式组件的后代Item样式组件,设置其边框

最流行的css-in-js库——styled-components

除了用&符号,还可以用&&符号,把上面的例子代码中的&都换成&&,效果也是一样的。

二者的区别是&&可以提升优先级,如下面的例子

import { styled } from "styled-components";
import { Fragment } from "react";

const DivOneSymbol = styled.div`
  width: 100px;
  height: 20px;
  & {
    background-color: pink;
  }
`;

const DivTwoSymbol = styled.div`
  width: 100px;
  height: 20px;
  && {
    background-color: pink;
  }
`;

const StyledDivOneSymbol = styled(DivOneSymbol)`
  background-color: green;
`;

const StyledDivTwoSymbol = styled(DivTwoSymbol)`
  background-color: green;
`;

export default function Demo() {
  return (
    <Fragment>
      <StyledDivOneSymbol>一个&</StyledDivOneSymbol>
      <StyledDivTwoSymbol>两个&</StyledDivTwoSymbol>
    </Fragment>
  );
}

最流行的css-in-js库——styled-components

可以发现第一个div的样式被覆盖了,而第二个div(&&)则不会被覆盖。

最流行的css-in-js库——styled-components

最流行的css-in-js库——styled-components

打开开发者工具可以发现&&通过“.iShNYi.iShNYi”这样两个类名选择的方式实现了样式优先级的提升。

实际上不止可以写两个&符号,写多少个都可以。只不过意义不大了。

const DivTwoSymbol = styled.div`
  width: 100px;
  height: 20px;
  &&&& {
    background-color: pink;
  }
`;

最流行的css-in-js库——styled-components

keyframes动画

大多时候我们不希望 CSS 动画@keyframes是全局的,以避免名称冲突。所以styled-components导出一个keyframes助手,它将生成一个可在整个应用程序中使用的唯一实例。

import { styled, keyframes } from "styled-components";

const rotate = keyframes`
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
`;

const Rotate = styled.div`
  display: inline-block;
  animation: ${rotate} 2s linear infinite;
  font-size: 60px;
  margin: 20px;
`;

export default function Demo() {
  return <Rotate>K</Rotate>;
}

最流行的css-in-js库——styled-components

最流行的css-in-js库——styled-components

打开开发者工具可以发现,styled-components为@keyframes 生成了一个唯一 ID

主题

styled-components 提供<ThemeProvider>组件,此组件通过上下文 API 为其下的所有 React 组件提供主题。在渲染树中,所有 styled-components 都可以访问提供的主题,即使它们位于多个层级。其用法类似react 的 context。

组件可以通过props获取主题色,也可以通过useTheme获取主题色

import { useState, ChangeEvent, FC } from "react";
import { styled, ThemeProvider, useTheme } from "styled-components";

const ThemeStyleBox = styled.div`
  display: inline-block;
  width: 200px;
  height: 200px;
  margin: 20px;
  background-color: ${(props) => props.theme.themeColor};
`;

const StyleBox = styled.div<{ $backgroundColor: string }>`
  display: inline-block;
  width: 200px;
  height: 200px;
  margin: 20px;
  background-color: ${(props) => props.$backgroundColor};
`;

const MyBox: FC = () => {
  const theme = useTheme();
  return <StyleBox $backgroundColor={theme.themeColor} />;
};

export default function Demo() {
  const [themeColor, setThemeColor] = useState("#000000");

  const colorChange = ($event: ChangeEvent<HTMLInputElement>) => {
    setThemeColor($event.target.value);
  };

  return (
    <ThemeProvider theme={{ themeColor: themeColor }}>
      <input
        type="color"
        value={themeColor}
        onChange={($event) => colorChange($event)}
      ></input>
      <ThemeStyleBox />
      <MyBox />
    </ThemeProvider>
  );
}

最流行的css-in-js库——styled-components

其他Api

createGlobalStyle

createGlobalStyle 可以创建全局样式,通过createGlobalStyle创建的组件不支持子组件,而是将其

放置在 React 树的顶部,当组件“呈现”时,将注入全局样式。

import { styled, createGlobalStyle } from "styled-components";

const StyleDiv = styled.div`
  color: pink;
`;

const GlobalStyle = createGlobalStyle<{ $globalColor?: string }>`
  body {
    color: ${(props) => props.$globalColor};
  }
  ${StyleDiv} {
    color: ${(props) => props.$globalColor};
  }
`;

export default function Demo() {
  return (
    <div>
      <GlobalStyle $globalColor="green" />
      <div>hello world</div>
      <StyleDiv>hello world</StyleDiv>
    </div>
  );
}

最流行的css-in-js库——styled-components

css

cssapi可用于样式复用

import { styled, css } from "styled-components";

const commonCss = css<{ $color?: string }>`
  display: inline-block;
  color: ${(props) => props.$color};
  width: 200px;
  height: 200px;
  text-align: center;
  line-height: 200px;
`;

const PinkDiv = styled.div<{ $color?: string }>`
  background: pink;
  ${commonCss};
`;

const RedDiv = styled.div<{ $color?: string }>`
  background: red;
  ${commonCss};
`;

export default function Demo() {
  return (
    <div>
      <PinkDiv $color="#f5f5f5">hello</PinkDiv>
      <RedDiv $color="#f5f5f5">world</RedDiv>
    </div>
  );
}

最流行的css-in-js库——styled-components

注意点

在 render 方法之外定义 Styled Components

在 render 方法之外定义 Styled Components,否则每次渲染过程中都会重新创建样式化组件,会降低渲染速度。

// good
const StyledBox = styled.div`
  /* ... */
`;

const Box = () => {
  return <StyledBox></StyledBox>;
};

// bad
const Box = () => {
  const StyledBox = styled.div`
  	/* ... */
	`;
  return <StyledBox></StyledBox>;
};

总结

上面是css-in-js的库styled-components的基本用法,从上面的使用可以发现,styled-components有以下优点

  • 无样式冲突问题,让 CSS 代码拥有独⽴的作⽤域
  • 可以用 js 来写样式逻辑,更好维护,动态性,灵活性,复用性更高
  • 更简单的主题设置

目前有不少主流的组件库都开始使用css-in-js,例如Ant Design 5.0 版本从 less 切换到 css-in-js,css-in-js也在慢慢成为主流。

转载自:https://juejin.cn/post/7400741845509914659
评论
请登录