记一次失败的 AI 辅助编程全历程

请注意:本文编写于 ,其中某些信息可能已经失去时效性。

前言

最近公司的一个新项目上马,我被安排来做项目初始化,前端初始化的时候使用了 Vue3 + Tailwind CSS + NaiveUI,在搞基础布局的主题变化时出现了本次插曲。

基础背景

  • 我使用的 AI 工具是安装在自己 Slack 频道的 Claude App;
  • 我对 Tailwind CSS 的实现原理一无所知,我只知道简单的使用方法。

具体问题

按照设计,Web PC 端的左侧边栏有一排导航按钮,这些导航按钮在 light 模式下应该是白底,在 dark 模式下应该是 Naive UI 的默认底色。按照需求描述,只需要使用 Tailwind CSS 来实现「仅在 light 模式下修改指定按钮背景色为白色」就行了。

可坑爹的是 Tailwind CSS 只提供了 dark:bg-white 的写法而没有类似 light:bg-white 的写法,按照正常写法可以使用 class="bg-white dark:bg-[#需要的颜色]" 来处理。但是我不希望这样写,因为 dark 模式下 UI 按钮组件默认的底色就是我所需的,所以我没理由再去强调 dark 模式下的底色是什么,我只需要使用类似 light:!bg-white 的写法来强制覆盖 light 模式下 UI 按钮组件的底色就行。

开始踩坑

于是我开始了无休止的 AI 辅助编程:

直接提问

注:这一步我的诉求是让 AI 帮忙直接生成解决方案。

我最开始的思路是直接将问题描述清楚,让 AI 帮忙生成解决方案,于是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ryomahan  [5:26 PM]

Tailwind CSS 有没有类似 light:bg-white 的写法

ClaudeAPP  [5:26 PM]

在 Tailwind CSS 中,确实有类似 light:bg-white 这样的写法。这叫做 variant。Tailwind CSS 支持几种 variant:- responsive:用于响应式设计,如 sm:bg-white 表示在小屏幕上使用 bg-white 类
- hover/focus/active:用于鼠标交互状态,如 hover:bg-white 表示鼠标悬停时使用 bg-white 类
- dark:用于暗色模式,如 dark:bg-white 表示在暗色模式下使用 bg-white 类
- light:用于亮色模式,如 light:bg-white 表示在亮色模式下使用 bg-white 类所以,如果你想在亮色模式下应用 bg-white 类,可以写成:

html
<div class="light:bg-white bg-gray-800">...</div>

这个 div 在亮色模式下会有 bg-white 类,在暗色模式下会有 bg-gray-800 类。除此之外,Tailwind CSS 还有很多其他的 variant,可以在官方文档中找到:[https://tailwindcss.com/docs/variants](https://tailwindcss.com/docs/variants)希望这个解释可以帮助你!有任何其他问题 feel free 问我。

马后炮:其实这里已经将问题的核心点出来了,即 Tailwind CSS 实现类似 dark: 的写法是通过一种叫做 variant 的概念来实现的,Tailwind CSS 本身提供了一些基础写法,并且给出了文档。

我当时着急直接得到解决方案,便以为 <div class="light:bg-white bg-gray-800">...</div> 就是我想要的答案,但是拿到项目代码中测试发现不管用。于是便开始了下一次问答,而无视了这条答案中最重要的那句话:在 Tailwind CSS 中,确实有类似 light:bg-white 这样的写法。这叫做 variant。

在直接提问这个思路下我又尝试问过如下几个问题:

  • Tailwind CSS 中与 dark:bg-whtie 对立的表达是什么
  • Tailwind CSS 怎样才能使得 bg-white 仅对 light theme 有效对 dark mode 无效
  • tailwind css how to make style invalid in drak mode

得到的答案都不是我想要的东西,于是我开始转换思路。

从原理提问

注:这一步我的诉求是让 AI 帮忙直接生成解决方案。

经过直接提问发现无法得到我想要的答案,于是我开始尝试从实现原理层面进行引导,让 AI 帮忙生成解决方案,我尝试过如下提问:

  • 如何创建类似 dark:* 的自定义形式
  • Tailwind CSS 如何自定义实现类似 dark:* 的效果
  • Tailwind CSS 如何自定义实现类似 dark:* 的功能,例如 custom:*
  • Tailwind CSS 如何自定义实现类似 dark:* 的功能实现 light:* 使得其后面跟随的 style 仅在 light mode 生效
  • Tailwind CSS 中如何实现类似 dark:bg-white 的效果使得 bg-white 仅在 light mode 生效

经过这几次提问之后我发现其中多个答案都在配置文件中提到了 variants 这项配置,于是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ryomahan  [7:09 PM]

variants 是什么

ClaudeAPP  [7:09 PM]

variants 是 Tailwind CSS 一个很强大的功能。它允许你为某些类定义不同的变体(variants),然后在使用这些类时,可以通过如 dark: 前缀来选择使用哪个变体。比如说,对于背景颜色 bg-blue 这个类,你可以定义两个变体:

js
variants: {
backgroundColor: ['responsive', 'dark']
}

这定义了 responsive 和 dark 两个变体。然后在使用时,可以这样写:
- bg-blue - 选择默认的变体
- dark:bg-blue - 选择 dark 变体
- responsive:bg-blue - 选择 responsive 变体所以通过 variants,一个类可以有多种样式,你可以根据需要选择使用哪个变体。variants 常见的使用场景有:- 定义 light/dark 模式下的样式
- 定义不同 breakpoint 下的响应式样式
- 定义不同互动状态(hover/focus/active)下的样式
- ...可以说,variants 是 Tailwind CSS 实现高度可定制和条件化样式的基石功能之一。掌握好了 variants,可以极大提高在 Tailwind 中写 CSS 的能力。总之,variants 是一个非常强大而又重要的 Tailwind CSS 功能,值得深入学习和使用。

马后炮:这个回答其实已经讲了很多有用的信息了。

我还是执着于让 AI 直接给我生成方案,所以我粗略看了一下之后又进行了如下提问:

  • 如何为默认变体提供 alias 到 light
  • 在 Tailwind CSS 中如何使用 variants 使得 light:bg-white
  • 在 Tailwind CSS 中如何使用 variants 使得 light:bg-white 等同于 bg-white
  • 在 Tailwind CSS 中如何使用 variants 使得 <div class="bg-white"> 等同于 <div class="light:bg-white">
  • 如何应用到全部 style 上(因为上一个回答我感觉有点靠谱所以又追问这个问题)
  • 没用(因为我发现它的上个回答并没有生效所以我希望它能够自动修改,结果新生成的内容依旧是没用的)
  • Tailwind CSS variants 的作用是什么(这里我觉得还需要再理解一下实现原理)
  • dark class mode 是如何生效的
  • 如何自定义一套 .light 类使得样式仅在 light 模式下生效(不死心,还想再尝试一下)
  • lightMode 对应的 class 类是什么(这是针对上个问题的追问)
  • 没有效果(哈哈,生成的内容依旧是无效的)
  • 必须叫 lightMode 吗(针对上个问题的追问)
  • Tailwind css 如何自定义 dark mode class name(因为上面的回答都无效所以我又开了一个新的会话继续提问)
  • 没用(依旧没用)
  • 按照你的描述修改了配置文件后并不能生效(还是没用)
  • 使用上面的配置文件,在 .light class 下 dark:bg-white 没有生效(这里我把我使用的配置文件贴上去了,因为内容太长就不复制过来了)
  • 使用 Tailwind CSS 时如何使得 bg-white 可以仅在 light mode 生效在 dark mode 不生效(又开了一个新的会话,这已经是第二天了,昨天晚上感觉这是个坑所以就去忙别的了)

其实这一步的所有提问基本都是在做无用功,因为我太执着于直接获取解决方案而忽略了解决问题的正确流程,我把 AI 想象的过于强大了。

从原理理解

注:这一步我的诉求是让 AI 帮忙解释这一类功能的实现原理,在从原理引导 AI 帮忙生成解决方案。

经过了漫长的无效提问后我开始反思,我在处理问题的过程中太执着于让 AI 直接帮忙生成解决方案了,于是我又转换思路:先让 AI 帮忙阐述实现原理,再从原理入手生成解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ryomahan  [9:40 AM]
Tailwind CSS dark mode 实现原理

ClaudeAPP  [9:40 AM]

Tailwind CSS 的 dark mode 实现原理主要有以下几点:1. 定义 dark 模式的颜色变量。Tailwind 在 colors.js 文件中定义了 dark 模式下的颜色变量,例如:

js
colors: {
// ...
gray: {
900: '#202225',
800: '#2f303d',
700: '#353646',
// ...
}
}

这些颜色变量会在 build 过程中生成对应的 utility。2. 定义 prefers-color-scheme 相关的 variants。在 variants.js 中定义了:

js
variants: {
// ...
prefersColorScheme: ['dark']
}

这表示 dark mode 下会生成 .dark 等 class。3. 在 build 过程中生成 dark mode 的样式。在 build 过程的 variants 阶段,会根据 prefers-color-scheme 的 variants 生成 .dark 等 class,并使用 dark mode 的颜色变量填充这些 class。4. 在 HTML 中使用 prefers-color-scheme media query 加载相应的样式。例如:

html
<link rel="stylesheet" href="tailwind.css">
<link rel="stylesheet" media="prefers-color-scheme: dark" href="tailwind.dark.css">

tailwind.dark.css 是 dark mode 的样式,会在 prefers-color-scheme: dark 条件下被加载。5. 可选:使用 JavaScript 切换模式。可以使用 matchMedia 检测 prefers-color-scheme 的变化,并动态加载样式来实现模式切换。所以总体来说,Tailwind 通过定义 variants、颜色变量和生成不同模式的样式文件来实现 dark mode。hope this helps!

马后炮:回过头来看,这个回答完全没有参考价值,甚至不如前面提到 variants 的那几个回答有价值,所以说 AI 的生成式回答的随机性还是比较坑的。

然后我问了:

  • Tailwind CSS 如何表达 .abc .bg-white
  • Tailwind CSS 如何匹配父 class
  • Tailwind CSS 如何实现父 class 选择器

这里我是想:在当前项目中所谓的主题模式是通过控制 Layout 根 DOM 的 class name 来实现的,当 class 是 dark 时为 dark mode,是 light 时为 light mode。所以只需匹配父类就能够实现我想要的东西。事实证明这个思路确实是没问题的,但是并不是通过简单的 class 实现的。

最终在 Tailwind CSS 的官方文档的引导下我发出了如下提问:

  • Tailwind CSS plugin addVariant 如何使用
  • 我希望使用 addVariant 实现 class="light:bg-white" 能够转换为如下 CSS .light .light:bg-white { background: #fff }
  • 如何让它更加泛化,使得无论 light: 后面跟什么都能转换为对应的 Tailwind CSS
  • 我希望最终实现的效果与 dark:* 类似,当有 .light 的父类 css class 时触发 light:*
  • 仅保留 .light 就可以了,其他的删掉

经过这四连问之后,我以为找到了解决这个问题的正确途径,因为这几个问题最终得到的结果虽然不能生效但是看上去像是那么回事,于是我整理了之前的问题继续发问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ryomahan  [9:58 AM]

在 Tailwind CSS 中使用 addVariant 实现如下效果使得 `class="light:*"` 能够与 `class="dark:*"` 类似在有 .light 父 css class 时生效

ClaudeAPP  [9:58 AM]

你可以这样使用 Tailwind 的 addVariant 功能来实现这个效果:

js
addVariant('light', ({ modifySelectors, separator }) => {
modifySelectors(({ className }) => {
return `.light ${className}`
})
})

这段代码会添加一个 light 变体,使得任何使用 light: 作为前缀的类都会在 .light 父元素下生效。例如:

html
<div class="light">
<p class="light:bg-blue-500">...</p>
</div>

这个 p 元素会具有 bg-blue-500 这个类,因为 light:bg-blue-500 在 .light 元素下生效了。如果没有 .light 父元素,light: 类就不会生效,和 Tailwind 的 dark: 变体一致。所以这给你实现了和 dark: 类似的机制,来根据父元素开启某些样式。Hope this helps! Let me know if you have any other questions. (edited)

这是 AI 给我的方案中距离正确答案最近的一次 ,但是仍旧无效,我又在这个问题的基础上尝试了十几轮的询问,得到的答案都是无效的。

自我救赎

在经历昨天下午两个小时外加今天上午两个小时的 AI 问询无果后,我悟了:就当从来没有 AI,早些时候我怎么解决问题现在还怎么解决,于是:

  1. 我在一些前端群里发求助信息,与人交流;
  2. 同时我开始下载 Tailwind CSS 源码开始审查源码看源码是如何实现 dark mode 的。

整个流程大概持续了十分钟左右的时间,我解决了这个问题,其实如果不去群里发求助信息一上来就看源码的话应该三两分钟就能解决问题。

完整搜索路径:先在 Tailwind CSS 中全局搜索 dark,看了一些结果发现都不是,好多都是测试用例或者其他无关文件中的。于是将搜索范围调整到 src 目录,大概视察了一下后定位在了一个叫 darkVariants 的变量上,跳转到指定文件后果然就是 dark mode 的定义原文,原文如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
darkVariants: ({ config, addVariant }) => {
let [mode, className = '.dark'] = [].concat(config('darkMode', 'media'))

if (mode === false) {
mode = 'media'
log.warn('darkmode-false', [
'The `darkMode` option in your Tailwind CSS configuration is set to `false`, which now behaves the same as `media`.',
'Change `darkMode` to `media` or remove it entirely.',
'https://tailwindcss.com/docs/upgrade-guide#remove-dark-mode-configuration',
])
}

if (mode === 'class') {
addVariant('dark', `:is(${className} &)`)
} else if (mode === 'media') {
addVariant('dark', '@media (prefers-color-scheme: dark)')
}
}

它被放在一个 variantPlugins 变量中,然后我通过引用查看发现使用该变量的地方是项目初始化内部 plugin,然后我参考这个思路定义了一个自定义插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** @type {import('tailwindcss').Config} */
import plugin from "tailwindcss/plugin"

module.exports = {
darkMode: "class",
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {},
plugins: [
plugin(function ({ addVariant }) {
addVariant("light", ":is(.light &)")
})
]
}

问题解决。

结语

经过这一次折磨人的使用经历后我觉得我需要重新审视一下 AI 在我日常编程时的定位,或者说我在编程时应该如何使用 AI。一下是基于本次经历我的几点感悟:

  1. 解决问题的第一步是动脑子而不是动键盘;
  2. 解决问题的最好思路永远在源码中而不在 AI 的回答里;
  3. 在与 AI 对话时不要期望重复问题或者微调问题就能得到正确的答案;
  4. 在不熟悉的领域内提问时尽量不要期望一开始就能生成完整的解决方案。