admin 管理员组

文章数量: 1184232

vanilla-extract的主题系统API:创建与使用主题

vanilla-extract是一个零运行时的TypeScript样式解决方案(Zero-runtime Stylesheets-in-TypeScript),其主题系统基于CSS变量(CSS Variables)构建,通过类型安全的API实现主题的创建、管理和切换。本文将详细介绍vanilla-extract主题系统的核心API,包括主题契约(Theme Contract)的定义、主题实现以及动态主题切换等功能,并结合实际代码示例展示如何在项目中应用。

主题系统核心概念

vanilla-extract的主题系统主要围绕以下两个核心概念构建:

  • 主题契约(Theme Contract) :定义主题中包含的变量结构,如颜色、字体、间距等,类似于接口(Interface)的作用,确保所有主题实现遵循统一的变量规范。
  • 主题实现(Theme Implementation) :基于主题契约定义具体的变量值,生成对应的CSS类名,用于在页面中应用主题。

主题系统的核心API包括 createThemeContract createTheme ,分别用于创建主题契约和实现主题。此外, @vanilla-extract/dynamic 包提供了 assignInlineVars 方法,支持在运行时动态设置主题变量。

创建主题契约: createThemeContract

createThemeContract 用于定义主题契约,即主题中包含的变量结构。它不会生成任何CSS代码,仅用于类型定义和变量名生成。

基本用法

// themes.css.ts
import { createThemeContract } from '@vanilla-extract/css';
export const vars = createThemeContract({
  color: {
    brand: null,
    background: null,
    text: null
  },
  font: {
    body: null,
    heading: null
  },
  space: {
    1: null, // 4px
    2: null, // 8px
    3: null  // 12px
  }
});

在上述代码中, createThemeContract 接受一个对象作为参数,该对象定义了主题变量的结构。对象的值可以是 null 、空字符串或任何占位值,因为这些值会被忽略,仅用于类型推断。定义完成后, vars 将包含与输入结构一致的变量访问器,例如 vars.color.brand 对应CSS变量 var(--color-brand__xxx) (其中 xxx 为vanilla-extract自动生成的哈希值,确保变量名唯一)。

类型安全

createThemeContract 会根据输入的结构生成对应的TypeScript类型,确保后续的主题实现必须包含所有定义的变量,否则会产生类型错误。这为大型项目中的主题维护提供了类型保障。

实现主题: createTheme

createTheme 用于基于主题契约实现具体的主题。它接受主题契约和主题变量值作为参数,生成对应的CSS类名,该类名包含了所有主题变量的CSS定义。

基本用法

// lightTheme.css.ts
import { createTheme } from '@vanilla-extract/css';
import { vars } from './themes.css.ts';
export const lightThemeClass = createTheme(vars, {
  color: {
    brand: '#0070f3',
    background: '#ffffff',
    text: '#333333'
  },
  font: {
    body: 'system-ui, sans-serif',
    heading: 'Georgia, serif'
  },
  space: {
    1: '4px',
    2: '8px',
    3: '12px'
  }
});

在上述代码中, createTheme 接受 vars (主题契约)和一个对象(主题变量值)作为参数,生成 lightThemeClass CSS类名。该类名对应的CSS代码如下(简化版):

.lightThemeClass__xxx {
  --color-brand__xxx: #0070f3;
  --color-background__xxx: #ffffff;
  --color-text__xxx: #333333;
  --font-body__xxx: system-ui, sans-serif;
  --font-heading__xxx: Georgia, serif;
  --space-1__xxx: 4px;
  --space-2__xxx: 8px;
  --space-3__xxx: 12px;
}

创建主题变体

可以基于同一个主题契约创建多个主题变体,例如深色主题:

// darkTheme.css.ts
import { createTheme } from '@vanilla-extract/css';
import { vars } from './themes.css.ts';
export const darkThemeClass = createTheme(vars, {
  color: {
    brand: '#61dafb',
    background: '#1a1a1a',
    text: '#ffffff'
  },
  font: {
    body: 'system-ui, sans-serif',
    heading: 'Georgia, serif'
  },
  space: {
    1: '4px',
    2: '8px',
    3: '12px'
  }
});

通过这种方式,可以轻松创建多个主题变体,且所有主题变体都遵循相同的主题契约,确保类型安全。

直接创建主题(不使用契约)

createTheme 还支持不通过 createThemeContract ,直接创建主题契约和主题实现。这种方式会同时生成主题契约和对应的CSS类名,但可能导致主题代码无法拆分(所有主题变体必须依赖原始主题)。

// theme.css.ts
import { createTheme } from '@vanilla-extract/css';
export const [themeClass, vars] = createTheme({
  color: {
    brand: 'blue'
  },
  font: {
    body: 'arial'
  }
});

在上述代码中, createTheme 返回一个元组,包含主题类名( themeClass )和主题契约( vars )。这种方式适用于简单的主题场景,但对于需要代码拆分的大型项目,建议使用 createThemeContract 单独定义主题契约。

应用主题

创建主题后,可以通过将主题类名应用到HTML元素上来激活主题。

静态主题切换

// App.tsx
import { lightThemeClass } from './lightTheme.css.ts';
import { darkThemeClass } from './darkTheme.css.ts';
function App() {
  const isDarkMode = true; // 根据实际逻辑切换
  return (
    <html className={isDarkMode ? darkThemeClass : lightThemeClass}>
      <body>
        <h1>Hello, vanilla-extract!</h1>
      </body>
    </html>
  );
}

在上述代码中,通过将 lightThemeClass darkThemeClass 应用到 <html> 元素,实现了主题的静态切换。所有使用 vars 中变量的样式都会自动应用当前主题的变量值。

组件中使用主题变量

在组件样式中,可以直接使用主题契约中的变量:

// Button.css.ts
import { style } from '@vanilla-extract/css';
import { vars } from './themes.css.ts';
export const button = style({
  backgroundColor: vars.color.brand,
  color: vars.color.text,
  padding: vars.space[2],
  fontFamily: vars.font.body,
  borderRadius: '4px'
});

生成的CSS代码如下:

.Button_button__xxx {
  background-color: var(--color-brand__xxx);
  color: var(--color-text__xxx);
  padding: var(--space-2__xxx);
  font-family: var(--font-body__xxx);
  border-radius: 4px;
}

动态主题: assignInlineVars

@vanilla-extract/dynamic 包提供的 assignInlineVars 方法支持在运行时动态设置主题变量,适用于主题变量值需要从后端获取或用户自定义的场景。

// DynamicTheme.tsx
import { assignInlineVars } from '@vanilla-extract/dynamic';
import { vars } from './themes.css.ts';
function DynamicTheme({ brandColor, textColor }) {
  return (
    <div
      style={assignInlineVars(vars, {
        color: {
          brand: brandColor,
          text: textColor
        }
      })}
    >
      <p>Dynamic theme content</p>
    </div>
  );
}
// 使用
<DynamicTheme brandColor="#ff0000" textColor="#ffffff" />

assignInlineVars 接受主题契约和变量值作为参数,返回一个包含CSS变量定义的对象,可直接作为 style 属性应用到元素上。这种方式不会生成额外的CSS类名,而是通过内联样式动态设置CSS变量。

响应式主题

vanilla-extract支持在媒体查询中使用 assignVars 方法,实现响应式主题切换。

// responsiveTheme.css.ts
import { style, assignVars } from '@vanilla-extract/css';
import { vars } from './themes.css.ts';
export const responsiveTheme = style({
  vars: assignVars(vars, {
    color: {
      backgroundColor: 'pink',
      text: 'purple'
    }
  }),
  '@media': {
    'screen and (min-width: 768px)': {
      vars: assignVars(vars.color, {
        backgroundColor: 'purple',
        text: 'pink'
      })
    }
  }
});

在上述代码中, assignVars 用于设置主题变量值,结合媒体查询可以实现不同屏幕尺寸下的主题切换。

主题系统示例: fixtures/themed

vanilla-extract的官方示例 fixtures/themed 展示了主题系统的完整应用。该示例定义了全局主题契约、多个主题实现以及响应式主题切换。

主题契约与实现

// fixtures/themed/src/themes.css.ts
import {
  createGlobalTheme,
  createTheme,
  assignVars,
  style,
  layer,
} from '@vanilla-extract/css';
export const theme = style({});
// 全局主题契约
export const vars = createGlobalTheme(`:root, ${theme}`, {
  colors: {
    backgroundColor: 'blue',
    text: 'white',
  },
  space: {
    1: '4px',
    2: '8px',
    3: '12px',
  },
});
// 主题变体
export const altTheme = createTheme(vars, {
  colors: {
    backgroundColor: 'green',
    text: 'white',
  },
  space: {
    1: '8px',
    2: '12px',
    3: '16px',
  },
});
// 响应式主题
export const responsiveTheme = style({
  vars: assignVars(vars, {
    colors: {
      backgroundColor: 'pink',
      text: 'purple',
    },
    space: {
      1: '6px',
      2: '12px',
      3: '18px',
    },
  }),
  '@media': {
    'screen and (min-width: 768px)': {
      vars: assignVars(vars.colors, {
        backgroundColor: 'purple',
        text: 'pink',
      }),
    },
  },
});

该示例使用 createGlobalTheme 定义了全局主题契约,将主题变量应用到 :root .theme 类上,支持全局主题和局部主题切换。同时,定义了 altTheme 主题变体和 responsiveTheme 响应式主题。

总结

vanilla-extract的主题系统通过 createThemeContract createTheme 提供了类型安全的主题定义和实现方式,支持静态主题切换、动态主题设置和响应式主题等多种场景。其核心优势在于:

  • 类型安全 :通过TypeScript确保主题变量的结构一致性,减少运行时错误。
  • 零运行时 :主题变量在构建时生成对应的CSS类名和CSS变量,无运行时开销。
  • 代码拆分 :通过 createThemeContract 单独定义主题契约,支持主题代码的拆分和懒加载。
  • 灵活性 :支持静态主题切换、动态主题设置和响应式主题等多种使用场景。

通过合理使用主题系统API,可以构建可维护、可扩展的样式系统,提升大型前端项目的开发效率和代码质量。

官方文档: 主题契约API: 主题实现API: 示例代码:

本文标签: 变量 主题契约 类名