
做小程序开发时间一长我最大的感受就是样式代码的失控速度远比页面数量的增长速度要快。很多项目写着写着wxml 越来越整洁wxss 却像一团乱麻。明明只是调一个间距、改一个主题色还得先去全局搜索会不会影响别的页面命名更是头疼一个.container能同时存在八份不同的定义。后来我尝试把原子化样式的思路搬进小程序单独维护一份“原子化样式文件”项目瞬间清爽了很多。这篇文章就围绕这个方案把思路、落地过程、踩坑记录都整理一遍适合正在被小程序样式维护折磨的朋友参考。1. 动手之前先想清楚“原子化”到底解决了什么1.1 小程序样式维护的典型痛点先说小程序原生样式的一个老毛病所有页面最终都在同一个运行时环境里跑wxss 的规则并不是完全隔离的。开发者工具虽然会提示你页面样式可以互相影响但实际项目里只要有一个类名在 app.wxss 里定义了某个页面里又定义了同名类覆盖关系就会变得非常隐晦。我见过最典型的一个场景A 页面写了一个.card当时只是给自己页面用的B 页面后来也写了一个.card命名的时候根本没想起来全局查重。结果两个页面的卡片风格互相干扰排查的时候光是定位是哪条规则在生效就花了不少时间。另一个痛点是页面级的 wxss 文件经常会被放任自流地膨胀。一个列表页写着写着就有上百行样式里面还掺杂着大量只为了某一个临时状态写的冗余规则。时间久了没人敢动这些文件。这些都是常见的“样式维护债”。我当时的诉求很直接能不能把“调整视觉细节”这个动作从反复写 wxss 变成“直接在 wxml 里拼类名”能不能让一套样式规则在全局只定义一份所有页面复用不搞那些模糊的覆盖关系1.2 原子化的核心逻辑一次只做一件事原子化 CSS 的核心思想其实很好理解把样式拆成最小的、功能单一的类。每个类只负责一件事要么管间距要么管颜色要么管布局绝不混在一起。用的时候在 wxml 里组合这些类就像搭乐高积木想要什么效果就拼什么类名。举个例子传统写法可能是这样view classgoods-card商品信息/view.goods-card { background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); }原子化之后wxml 变成了这样view classbg-white radius-16 padding-24 margin-bottom-16 shadow-card商品信息/view这些样式类统一定义在一份atomic.wxss文件里每个类就一行规则。这样做的直接好处是你不需要去关心这个样式是哪个页面用的因为在任何页面里padding-24的含义都完全一致。它天然避免了类名覆盖的混乱也让页面级的 wxss 文件变得非常薄——大部分视觉细节直接用原子类解决真正需要写在页面里的往往只剩一些真正独有的特殊样式。当时我做出这个决定之后团队里最直观的感受就是Review 代码的时候大部分人不再纠结于“这个颜色为什么不统一”因为颜色已经被收敛成了有限的几个原子类视觉规范通过类名本身就被强制落地了。2. 样式文件怎么组织才能既原子又能收2.1 直接照搬 Tailwind还是做小尺寸文件一提到原子化 CSS很多人第一反应就是 Tailwind。Tailwind 在小程序方向确实有对应方案比如配合 Taro、uni-app 这类跨端框架能够通过插件在构建时生成适配的样式文件。如果你本身就在用这类框架直接上 Tailwind 生态是顺理成章的选择它能帮你把代码体积和构建流程都管起来。但如果你的项目是原生小程序或者只是一个小型工具类小程序照搬 Tailwind 需要额外承担一些成本构建流程要改、配置文件要写、还要处理它默认生成的媒体查询在小程序端是否生效的问题。对我来说很多场景并不需要 Tailwind 的全部能力。我需要的只是一套收敛过的、能直接用类名组合的样式规则以及可以随时往里追加新规则的机制。所以我的做法是折中的参考原子化 CSS 的设计思路自己维护一份精简的原子化样式文件。它不依赖任何构建工具不需要在编译时扫描 wxml生成方式也很直接——要么手工写要么用一个简单脚本去生成。文件本身是纯 wxss小程序原生支持不挑技术栈。2.2 我建议的自定义原子类结构这份文件在设计的时候我给自己定了三条原则。第一规则只围绕真正高频的样式需求不追求大而全第二取值必须是离散的不能是任意值比如间距只允许8、16、24这类固定档位不允许出现13rpx这种临时值第三类名要有强规律看到名字就能猜到作用。基于这三条我在项目中维护了这样的分类分类命名规律示例间距方向缩写 数值mt-16、pl-8、gap-16内边距padding缩写 数值pd-16、pd-32字体大小font 数值font-24、font-28字体颜色text 颜色别名text-primary、text-gray背景色bg 颜色别名bg-white、bg-page圆角radius 数值radius-8、radius-full布局flex相关语义flex-row、flex-between、flex-1其他常用直接对应属性hidden、ellipsis、nowrap这套结构最大的好处是你不用背太多规则每个类名的拼写逻辑非常一致mt-16就是 margin-top: 16rpxfont-28就是 font-size: 28rpx。这种一致性在团队协作中尤其重要新人对项目的第一认知成本大幅降低。3. 落地实操如何生成一个可用的原子化样式文件3.1 第一步收敛设计变量在动手生成文件之前我强烈建议先做一件事把项目的设计变量收敛好。这里说的设计变量就是那些会在多个页面重复出现的颜色、间距、字号、圆角值。把它们抽出来整理成一份清晰的清单。这一步不做原子化文件就会变成一个膨胀的、什么都往里塞的大杂烩。比如一个电商类的小程序我可以先把主色、辅助色、价格色、背景色、分割线色固定下来间距上只保留8/16/24/32/48这五档字号上24/26/28/30/32/36/40基本覆盖了绝大多数场景圆角上8/16/24三段就够用了。把这些值定死之后再生成原子类整个项目的视觉语言会极其统一。我当时把这个清单直接写在一个配置文件里后面不管是谁要加新颜色或者新间距都必须先改这份清单再重新生成文件。从流程上杜绝了“临时想出一个颜色就随手加一个类”的坏习惯。3.2 第二步写一个生成脚本如果这份文件完全靠手工维护很快就会有人偷懒、复制粘贴、出现重复类。所以更靠谱的方式是用脚本生成。下面是我在项目里实际用过的一个脚本结构思路非常简单定义一堆spacing数值和颜色别名然后循环拼接成 css 字符串最后写入文件。// gen-atomic.mjs import fs from node:fs const spacing [0, 8, 16, 24, 32, 48] const fontSize [24, 26, 28, 30, 32, 36, 40] const colors { primary: #07c160, danger: #fa5151, warning: #ffc300, text: #333333, subtext: #999999, page: #f6f6f6, white: #ffffff, border: #eeeeee } const radius [0, 8, 16, 24] let css for (const size of spacing) { css .mt-${size}{margin-top:${size}rpx}\n css .mr-${size}{margin-right:${size}rpx}\n css .mb-${size}{margin-bottom:${size}rpx}\n css .ml-${size}{margin-left:${size}rpx}\n css .pd-${size}{padding:${size}rpx}\n css .pt-${size}{padding-top:${size}rpx}\n css .pb-${size}{padding-bottom:${size}rpx}\n css .ph-${size}{padding-left:${size}rpx;padding-right:${size}rpx}\n css .pv-${size}{padding-top:${size}rpx;padding-bottom:${size}rpx}\n } for (const size of fontSize) { css .font-${size}{font-size:${size}rpx}\n } for (const [name, hex] of Object.entries(colors)) { css .text-${name}{color:${hex}}\n css .bg-${name}{background-color:${hex}}\n } for (const size of radius) { css .radius-${size}{border-radius:${size}rpx}\n } css .flex-row{display:flex;flex-direction:row}\n css .flex-col{display:flex;flex-direction:column}\n css .flex-center{align-items:center;justify-content:center}\n css .flex-between{align-items:center;justify-content:space-between}\n css .flex-1{flex:1}\n css .hidden{display:none}\n css .ellipsis{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}\n css .nowrap{white-space:nowrap}\n fs.writeFileSync(styles/atomic.wxss, css) console.log(generated atomic.wxss)跑一遍node gen-atomic.mjs就能得到一份几百行的atomic.wxss。这个文件已经够应付大多数常规页面的视觉需求了。后续如果要调整设计变量只要改脚本里的数组和对象重新生成即可。整个过程可控、可追溯。3.3 第三步在全局样式中引入文件生成完的atomic.wxss需要在小程序的app.wxss中引入。最直接的方式就是用import把原子类做成全局可用的规则。/* app.wxss */ import ./styles/atomic.wxss; page { /* 这里可以放基础的页面级样式 */ }放到app.wxss里之后所有页面都能直接用这些类名。这里有一个非常关键的点全局引入意味着原子类拥有全局样式的能力页面里面如果又要覆盖它必须特别注意顺序和权重。我在实际项目中遇到过几次“类名写了但样式不生效”的问题最后发现都是因为页面 wxss 里同样名称的类把它盖过去了。所以我的建议是原子类只负责“基础统一”页面级 wxss 里尽量不要出现同名类实在要覆盖就用更具体的选择器不要图省事直接改原子类的定义。4. 实际项目中使用的高频原子类清单4.1 布局类应该怎么用在小程序的页面里flex 布局的使用频率非常高。原来写一个“两端对齐且垂直居中”的布局至少要写三行 CSS。用原子类的话一行类名直接搞定。view classflex-between view左侧内容/view view右侧内容/view /viewflex-between这个类内部已经包含了display:flex; align-items:center; justify-content:space-between。这样写的好处不只是省代码更是让页面的结构一目了然——我看一眼 wxml 就知道这是一个水平两端对齐的容器不用再去翻对应的 wxss。我自己用下来频率极高的布局类有这几个flex-row、flex-col、flex-center、flex-between、flex-1。尤其是flex-1在实现列表左右结构、Tab 栏分配剩余空间时特别好用。组合起来也很灵活比如一个商品卡片里的“标题 价格 按钮”完全可以用一组类名描述清楚。4.2 间距、字号、颜色的组合示例间距类在真实页面里要避免“到处都加”的情况。我一般只会在固定的区块边界使用mt-16、mb-24这类类名内部的细粒度间距能省则省。但有了这套类名之后页面调整间距变得非常快设计师说“左边再留大一点”我改一个类名pl-16为pl-24效果立刻出来不用再打开开发者工具搜索样式定义。颜色类名是这份文件带给我的最大收益。以前项目里总有几种灰色深浅不一谁也说不清哪个是标准色。现在页面上出现的文字颜色基本只会有text-text、text-subtext、text-primary这几个。一旦有视觉走查指出颜色偏差我只需要回去检查是不是有人用了不规范的颜色类而不用全局搜索很多个十六进制色值。字号类的使用频率也很高。由于小程序设计稿通常以 rpx 为单位我把字号也统一成了 rpx 值像font-24就是 24rpx。这里要提醒一下字体大小用 rpx 在不同屏幕宽度下会等比缩放对大部分页面来说这符合设计预期但如果你有类似“公告栏”这样对字号一致性要求很高的场景可能需要单独处理。5. 常见问题与排查记录5.1 样式优先级为什么类名不生效这种问题出现得最多。明明 wxml 里写了mt-16页面表现就是没有上边距。排查思路其实很固定先看基础类——mt-16本身在atomic.wxss里定义是否正确再看有没有别的样式规则覆盖了它最后看元素的父级有没有设置特殊布局方式影响了 margin 的渲染效果。最常见的原因出在权重上。比如你在页面的 wxss 里写了.good { margin-top: 8rpx; }又在 wxml 里写了classgood mt-16那最终生效的是页面 wxss 里的margin-top因为两条规则都只是单类名选择器后者在文件引入顺序上晚于app.wxss就被覆盖了。解决办法非常明确不要在页面的 wxss 里手动覆盖原子类如果有特殊情况优先考虑修改 wxml 结构或者用!important但一定要克制。我在项目规范里甚至直接写明页面 wxss 不允许出现与原子类同名的类名。5.2 rpx 与 px 的混用问题小程序端 rpx 和 px 的换算关系大约是“设计稿 750px 宽 750rpx”所以 16rpx 在视觉上大约等于设计稿的 16px。但当你把一个 h5 页面迁到小程序时经常会有人直接把 h5 里的16px写成16rpx导致字号或间距“按比例放大”得过大。原子化文件里我用的是 rpx所以所有原子类都遵循同样的等比缩放规则。这个决策本身没问题关键是团队里每个人都要明白写font-28就是 28rpx对应设计稿里的 28px以 750 设计稿为准不要把它当成28px去理解。遇到需要绝对不受屏幕缩放影响的场景比如视觉上必须保持 1rpx 左右的极细边框那就单独定义一个类不要试图用常规的间距类去硬凑。5.3 包体大小与按需生成原子化文件如果生成得过于激进体积会比较大。比如我把所有间距从 0 到 100 全部枚举一遍生成的文件会有上千行虽然 wxss 对小程序包体积的影响不像图片那样致命但依然不优雅。更重点的是文件里充斥大量永远用不到的死代码本身就是一种维护负担。所以我建议按需生成只保留设计变量表里实际出现过的离散值。项目迭代过程中如果发现某个页面需要临时用一个之前没定义过的间距不要直接改 wxml 硬编码 style而是回到脚本里加一个值重新生成。这么做看似多了一步操作却保证了文件的整洁和可追溯性。实际体验下来多花这几秒非常值。6. 关于这份文件的维护心得最后再分享一点我用了很久才想明白的体会。原子化样式的本质其实是把“写样式”这件事从“创造”变成“选择”。在传统写法里每个人都有自己写间距的方式有人写 margin有人写 padding有人干脆给外层套一个空 view 来撑开布局。原子化之后选择变成唯一的间距就是那几个离散值颜色就是那几个别名方案一多规范性自然就上来了。维护这份文件最怕的是“失控式扩容”。今天加一个透明度类明天加一个动画类后天又想加一个毛玻璃效果类文件最终会变成第二套样式垃圾场。我的做法是每三个月做一次复盘看看哪些类在实际代码里被引用的次数极少然后直接在生成脚本里删掉重新生成一次。听起来有点笨但效果很好——至少我项目的原子化文件一直保持在一个一眼能看完全部内容的状态。如果你现在也正被小程序样式维护的问题困扰我建议可以先从一个小页面开始尝试比如把一个列表页或者详情页的样式换成原子类并单独建一份简化版的atomic.wxss放在全局样式里跑一个迭代。等团队习惯了这种方式再逐步推广到所有页面。这套东西没有多高深但它确实能让人从繁琐的 wxss 堆砌里解脱出来把精力放到更值得关注的业务逻辑和用户体验上。