Panda CSS 确实很适合用来写UI库(CSS-in-JS)

1,926 阅读5分钟

哈喽艾瑞巴蒂,我是努力在写优秀技术爽文的 HoMeTown

Chakra UI 最近发布了一个新的CSS框架,PandaCSS

在Github扒了一下作者发现 PandaCSS 其实在2023 年 3 月 Segun Adebayo 发布的《Chakra UI的未来》 一文中有提到过,只不过最近才正式发布。

image.png

image.png

PandaCSS的特点

来自 Github README:

  • Write style objects or style props, extract them at build time / 为 Style 定义对象和 props 并在构建中提取它们
  • ✨ Modern CSS output — cascade layers @layer, css variables and more / 现代 CSS 输出 — 级联层 @layer、css 变量等
  • 🦄 Works with most JavaScript frameworks / 适用于大多数JavaScript框架
  • 🚀 Recipes and Variants - Just like Stitches™️ ✨ / 专题和变体
  • 🎨 High-level design tokens support for simultaneous themes / 支持同步主题
  • 💪 Type-safe styles and autocomplete (via codegen) / 类型安全样式和自动完成(通过codegen)

文档中提到的支持的框架有:

  • Next.js
  • Solid
  • Vite
  • Svelte
  • Remix
  • Vue
  • ...

试试

文档的入门其实也写的比较详细了,这里简单尝试一下基于Vite + React的项目:

mkdir ./pandacss-react && cd ./pandacss-react

pnpm create vite . --template react-ts

pnpm install

安装 panda,使用panda init 可以生成配置文件:

pnpm install -D @pandacss/dev

pnpm panda init --postcss

这里需要注意的是,node版本不能过低,我首次init时的node版本为14.x,报错||=的语法错误,升级为最新长期支持稳定版v18.16.1后,就没问题了。

package.json中添加命令,构建panda

{
  "scripts": {
    "prepare": "panda codegen",
    ...
  }
}

修改src/index.css

@layer reset, base, tokens, recipes, utilities;

组件中使用:

main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

App.tsx

import { css } from "../styled-system/css";

function App() {
  return (
    <div className={css({ fontSize: "2xl", fontWeight: "bold" })}>
      Hello 🐼!
    </div>
  );
}

export default App;

styled-system/css 目录是用 panda 构建的结果。运行一次构建,然后启动 Vite。

pnpm panda codegen
pnpm dev

启动页面如下:

image.png

非常简单。

Cascade Layers/@layer 级联层

Panda CSS 使用Cascade Layers来控制CSS优先级,所以就意味着当前浏览器必须支持Cascade Layers,根据 MDN,浏览器需要以下版本:

image.png

层数固定为以下五层:

  • reset 重置CSS
  • base 全局CSS
  • tokens 设计令牌的CSS变量
  • recipes 类似一种封装
  • utilities 单独定义的CSS

图层的优先级顺序是@layer在index.css等css中定义的,所以你可以根据需要更改级联层顺序。

css()

使用 PandaCSS 时写 CSS 最简单的方法是通过css()函数并在对象中写css属性。而且类型安全。

image.png

<div>
  <div className={css({ color: "red" })}>red</div>
  <div className={css({ fontWeight: "bold" })}>bold</div>
  <div className={css({ color: "red", fontWeight: "bold" })}>red bold</div>
</div>

生成的CSS如下:

@layer utilities {
  .text_red {
    color: red;
  }
  .font_bold {
    font-weight: var(--font-weights-bold);
  }
}

可以看到生成的CSS内容会进行去重的处理。

event

<button className={css({ color: "red", _hover: { color: "blue" } })}>
  Button
</button>

01f51062a60423ea8768eb68.gif

Patterns

写CSS的时候会有一些频繁出现的布局或者样式,我们更希望这种情况能以模块的形式使用从而达到复用的目的,PandaCSS提供了一些内置的方法,比如center

import { center } from "../styled-system/patterns";

function App() {
  return (
    <div
      className={center({
        bg: "gray",
        color: "white",
        inlineSize: "200px",
        blockSize: "200px",
      })}
    >
      text
    </div>
  );
}

image.png

除了center之外PandaCSS还提供了以下内置方法:

  • container 容器
  • stack 垂直或水平布局容器
  • hstack 水平布局容器
  • vstack 垂直布局容器
  • wrap 元素间距与换行
  • aspectRatio 宽高比
  • flex 弹性布局
  • float 浮动
  • grid 网格
  • gridItem 网格子元素
  • divider 分割线
  • circle 圆形
  • square 正方形

还可以自己自定义一个Pattern,参考

Recipes

Recipes主要用来封装组件样式,比如封装一个button组件的样式:

import { cva } from "../styled-system/css";

export const button = cva({
  base: {
    display: "flex",
    borderWidth: "1px",
    borderColor: "gray",
  },
  variants: {
    type: {
      default: { color: "gray" },
      danger: { color: "red", borderColor: "red" },
    },
    size: {
      small: { padding: "8px", fontSize: "12px" },
      large: { padding: "16px", fontSize: "16px" },
    },
  },
  defaultVariants: {
    type: "default",
    size: "small",
  },
});

组件定义了两种类型defaultdanger,两种大小smalllarge。如果没有指定值,默认被设置为defaultsmall,并且在写代码的时候也会有提示:

image.png

组件中使用:

import { hstack } from "../styled-system/patterns";
import { button } from "./button.css";

function App() {
  return (
    <>
      <div className={hstack({ gap: "8px", padding: "16px" })}>
        <button className={button({ size: "small", type: "default" })}>
          Button
        </button>
        <button className={button({ size: "large", type: "default" })}>
          Button
        </button>
        <button className={button({ size: "small", type: "danger" })}>
          Button
        </button>
        <button className={button({ size: "large", type: "danger" })}>
          Button
        </button>
        <button className={button()}>Button</button>
      </div>
    </>
  );
}

image.png

这仅仅是样式的封装,如果想要封装成组件,并将这些属性作为props使用的话,可以利用RecipeVariantProps提取类型:

Button.tsx

import { ReactNode } from "react";
import { RecipeVariantProps } from "../styled-system/css";
import { button } from "./button.css";

type Props = {
  children: ReactNode;
} & RecipeVariantProps<typeof button>;

export const Button = ({ children, ...recipeVariantProps }: Props) => {
  <button className={button(recipeVariantProps)}>{children}</button>;
};

使用:

import { Button } from "./Button";

function App() {
  return (
    <Button size="small" type="default">
      Button
    </Button>
  );
}

这里很好的一个点是,我们通过这种方式,可以把样式props和其他的props隔离开。

JSX Style Props

上面的方式都是通过className然后使用Panda生成的类来设计样式,类似unocss、tailwindcss这些css框架,除此之外,这些类在Panda中还可以作为一个JSX属性来使用。

首先,需要在panda.config.ts中指定对应的框架:

export default defineConfig({
  ...
  jsxFramework: 'react'
})

然后通过引入styled,使用styled.xxx创建JSX元素:

import { VStack, styled } from "../styled-system/jsx";

function App() {
  return (
    <VStack gap="8px">
      <styled.a href="https://example.com" color="red">
        Link
      </styled.a>
      <styled.button type="button" color="blue">
        Button
      </styled.button>
    </VStack>
  );
}

export default App;

可以看到styled.a处理<a>标签的同时,也可以处理样式。

image.png

我个人还是更偏好于className的方式。

End

简单接触了一下之后,说一下给我的感受:

就我个人而言,体验还不错,用这个框架写UI库应该很爽。

下次见~ 我的朋友,我是HoMeTown👨‍💻‍,➕我VX,💊你进群,这是一个大家共同成长、共同学习的社群!群内十分活跃,至于有多活跃,你来了就知道了!在这里你可以:讨论技术问题、了解前端资讯、打听应聘公司、获得内推机会、聊点有的没的。

👉 vx: hometown-468

👨‍👩‍👧 公众号:秃头开发头秃了 【关注回复“进群”】

🤖 Github:HoMeTownSoCool

高赞好文