cnfast 与 cn():React 中的 7 倍加速是真的吗?
你克隆过的每个shadcn/ui项目都会附带同一个小文件:。里面有一个叫做 的两行函数,它对每个元素、每个渲染都运行。没人会对它进行分析,因为它是一个效用函数,而效用函数本应是自由的。lib/utils.ts``cn()
事实并非如此。Million.js的创始人Aiden Bai发布了一个名为cnfast的替换产品,声称平均速度快3.8倍,组件密集代码最高可达7倍,输出字节相同。一次导入更改,没有其他代码,整个组件库的样式层都会变快。听起来像是免费赚钱。lib/utils.ts
所以我不再假设,而是把它放进了一个真正的仪表盘里。我先单独测试了这个函数,然后在一个500行的数据网格、一个包含1500个项目的虚拟列表和服务器渲染中进行基准测试。这些孤立数字很大,且根据你是在Node还是浏览器中运行而变化很大。真实应用的数据无论如何都只是耸耸肩。这篇文章讲述了全部故事,包括死胡同和设置错误。
这种模式无人质疑cn()
如果你用过shadcn/ui,那你是在没多想的情况下创建了这个文件。医生告诉你这么做。每个Tailwind组件教程都包含。它看起来像这样:import { cn } from "@/lib/utils"
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
这条线里有两个工作。 它连接你的职业名称并去掉假的,修复:Windows Defender 无法使用或无法开启 - 致知笔记 - 专注教程知识分享 因此变成了一个干净的字符串。 然后解决冲突,崩溃到,最后一个效用者获胜。clsxcn("px-2", isActive && "px-4")tailwind-mergepx-2 px-4px-4
这里是没人会想到的部分。 通过基于正则表达式的分词器解析每个类字符串,将每个令牌排序到Tailwind工具组,检查这些组之间的冲突并加以解决。这项工作不是免费的,每次 React 重新渲染调用 的组件时都会运行。在静态的营销页面上,谁在乎呢?在绘制数百行的数据网格或实时图表的仪表盘上,这些功能每秒触发数千次。tailwind-merge``cn()
这就是CNFAST的推销。让我来演示一下我是如何测试提案是否能经得起真实应用的。
我是如何设置测试的,这样你可以信任这些数字
我用Vite、React 18、Tailwind v4和shadcn/ui构建了一个逼真的仪表盘。不是玩具:一个可折叠侧边栏、一个带搜索和通知徽章的顶部栏、六张带趋势徽章的统计卡、一个带条件状态颜色的近期订单表、一个创建顺序模态,以及两个压力页面。一个压力页面是一个500行的网格,包含八个样式列、选择和排序功能。另一个是1500项的列表,虚拟化为。@tanstack/react-virtual
让整个游戏公平的关键在于我做了可交换的。两个文件保存了两种实现:cn()
// src/lib/cn-slow.ts (the standard shadcn cn)
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// src/lib/cn-fast.ts
export { cn } from "cnfast"
Vite 别名是基于环境标志选择一个,所以只用 cnfast 和 clsx 加顺风合并。应用代码从未改变。只有别名解析会这样。从我的属性卡到shadcn原件,每个组件都从同一个地方拉取资源,所以除了那个函数外,两个构建几乎完全相同。VITE_CN=fast npm run buildVITE_CN=slow npm run buildcn
供参考:Apple M4,16 GB,Node 22.14,Chrome 151(由 Puppeteer 驱动),React 18.3.1,tailwind-merge 3.6.0,cnfast 0.1.0。
事情并非一帆风顺。 我发货时是React 19,所以我得先降级到18。 直到我添加 和 到 root tsconfig(它读取别名验证的那个,而不是应用的 tsconfig)之前都无法运行。我第一次慢速构建失败,是因为shadcn初始化器没有留下包。我很快修好了。有些小瑕疵,但这东西能吃掉整个下午,所以我决定留着。create-viteshadcn initbaseUrlpathsRolldown failed to resolve import "tailwind-merge"``npm install tailwind-merge clsx
CNFAST是直接替代的吗?cn()
在任何速度说话之前,承诺是字节相同的输出。如果cnfast更快但冲突解决方式不同,那它不是临时加入,而是bug生成器。所以我先测试了这个。
我建立了一个包含351个论证组的语料库。81直接来自我仪表盘里的真实呼叫网站。108个是刻意设计的边缘案例:冲突填充、响应式变体如、任意值如、数组对象语法、数组,以及我能想到的各种虚假值。最后162张是两者的混合组合。然后我对每个组运行了两个实现,并比较了输出字符串。cn()md:px-4bg-[#123456]``{ 'text-red-500': hasError }
| 度规 | 价值 |
|---|---|
| 总论元群 | 351 |
| 不匹配 | 0 |
零。每个群体的产出都完全相同。CNFAST清除了信任条。它确实是个随手使用,至少在我的仪表盘和我的多疑中能找到的范围内。很好。现在我们可以谈谈速度了。
CNFAST比顺风合并快多少?
我单独测量了函数,在一个紧密的循环中,使用了来自实分量的八个代表参数集。每个变体预热1000次迭代,然后运行八个约500毫秒的定时采样,丢弃第一个。我比较了三种东西:标准的、cnfast的呼叫表单,以及cnfast的标记模板表单,后者按呼叫站点身份缓存。cn()
| 变体 | 中位数操作 | 加速 |
|---|---|---|
| 顺风合流cn() | 6,367,481 | 1.00倍 |
| CNFAST呼号形式cn() | 36,587,575 | 5.75倍 |
| CNFAST标签模板cn | 45,781,656 | 7.19x |
再读一遍。在Node中,纯呼号格式交换快了5.75倍,已经超过了3.8倍的标题,而标记模板则将其提升到了7.19倍。方差很小,所以这些是真实数字,不是运气好。
然后我在 Chrome 上运行了完全相同的基准测试,这很重要,因为 Chrome 才是 React 真正所在的地方。数据发生了变化,而且对cnfast不利。
| 变体 | 中位数操作(Chrome) | 加速 |
|---|---|---|
| 顺风合流cn() | 15,460,192 | 1.00倍 |
| CNFAST呼号形式cn() | 32,841,520 | 2.12倍 |
| CNFAST标签模板cn | 42,122,784 | 2.72倍 |
同样的代码,相同的参数集,调用表单的胜利率从Node的5.75x降到了浏览器的2.12x。先想一会儿。在Chrome中,cnfast的直接呼叫表单甚至达不到盒子上的3.8倍数字。带标签的模板在2.72倍时表现稍微好一点,但也远远比不上节点的数字。
原因已经融入了V8。它已经缓存了重复调用的参数,所以 cnfast 手动优化的一部分,是引擎悄悄地帮你做的。Node 22和Chrome 151在不同条件下运行不同的V8版本,而它们之间的差距正是这里的重点。你获得的加速不是一个数字;这个范围很大程度上取决于你测量的地点。而对React应用来说,浏览器最关键的地方是cnfast看起来最弱的地方。cn()
不过,事情是这样的。即使是2.12倍的计算也是一个紧密的循环,而这个紧凑的循环并不是你的应用。在你的应用里,它不会单独运行。它运行在React渲染中,而React不仅仅是字符串合并。cn()
cnfast 真的会加快 React 的渲染速度吗?
这正是发布推文没有展示的部分。修复:Windows 笔记本电脑电池快速耗尽 - 致知笔记 - 专注教程知识分享 我把这个函数,在浏览器里大约值2倍,Node里最高7倍,然后把它放进去锤打它的组件里,然后测量了整个渲染。
因为React的Profiler回调在生产构建中不会触发,我用一个在生产环境中有效的plus模式来定时渲染。每个变异有三次独立运行,每个中位数为七个样本。onRenderuseLayoutEffectperformance.now()
500行数据网格。每个单元格调用条件类,所以一次排序重新渲染会触发该函数超过4000次。cn()
| 互动 | 慢速(clsx + tw-merge) | Fast(cnfast) | 加速 |
|---|---|---|---|
| 排序 重新渲染 | 28.47毫秒 | 26.37毫秒 | ~7.4% |
| 行选择重新渲染 | 20.90毫秒 | 20.50毫秒 | ~1.9%(噪声) |
单独用一个快几倍的函数,在28毫秒渲染上帮我节省了2毫秒。三次跑步中速度稳定地更快。但2毫秒。行选择机箱处于运行到运行的噪音范围内。
包含1500件物品的虚拟卷轴。我用程序滚动了三秒,数了帧,超出了16.7毫秒的预算。
| 度规 | 慢 | 快点 |
|---|---|---|
| 掉帧数(平均~181帧) | 92 | 88 |
平均少掉四帧,但这些距离重叠极大,一次快速运行(97帧)比一次慢速运行(86帧)更糟。坦率地说,这只是噪音。CNFAST对滚动没有明显帮助,因为滚动消耗是布局和绘画,而不是职业合并。
服务器渲染。一棵由约3500个元素组成的树。renderToString
| 变体 | 中位数 | 加速 |
|---|---|---|
| 顺风合流cn() | 1.90毫秒 | 1.00倍 |
| CNFAST的cn() | 1.65毫秒 | 1.15倍 |
SSR赢了15%。真实但小,最小/最大范围重叠。 元素创造主导该数,而非。renderToString``cn()
在那个数据网格上打开React Profiler,观点就自己明白了。一次排序提交需要72毫时间。组件消耗了28.5毫秒,桌面本体又消耗了41.8毫秒,布局效果的时钟不到0.1毫秒。现在去寻找火焰图中的 。它甚至没有自己的资格。它被无形地折叠进每个组件的渲染时间中,拉长几微秒。你找不到的能量条是无法加速的。DataGridStress``cn()
为什么胜利消失了
这是简洁版。 只是渲染成本的一小部分。降低 Windows 中 CPU 和内存高占用的 10 种方法 - 致知笔记 - 专注教程知识分享 React 实时用于对账、创建和差异元素、变更 DOM 以及让浏览器自行布局和绘制。让职业合并步骤快几倍,就像让咖啡师在杯子上写你名字的速度快了一样。线条依然以意式咖啡机的速度移动。cn()
数学非常严苛。如果是渲染的3%,那么让渲染速度快7倍,实际减少的比例不到3%,而你用的浏览器更接近2倍。这是一个你永远不会感受到的四舍五入误差,正如上面数字所示。cn()
CNFAST的捆绑包大小有多大?
快速谈话通常跳过成本部分,所以我先说说。单独测量cn层,进行压缩和gzip处理:
| 变体 | Gzipped | 三角洲 |
|---|---|---|
| CLSX + 顺风合并 | 8.6 KB | 基线 |
| CNFAST的 | 9.7 KB | +~1 KB |
CNFAST大约大1KB,因为它自带优化的顺风合并分叉和缓存机制。所以你每次加载大约增加1KB,以加快一些不是你瓶颈的部分。这种交换只有在运行时胜利对你的应用来说是真实的,而大多数应用并非如此。
标记模板星号
最大的数字是7.19x,来自标签模板表单。它按调用-站点身份缓存,因此稳定的调用站点在每次重复时跳过连接和哈希。这很聪明,Windows 中公用网络和专用网络的区别 - 致知笔记 - 专注教程知识分享 也是CNFAST最令人印象深刻的地方。cnpx-2 py-1 ${isActive && 'px-4'}``
但要达到它,就意味着把每个呼叫网站都改写成一个字面模板。对于现有的 shadcn 代码库,需要上千次编辑,而 shadcn 原语本身使用调用形式,所以你甚至不拥有那些调用站点。此外,V8已经缓存了纯调用表单的参数,这也是为什么我的Node运行中模板表单只比调用表单快大约25%,而不是领先数倍的原因。所以标题7x是真实的,也是真实迁移中最难达到的数字。cn()
cnfast有TypeScript兼容性问题吗?
有一个未解决的cnfast问题,其类型与clsx冲突,导致Radix和Base UI原语中的-作为渲染函数使用失效。我去找了。在cnfast 0.1.0和我的shadcn加基础UI组件集下,TypeScript保持绿色。对象语法、数组和混合输入都进行了类型检查;运行时没有错误。ClassValueClassDictionaryclassName
但这并不意味着你安全。它很可能只会以特定的原始图案出现,比如那些可以作为渲染函数的组件,而我的仪表盘并没有执行这些。如果触发了,解决办法是把问题调用点投射,或者保留那些特定文件的旧文件,其他地方用cnfast。SlotclassNamecn
你应该在项目中安装cnfast吗?
这是我从一位开发者对另一位开发者的读法。
-
如果你有真正 -重的热路径,每帧有数千个通话,并且已经针对顺风合并的减速设置了,这很值得。这是一个狭窄且真实的案例,CNFAST会提供帮助cn()
-
对于典型的仪表盘、表单和营销页面来说,不值得。microbench 的优势,在浏览器中是 2 倍,Node 中最高可达 7 倍,但无论如何都被 React 的渲染开销淹没,而且你为此付费 1 KB
-
只有当你真的打算重写呼叫网站时,带标签模板才值得迁移,而几乎没人会这样做
我的真实看法
CNFAST正如其名。它是一种更快的输出,字节相同;微基准数据真实且令人印象深刻,且在351个棘手测试案例中准确无误。我对工程设计没有任何抱怨。这是干净、细致的工作。cn()
关键是,这从来不是 React 渲染的瓶颈,所以在真实应用中加速几乎看不见。这是一个完美执行的优化,同时又没有拖慢你的速度。如果你喜欢它,且多出的千字节不困扰你,那就安装它然后继续,因为输出完全相同,没有正确性风险。只要因为它很酷就安装,而不是因为用户会喜欢它。cn()
我大胆的预测是:CNFAST的真正价值可能根本不是毫秒级。也许是因为这让我们几千人几个月来第一次打开火焰图,终于看到渲染图实际花在哪里了。我的都没花钱。我敢打赌你的也不是。cn()

浙公网安备 33010602011771号