Skip to content

CSS IN JS(@emotion/react + @emotion/styled) ​

什么是 CSS IN JS ​

传统的 React 项目里,样式写在独立的 .css 文件里,再用 className 挂到元素上。但这种方式有几个痛点:

  • 类名容易冲突(起名都得想半天,还怕和别人重名)。
  • 样式是"死"的,想根据组件状态(比如按钮是否禁用)改样式很麻烦。
  • 删组件时常常忘了删对应的 CSS,越堆越多。

CSS IN JS 就是"把样式直接写在 JS/TSX 文件里",样式跟着组件走,不用单独开 .css 文件。它的好处:

  • 样式跟着组件走:组件删了,样式也一起没了,不会留垃圾。
  • 样式可以根据状态动态变化:直接读 props、读 state,像写 JS 一样写样式。
  • 不用担心类名冲突:Emotion 会自动生成独一无二的类名。

Emotion 提供了两种写法,正好对应两个包:

包用法适合场景
@emotion/react用 css 函数生成样式,挂到 className 上少量、临时的样式
@emotion/styled创建"自带样式的组件"(Styled Components)主要推荐,可复用、可读性好

安装 ​

bash
pnpm add @emotion/react @emotion/styled
  • 找到tsconfig.app.json 添加
bash
  "jsxImportSource": "@emotion/react",
  • 找到vite.config.ts 添加
ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vite.dev/config/
export default defineConfig({
  plugins: [react({ compiler: true, jsxImportSource: "@emotion/react" })],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
      "~": fileURLToPath(new URL("./src/assets", import.meta.url)),
    },
  },
});

一、@emotion/react:用 css 写样式 ​

1.1 最基础的用法 ​

@emotion/react 导出一个 css 函数,把样式写在模板字符串里,然后挂到元素的 css 属性上:

tsx
/** @jsxImportSource @emotion/react */
// 上面这行注释告诉 TS/构建工具:这个文件用 Emotion 处理 css 属性
import { css } from "@emotion/react";

export default function Demo() {
  return (
    <div
      css={css`
        padding: 20px;
        background: #f0f0f0;
        color: #333;
      `}
    >
      这是一个带样式的 div
    </div>
  );
}

注意:文件顶部的 /** @jsxImportSource @emotion/react */ 是让 css 属性生效的关键(Vite/CRA 新版都支持)。如果不写,就需要在 tsconfig.json 或构建工具里全局配置。

1.2 简化写法(直接用对象) ​

css 除了模板字符串,还支持对象写法(更像写 JS 对象,适合简单样式):

tsx
import { css } from "@emotion/react";

const boxStyle = css({
  padding: "20px",
  background: "#f0f0f0",
  color: "#333",
});

export default function Demo() {
  return <div css={boxStyle}>带样式的 div</div>;
}

1.3 样式复用与组合 ​

css 生成的样式可以当变量复用,还能组合:

tsx
import { css } from "@emotion/react";

const base = css({
  padding: "10px",
  borderRadius: "4px",
});

const primary = css({
  background: "#1677ff",
  color: "#fff",
});

export default function Demo() {
  // 组合多个样式,后面的会覆盖前面的
  return <div css={[base, primary]}>组合样式</div>;
}

1.4 动态样式(根据 props 变化) ​

这是 CSS IN JS 最核心的优势:样式可以读组件的 props 动态生成:

tsx
import { css } from "@emotion/react";

interface ButtonProps {
  disabled?: boolean;
}

export default function Button({ disabled }: ButtonProps) {
  return (
    <button
      css={css({
        padding: "8px 16px",
        border: "none",
        borderRadius: "4px",
        // 根据 props 动态改变样式
        background: disabled ? "#ccc" : "#1677ff",
        color: "#fff",
        cursor: disabled ? "not-allowed" : "pointer",
      })}
    >
      按钮
    </button>
  );
}

1.5 写嵌套与伪类 ​

模板字符串写法和普通 CSS 一样,支持嵌套和伪类(&:hover、&:focus):

tsx
<div
  css={css`
    background: #fff;
    &:hover {
      background: #f0f0f0;
    }
    .title {
      font-size: 20px;
    }
  `}
>
  <div className="title">标题</div>
</div>

二、@emotion/styled:创建带样式的组件 ​

2.1 最基础的用法 ​

@emotion/styled 导出一个 styled 对象,可以把任意 HTML 标签变成"自带样式的组件":

tsx
import styled from "@emotion/styled";

// 创建一个带样式的 div 组件
const Box = styled.div`
  padding: 20px;
  background: #f0f0f0;
  color: #333;
`;

export default function Demo() {
  return <Box>这是一个带样式的盒子</Box>;
}

用法和普通组件一模一样,只是自带了样式。这是 Emotion 最推荐的写法。

2.2 根据 props 动态样式 ​

styled 最实用的能力:直接在模板字符串里读 props 改样式:

tsx
import styled from "@emotion/styled";

interface ButtonProps {
  primary?: boolean;
  disabled?: boolean;
}

const Button = styled.button<ButtonProps>`
  padding: 8px 16px;
  border: none;
  border-radius: 4px;
  color: #fff;
  /* 根据 props 动态返回样式 */
  background: ${(props) => (props.primary ? "#1677ff" : "#999")};
  cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
  opacity: ${(props) => (props.disabled ? 0.6 : 1)};

  &:hover {
    background: ${(props) => (props.primary ? "#0958d9" : "#888")};
  }
`;

export default function Demo() {
  return (
    <>
      <Button primary>主要按钮</Button>
      <Button disabled>禁用按钮</Button>
    </>
  );
}

注意:styled.button<ButtonProps> 里的泛型告诉 TS 这个组件接收哪些 props,这样写既能动态改样式,又有类型提示。

2.3 复用已有组件的样式 ​

如果想给一个已经存在的组件(包括自己写的或第三方组件)加样式,用 styled(组件):

tsx
import styled from "@emotion/styled";

const MyButton = ({ children, ...props }) => (
  <button {...props}>{children}</button>
);

// 给 MyButton 加样式
const StyledMyButton = styled(MyButton)`
  background: #1677ff;
  color: #fff;
  padding: 8px 16px;
`;

// 甚至可以直接给 antd 的组件加样式
import { Button } from "antd";
const StyledAntdButton = styled(Button)`
  border-radius: 20px;
`;

2.4 对象写法 ​

styled 也支持对象写法,和模板字符串二选一:

tsx
import styled from "@emotion/styled";

const Box = styled.div({
  padding: "20px",
  background: "#f0f0f0",
  color: "#333",
});

三、两个包怎么选 ​

  • 简单、临时的样式:用 @emotion/react 的 css,快速写几行。
  • 需要复用、需要 props 动态变化的组件:用 @emotion/styled,把样式和组件绑定,更清晰。

实际项目里,绝大多数情况推荐用 @emotion/styled,因为它把"样式"和"组件"绑定在一起,代码结构更清晰,也更好复用。


四、完整小案例:一个可复用的卡片组件 ​

把上面学的串起来,做一个"可传标题、可传背景色"的卡片组件:

tsx
import styled from "@emotion/styled";

interface CardProps {
  bgColor?: string;
}

const CardWrapper = styled.div<CardProps>`
  border: 1px solid #e8e8e8;
  border-radius: 8px;
  padding: 20px;
  background: ${(props) => props.bgColor || "#fff"};
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);

  &:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  }
`;

const CardTitle = styled.h3`
  margin: 0 0 12px;
  font-size: 18px;
  color: #333;
`;

const CardContent = styled.div`
  color: #666;
  line-height: 1.6;
`;

interface Props extends CardProps {
  title: string;
  children: React.ReactNode;
}

export default function Card({ title, children, bgColor }: Props) {
  return (
    <CardWrapper bgColor={bgColor}>
      <CardTitle>{title}</CardTitle>
      <CardContent>{children}</CardContent>
    </CardWrapper>
  );
}

使用:

tsx
<Card title="默认卡片">内容</Card>
<Card title="高亮卡片" bgColor="#e6f4ff">内容</Card>

五、常用技巧速查 ​

需求写法
给标签加样式styled.div\...``
给已有组件加样式styled(MyComponent)\...``
根据 props 改样式${props => ...}`
写 hover 等伪类&:hover { ... }
组合多个样式css={[styleA, styleB]}
加 TS 类型styled.div<Props>\...``
响应式模板字符串里直接写 @media

六、小结 ​

  • @emotion/react 提供 css,适合写"一次性"样式,灵活。
  • @emotion/styled 提供 styled,把样式和组件绑定,日常开发首选。
  • 核心优势:样式跟着组件走、样式可以根据 props 动态变化、不用操心类名冲突。

上手建议:先用 @emotion/styled 写几个小组件(按钮、卡片、标题),熟练后再按需用 css 处理零散样式即可。