KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA

组件与样式 — keel 龙骨

组件与样式 的参考信息:组件与样式

课程导读 · 每一条结论都要能在真实浏览器里量出同一个 computed 值

这门课解决的是同一类问题:按钮的颜色改不动、Tailwind 的类名加上去了页面没变、w-40 写上去了输入框还是撑满一行、暗色切了半页白了半页、服务端渲染出来的首屏先闪一下没样式。它们的共同点是——页面上同时住着两套样式,而它们不是按同一种方式、在同一个时刻、进入同一套优先级规则的。

你现在的起点

如果你符合上面这几条,这门课就是为你写的。前提是你会写 React 并且跑得起来 npm——这门课不教组件怎么写,它教的是"你写下的类名和组件,最后变成了文档里的哪几条 CSS 规则"。

一条能走通的学习路径

先看清两套生成模型(01 一个按钮上的两条时间线)
  → antd:组件渲染时生成 → prepend 进 head → 18 个 <style>
  → Tailwind:构建时扫描源码 → 产出四层 → 一份 <link>
  → 现场:类名写对了、权重也一样,颜色就是不变

再拆构建期(02 一次扫描决定产出什么)
  → @source 的边界:目录树外扫不到,页面"像有样式"但组件全是裸的
  → 动态拼类名:写下来过的生成,拼出来的不生成
  → @theme / @utility / @apply 的产出规则

再拆运行期(03 运行时生成 + 变量表)
  → 381 个 --ant-* 变量;改一个 token = 多生成一整张表
  → :where() 把权重压到 0,1,0 —— 和 .bg-red-600 一模一样
  → 颜色真正存在变量里,抢 background-color 是在跟影子打架

核心那一章(04 层序:谁赢是有先后规则的)
  → 未分层 > 每一层;层之间后声明的 > 先声明的
  → 三页对照:不排层 / antd 先声明 / 显式排层
  → 排错一格的代价:Tailwind 全赢,但 antd 的按钮被打成透明

边界(05 组件边界上的四种传参)
  → className / classNames / styles / token 各自什么时候生效
  → 别写死 hash:开发态与生产态的类名不一样

状态(06 暗色与响应式)
  → dark: 变体的默认开关是系统偏好,改成 class 要用 @custom-variant
  → class="dark" 驱动不了 antd —— 两套开关必须由同一个状态源同时喂

交付侧(07 体积、首屏与 SSR)
  → 渲染时注入 vs 抽出成静态文件
  → renderToString 的 HTML 里一个 <style> 都没有

你会拿到什么

章节 一句话 关键动作
01 两套样式是两套生成模型 类名写对了、权重也一样,颜色就是不变 同一页里量出 31 个注入 <style> 与一份 <link>,逐条列出 antd 注入的 18 个样式表
02 Tailwind v4:一次扫描决定产出什么 写下来过就生成,拼出来的不生成 13 组编译对照:@source 边界、@theme 命名空间、@utility 按需、@apply 内联
03 antd:运行时生成 + 变量表 改 token 是多生成一张 381 项的表 五块面板真跑:token / 暗色算法 / 提权重 / styles / classNames
04 层序:谁赢是有先后规则的 未分层比任何层都大 四页对照 + 一个能逆推"哪条规则赢了"的探针
05 组件边界上的四种传参 styles 落成内联、classNames 落成类名 四种传参的落点与 computed 对照 + 类名 hash 的环境差异
06 暗色与响应式:一个开关还是两个 class="dark" 管不到 antd 同一页里切 class × 切视口,四个状态各量一遍
07 交付面:体积、首屏与 SSR renderToString 的 HTML 里没有 <style> 五种 import 组合的体积 + extractStyle 四组对照

每章结构统一:现场 → 形态 → 原因 → 本章脉络 → 生产边界 → 动手:可观察结果 → 故障注入 → 自测题 → 现在能解释什么。

三个必须先纠偏的认知

  1. StyleProvider layer 不是"把 antd 关进笼子"的开关。 实测:它只把组件样式放进 @layer antd;--ant-* 的变量表与 .anticon 基础样式一共 13 条规则永远留在未分层。而未分层规则的优先级高于任何层——所以只加 layer 而不排层序,第一次量出来的 computed 可能一个像素都没变。

  2. antd 输给 Tailwind,不是因为它权重低。 :where(.css-oc1rc0).ant-btn 的权重是 0,1,0,和 .bg-red-600 完全一样。:where() 的作用正是把权重归零,让 antd 接受"被更高层的东西覆盖"。所以你遇到的覆盖失败,通常和"选择器写得不够狠"无关,而是层和顺序的事。

  3. class="dark" 只驱动 Tailwind 的 dark: 变体。 antd 的暗色完全由 React 状态(ConfigProvider theme.algorithm)决定,两者之间没有任何自动同步。实测:给 <html> 加 class="dark" 之后,页面肤色从 rgb(255,255,255) 变成 oklch(0.208 0.042 265.755),而两个 antd 按钮的 background-color 纹丝不动。

学完这门课你能做什么

前置要求

替身边界

实验以 antd 6.6.5 + tailwindcss 4.3.3 + React 19.3.0 为准,浏览器用 Playwright 自带的 HeadlessChrome 151。

这个组合里有两件事是"刚刚才变成这样"的,一定要自己复核:

结论的形状("运行时生成 vs 构建期生成"、"未分层 > 分层"、"改 token 而不是抢选择器"、"class 开关管不到 antd")跨版本成立;具体字节数、hash、报错文案请在你自己的版本上复核。

实验全部是本机小页面、无网络。凡涉及字节数的地方,请只看比值和形状,不要抄绝对值。

进入 keel 阅读