【翻译】主题切换零重渲染:介绍 Uniwind Pro

7 阅读10分钟

主题切换零重渲染:介绍 Uniwind Pro

副标题:Uniwind Pro 通过 C++ 更新 className 样式;主题切换、屏幕旋转或深色模式切换时实现零二次渲染。


Uniwind Pro 1.0 发布头图:风车与丘陵的像素风插画

早在去年 11 月我们宣布 Uniwind 首个稳定版时,我就预告过带 C++ 引擎、以及基于 className 的 Reanimated 动画的 Pro 版本。超过 1,500 人加入了等候名单,而 Uniwind 的周 npm 下载量也已突破 115,000。这份势头让我们有信心全力投入。

经过 3 个月 beta 与 7 个 RC 之后,Uniwind Pro 1.0.0 正式发布。

为什么要 Pro?

那么 Pro 和你已经熟悉的开源库有什么不同?

开源版 Uniwind 已经比 NativeWind 快 2.5 倍(见基准测试)。它自带自定义 CSS 解析器、多主题支持,以及自动生成的 React Native 绑定。对许多团队来说,这已经是一次巨大升级。但任何纯 JavaScript 样式方案都有一个根本限制:当主题变化、设备旋转或配色切换时,React 必须二次渲染每一个依赖这些值的组件。小应用、几个屏幕时几乎无感;生产环境里成百上千个带样式组件时?累积起来非常快

如果你的 className 样式可以在 React 完全不知情的情况下更新呢?如果主题变化、旋转与 inset 更新全部在原生层完成,并像 Web 上的 CSS 那样直接提交到 Shadow Tree,会怎样?

这就是 Uniwind Pro 在做的事。如果你关注过 Unistyles 3.0,你会知道我们用 StyleSheet 率先探索了这条路径;Uniwind Pro 把同一理念带到了 className

C++ Shadow Tree 引擎

要实现这一愿景,意味着要用 C++ 从零重建整条样式管线。

Uniwind Pro 由第二代 Unistyles C++ 引擎驱动。要理解它到底哪里不同,先看一个简单的组件:

Card.tsx

<View className="flex-1 bg-background p-4">
    <Text className="text-lg font-bold text-default">Hello from Uniwind Pro!</Text>
</View>

开源版 Uniwind 在 JavaScript 里解析这些 className,再把生成的 style 对象交给 React Native。每次主题变化,React 都会二次渲染你的组件以拿到新值。

在 Pro 里,流程完全不同。组件挂载时,我们的翻译层(基于 react-native-nitro-modulesfolly::dynamic)开始工作:

  • 挂载(Mounting):保留对 ShadowNode 的引用,并把元数据存进 C++ struct
  • 依赖追踪(Dependency tracking):构建时由 Metro 插件精确标出每个样式依赖什么(Theme、ColorScheme、Dimensions、Orientation、Insets、FontScale、Rtl、AdaptiveThemes 或 Variables)
  • 选择性更新(Selective updates):用户切换深色模式时,我们只与 JS 解析器做一次往返;依赖 Theme 的样式会被重算,并直接提交到 Shadow Tree 或原生层(Kotlin / Objective-C)
  • 缓存(Caching):解析结果按 className 与组件状态缓存;状态用 bitmask 跟踪(isDisabledisFocusedisPressedisScopedTheme),因此 pressed / focused 等变体的查找是瞬时的

相对 Unistyles 早期 JS 与 C++ 之间更像「乒乓球」来回的通信方式,这是很大一步改进。选择性更新的理念仍在,但现在同样适用于 className

在渲染 2000+ 个视图的基准测试里,Uniwind Pro 比开源版大约 快 33%。主题切换、旋转与动态 inset 更新期间,应用仍保持响应,因为 React 不参与这些更新。

与必须把每次更新都塞进 React reconciler 的纯 JS 样式库不同,Uniwind Pro 完全绕开这一瓶颈。

基于 className 的 Reanimated 动画

更快的引擎只是故事的一半;另一半是你现在只通过 className 就能表达的能力。

在 Uniwind Pro 之前,若要给 Tailwind 风格的组件做动画,你得退回 style,并手写 Reanimated hook——等于丢掉了 className 带来的好处。

Uniwind Pro 提供翻译层,把基于 className 的动画声明映射到 Reanimated 的 CSS 动画:

Animations.tsx

// Basic entering and exiting
<View className="size-20 bg-primary rounded-xl
    uw-entering-fade-in uw-exiting-fade-out"
/>

// Slide from the left, exit to the right
<View className="size-20 bg-primary rounded-xl
    uw-entering-slide-in-left uw-exiting-slide-out-right"
/>

// Spring physics
<View className="size-20 bg-primary rounded-xl
    uw-entering-zoom-in uw-entering-springify
    uw-entering-damping-10 uw-exiting-zoom-out"
/>

// Custom easing
<View className="size-20 bg-primary rounded-xl
    uw-entering-bounce-in uw-entering-ease-bounce
    uw-exiting-bounce-out"
/>

三个工具前缀覆盖完整动画生命周期:

  • uw-entering:组件挂载时播放的动画(FadeIn、SlideIn、ZoomIn、BounceIn 等)
  • uw-exiting:卸载时播放的动画
  • uw-layout:布局变化时的过渡(LinearTransition、JumpingTransition、FadingTransition 等)

时长、缓动曲线与弹簧参数都可以从 className 控制。下面是一个可重排序列表的实用示例:

AnimatedList.tsx

{
    items.map((item) => (
        <Pressable
            key={item.id}
            onPress={() => removeItem(item.id)}
            className="w-full h-14 bg-primary rounded-xl items-center justify-center
            uw-entering-fade-in
            uw-exiting-fade-out
            uw-layout-linear-transition"
        >
            <Text className="text-white font-bold">{item.label}</Text>
        </Pressable>
    ));
}

删掉一项,列表会平滑归位;新增一项会淡入。没有 hook、没有 animated style、没有命令式代码——只有 className。据作者称,目前没有其他面向 React Native 的 Tailwind 风格库支持这一点。

动画化主题切换

单个组件上的动画很强,但更「抓眼」的是整应用一起换肤时发生的事。

多数 React Native 应用切主题是一瞬间从亮到暗的「闪一下」。Uniwind Pro 加入原生主题过渡,让切换更顺滑、更「上瘾」:

ThemeSwitch.tsx

import { Uniwind, ThemeTransitionPreset } from 'uniwind';

// Fade between themes
Uniwind.setTheme('dark', { preset: ThemeTransitionPreset.Fade });

// Circular reveal from the top-right corner
Uniwind.setTheme('coffee', { preset: ThemeTransitionPreset.CircleTopRight });

// Blur transition from left to right
Uniwind.setTheme('light', { preset: ThemeTransitionPreset.BlurLeftToRight });

我们内置 11 种过渡预设:Fade、Slide(左→右、右→左)、Circle(四角与中心)、Blur 以及方向性 Blur 变体。在 Web 上,这些过渡使用浏览器原生 View Transition API,因此能获得流畅、硬件加速的动画而几乎不用额外成本。

幕后:如何交付一款付费 React Native 库

Pro 里不少功能需要数周专注的 C++ 工作。面向数百万用户发布产品的公司,需要获得顶级、性能关键的能力,而构建这些能力的工程师也值得获得回报。开源版仍是免费、稳定、可快速替换 NativeWind 的方案,已有数千团队信任。Pro 面向需要再往前推一把的团队。

造出技术是第一大挑战;可靠地交到你手里是第二大。npm 包没有 App Store:没有内置许可、下载配额或席位管理。我们不得不从零搭建整套分发基础设施。

我们的 CDN 跑在 Cloudflare Workers 上:

  • Durable Objects:每个许可证对应独立实例与 SQLite;安装包时,配额检查在最近的 Cloudflare 边缘完成,无需往返中央数据库;下载计数原子递增,审计日志在同一事务写入
  • R2 Storage:tarball 全球存储并从边缘分发,充当 CDN
  • D1 Database:订阅与许可证元数据
  • Monthly Alarm System:每个 Durable Object 为下个月 1 号调度 alarm;触发时下载配额自动重置——无需 cron、无需 Lambda、无需额外运维

结果是:体验接近普通 npm install 的速度,CI/CD 不会被拖慢,也没有单点故障。你也不必折腾 .npmrc token:大约每 6 个月运行一次 bunx uniwind-pro 完成授权即可。

作者提到日后可能会单独写一篇文章讲这套架构。

最大的难点

基础设施如今跑得顺,但到达这里意味着解决没有现成剧本的问题。

  • 与 Reanimated 的集成是最难的部分:Reanimated 会覆盖每次 Shadow Tree 提交,Uniwind Pro 必须在精确时间窗口内触发自身更新,才能让 React 与 Reanimated 都认可——为此花了数周实验
  • CSS 解析建立在 React Native 内部与自定义 Nitro 类型之上;把 CSS 值翻译成与 JS 版像素级一致的原生样式,需要大量测试与边界情况排查
  • Suspense 树与普通组件树行为不同:屏幕一冻结就会卸载;我们为它们建了独立更新队列并支持后台更新,确保重新挂载时样式始终是最新的

随团队规模定价

我们做这一切是为了让团队更快交付。下面是价格。

希望 Uniwind Pro 对独立开发者可负担,对大团队也公平:席位越多,单价越低

Seats(席位数)Per Seat / Year(每席/年)Example Total(示例总价)
1–3$993 seats = $297
4–6$496 seats = $444
7–9$299 seats = $531
10–15$915 seats = $585
16+$150 seats = $620

50 人团队每年共 620,约合620**,约合 **12.40 / 席20 人团队人均约 $29.50。作者在 15 席之后有意把单价压得很低:若公司大到有 16+ 名 React Native 开发者,样式库不应再成为预算里被反复争论的那一行。

所有方案按计费,包含本文所述完整 Pro 功能。可在控制台(dashboard)管理席位、邀请成员并生成 CI/CD token。

想先试试看再买?

先试再买

我们做了一个免费 iOS 演示应用,让你在付费前就能体验 C++ 引擎、ShadowTree 更新与动画化主题切换。

它是预编译的 XCFramework,兼容 React Native 0.81.5 与 Expo SDK 54;可在 iOS 模拟器运行,无需账号或购买。

GitHub 下载演示:uniwind-pro-demo

接下来做什么?

Pro 已在生产可用,我们已在规划下一步:

  • Unistyles 4.0:更简单的 Babel 配置、更好的 Web 支持,以及呼声最高的 Expo Go 支持
  • 更多原生能力:native boundaries、父子选择器、共享元素过渡等;Pro 订阅直接资助这些高级能力的研发
  • 更深度的 HeroUI Native 集成:与 HeroUI 的合作在加强,Pro 为其组件库解锁新可能;见 heroui.pro
  • 壮大生态:与更多 UI kit 作者合作,把 className 优先的组件带到 React Native
  • 年底周下载量 100 万:这是我们的目标

致谢

没有从一开始就相信它的人,这一切都不会存在。

感谢 Hubert Bieszczad 与我共同创建 Uniwind,以及那份「开箱即用」的出色 Metro 插件。感谢每一位提前试用、压测每个 RC 的早期用户,以及成千上万愿意尝试 Uniwind 的开发者。

感谢每一家在 Uniwind Pro 上投入时间与资金的团队:你们的支持已经覆盖我们的成本,让我们能把更多时间投入到新功能与修复上。由衷感谢。

Uniwind Pro 1.0.0 已可用于生产。若你的团队交付 React Native 应用,并关心性能、顺滑主题切换与 className 优先动画,这就是作者在等的升级。从 NativeWind 迁移?开箱即可期待最高约 5 倍性能提升(原文表述)。

Happy coding!


原文页脚信息(保留链接语义)


译文说明:正文外链与按钮链接均与英文原文一致;价格与版本号以原文及官方页面为准。若你希望目录日期与上一篇同为 2026-04-17,可自行移动文件或告知我统一调整 processed_at